Merge pull request #1084 from metasim/youtrack-hook
Initial implementation of a JetBrains YouTrack hook for GitBlit.
diff --git a/.classpath b/.classpath
index 2644d44..ccf6a4e 100644
--- a/.classpath
+++ b/.classpath
@@ -5,59 +5,61 @@
<classpathentry kind="src" path="src/test/java" output="bin/test-classes" />
<classpathentry kind="src" path="src/test/bugtraq" output="bin/test-classes" />
<classpathentry kind="src" path="src/main/resources" />
- <classpathentry kind="lib" path="ext/dagger-1.1.0.jar" sourcepath="ext/src/dagger-1.1.0.jar" />
+ <classpathentry kind="lib" path="ext/guice-4.0.jar" sourcepath="ext/src/guice-4.0.jar" />
<classpathentry kind="lib" path="ext/javax.inject-1.jar" sourcepath="ext/src/javax.inject-1.jar" />
- <classpathentry kind="lib" path="ext/dagger-compiler-1.1.0.jar" sourcepath="ext/src/dagger-compiler-1.1.0.jar" />
- <classpathentry kind="lib" path="ext/javawriter-2.1.1.jar" sourcepath="ext/src/javawriter-2.1.1.jar" />
+ <classpathentry kind="lib" path="ext/aopalliance-1.0.jar" sourcepath="ext/src/aopalliance-1.0.jar" />
+ <classpathentry kind="lib" path="ext/guava-18.0.jar" sourcepath="ext/src/guava-18.0.jar" />
+ <classpathentry kind="lib" path="ext/guice-servlet-4.0-gb2.jar" sourcepath="ext/src/guice-servlet-4.0-gb2.jar" />
<classpathentry kind="lib" path="ext/annotations-12.0.jar" sourcepath="ext/src/annotations-12.0.jar" />
<classpathentry kind="lib" path="ext/log4j-1.2.17.jar" sourcepath="ext/src/log4j-1.2.17.jar" />
- <classpathentry kind="lib" path="ext/slf4j-api-1.7.5.jar" sourcepath="ext/src/slf4j-api-1.7.5.jar" />
- <classpathentry kind="lib" path="ext/slf4j-log4j12-1.7.5.jar" sourcepath="ext/src/slf4j-log4j12-1.7.5.jar" />
+ <classpathentry kind="lib" path="ext/slf4j-api-1.7.12.jar" sourcepath="ext/src/slf4j-api-1.7.12.jar" />
+ <classpathentry kind="lib" path="ext/slf4j-log4j12-1.7.12.jar" sourcepath="ext/src/slf4j-log4j12-1.7.12.jar" />
<classpathentry kind="lib" path="ext/javax.mail-1.5.1.jar" sourcepath="ext/src/javax.mail-1.5.1.jar" />
<classpathentry kind="lib" path="ext/javax.servlet-api-3.1.0.jar" sourcepath="ext/src/javax.servlet-api-3.1.0.jar" />
- <classpathentry kind="lib" path="ext/jetty-all-9.2.3.v20140905.jar" sourcepath="ext/src/jetty-all-9.2.3.v20140905.jar" />
- <classpathentry kind="lib" path="ext/wicket-1.4.21.jar" sourcepath="ext/src/wicket-1.4.21.jar" />
- <classpathentry kind="lib" path="ext/wicket-auth-roles-1.4.21.jar" sourcepath="ext/src/wicket-auth-roles-1.4.21.jar" />
- <classpathentry kind="lib" path="ext/wicket-extensions-1.4.21.jar" sourcepath="ext/src/wicket-extensions-1.4.21.jar" />
- <classpathentry kind="lib" path="ext/lucene-core-4.6.0.jar" sourcepath="ext/src/lucene-core-4.6.0.jar" />
- <classpathentry kind="lib" path="ext/lucene-analyzers-common-4.6.0.jar" sourcepath="ext/src/lucene-analyzers-common-4.6.0.jar" />
- <classpathentry kind="lib" path="ext/lucene-highlighter-4.6.0.jar" sourcepath="ext/src/lucene-highlighter-4.6.0.jar" />
- <classpathentry kind="lib" path="ext/lucene-memory-4.6.0.jar" sourcepath="ext/src/lucene-memory-4.6.0.jar" />
- <classpathentry kind="lib" path="ext/lucene-queries-4.6.0.jar" sourcepath="ext/src/lucene-queries-4.6.0.jar" />
- <classpathentry kind="lib" path="ext/lucene-queryparser-4.6.0.jar" sourcepath="ext/src/lucene-queryparser-4.6.0.jar" />
- <classpathentry kind="lib" path="ext/lucene-sandbox-4.6.0.jar" sourcepath="ext/src/lucene-sandbox-4.6.0.jar" />
+ <classpathentry kind="lib" path="ext/jetty-all-9.2.13.v20150730.jar" sourcepath="ext/src/jetty-all-9.2.13.v20150730.jar" />
+ <classpathentry kind="lib" path="ext/wicket-1.4.22.jar" sourcepath="ext/src/wicket-1.4.22.jar" />
+ <classpathentry kind="lib" path="ext/wicket-auth-roles-1.4.22.jar" sourcepath="ext/src/wicket-auth-roles-1.4.22.jar" />
+ <classpathentry kind="lib" path="ext/wicket-extensions-1.4.22.jar" sourcepath="ext/src/wicket-extensions-1.4.22.jar" />
+ <classpathentry kind="lib" path="ext/lucene-core-4.10.4.jar" sourcepath="ext/src/lucene-core-4.10.4.jar" />
+ <classpathentry kind="lib" path="ext/lucene-analyzers-common-4.10.4.jar" sourcepath="ext/src/lucene-analyzers-common-4.10.4.jar" />
+ <classpathentry kind="lib" path="ext/lucene-highlighter-4.10.4.jar" sourcepath="ext/src/lucene-highlighter-4.10.4.jar" />
+ <classpathentry kind="lib" path="ext/lucene-memory-4.10.4.jar" sourcepath="ext/src/lucene-memory-4.10.4.jar" />
+ <classpathentry kind="lib" path="ext/lucene-queries-4.10.4.jar" sourcepath="ext/src/lucene-queries-4.10.4.jar" />
+ <classpathentry kind="lib" path="ext/lucene-queryparser-4.10.4.jar" sourcepath="ext/src/lucene-queryparser-4.10.4.jar" />
+ <classpathentry kind="lib" path="ext/lucene-sandbox-4.10.4.jar" sourcepath="ext/src/lucene-sandbox-4.10.4.jar" />
<classpathentry kind="lib" path="ext/jakarta-regexp-1.4.jar" />
- <classpathentry kind="lib" path="ext/pegdown-1.4.2.jar" sourcepath="ext/src/pegdown-1.4.2.jar" />
- <classpathentry kind="lib" path="ext/parboiled-java-1.1.6.jar" sourcepath="ext/src/parboiled-java-1.1.6.jar" />
- <classpathentry kind="lib" path="ext/parboiled-core-1.1.6.jar" sourcepath="ext/src/parboiled-core-1.1.6.jar" />
- <classpathentry kind="lib" path="ext/asm-4.1.jar" sourcepath="ext/src/asm-4.1.jar" />
- <classpathentry kind="lib" path="ext/asm-tree-4.1.jar" sourcepath="ext/src/asm-tree-4.1.jar" />
- <classpathentry kind="lib" path="ext/asm-analysis-4.1.jar" sourcepath="ext/src/asm-analysis-4.1.jar" />
- <classpathentry kind="lib" path="ext/asm-util-4.1.jar" sourcepath="ext/src/asm-util-4.1.jar" />
+ <classpathentry kind="lib" path="ext/pegdown-1.5.0.jar" sourcepath="ext/src/pegdown-1.5.0.jar" />
+ <classpathentry kind="lib" path="ext/parboiled-java-1.1.7.jar" sourcepath="ext/src/parboiled-java-1.1.7.jar" />
+ <classpathentry kind="lib" path="ext/parboiled-core-1.1.7.jar" sourcepath="ext/src/parboiled-core-1.1.7.jar" />
+ <classpathentry kind="lib" path="ext/asm-5.0.3.jar" sourcepath="ext/src/asm-5.0.3.jar" />
+ <classpathentry kind="lib" path="ext/asm-tree-5.0.3.jar" sourcepath="ext/src/asm-tree-5.0.3.jar" />
+ <classpathentry kind="lib" path="ext/asm-analysis-5.0.3.jar" sourcepath="ext/src/asm-analysis-5.0.3.jar" />
+ <classpathentry kind="lib" path="ext/asm-util-5.0.3.jar" sourcepath="ext/src/asm-util-5.0.3.jar" />
<classpathentry kind="lib" path="ext/wikitext-core-1.4.jar" sourcepath="ext/src/wikitext-core-1.4.jar" />
<classpathentry kind="lib" path="ext/twiki-core-1.4.jar" sourcepath="ext/src/twiki-core-1.4.jar" />
<classpathentry kind="lib" path="ext/textile-core-1.4.jar" sourcepath="ext/src/textile-core-1.4.jar" />
<classpathentry kind="lib" path="ext/tracwiki-core-1.4.jar" sourcepath="ext/src/tracwiki-core-1.4.jar" />
<classpathentry kind="lib" path="ext/mediawiki-core-1.4.jar" sourcepath="ext/src/mediawiki-core-1.4.jar" />
<classpathentry kind="lib" path="ext/confluence-core-1.4.jar" sourcepath="ext/src/confluence-core-1.4.jar" />
- <classpathentry kind="lib" path="ext/org.eclipse.jgit-3.5.1.201410131835-r.jar" sourcepath="ext/src/org.eclipse.jgit-3.5.1.201410131835-r.jar" />
- <classpathentry kind="lib" path="ext/jsch-0.1.50.jar" sourcepath="ext/src/jsch-0.1.50.jar" />
+ <classpathentry kind="lib" path="ext/org.eclipse.jgit-4.1.1.201511131810-r.jar" sourcepath="ext/src/org.eclipse.jgit-4.1.1.201511131810-r.jar" />
+ <classpathentry kind="lib" path="ext/jsch-0.1.53.jar" sourcepath="ext/src/jsch-0.1.53.jar" />
<classpathentry kind="lib" path="ext/JavaEWAH-0.7.9.jar" sourcepath="ext/src/JavaEWAH-0.7.9.jar" />
- <classpathentry kind="lib" path="ext/httpclient-4.1.3.jar" sourcepath="ext/src/httpclient-4.1.3.jar" />
- <classpathentry kind="lib" path="ext/httpcore-4.1.4.jar" sourcepath="ext/src/httpcore-4.1.4.jar" />
- <classpathentry kind="lib" path="ext/commons-logging-1.1.1.jar" sourcepath="ext/src/commons-logging-1.1.1.jar" />
+ <classpathentry kind="lib" path="ext/httpclient-4.3.6.jar" sourcepath="ext/src/httpclient-4.3.6.jar" />
+ <classpathentry kind="lib" path="ext/httpcore-4.3.3.jar" sourcepath="ext/src/httpcore-4.3.3.jar" />
+ <classpathentry kind="lib" path="ext/commons-logging-1.1.3.jar" sourcepath="ext/src/commons-logging-1.1.3.jar" />
<classpathentry kind="lib" path="ext/commons-codec-1.7.jar" sourcepath="ext/src/commons-codec-1.7.jar" />
- <classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar" sourcepath="ext/src/org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar" />
- <classpathentry kind="lib" path="ext/bcprov-jdk15on-1.49.jar" sourcepath="ext/src/bcprov-jdk15on-1.49.jar" />
- <classpathentry kind="lib" path="ext/bcmail-jdk15on-1.49.jar" sourcepath="ext/src/bcmail-jdk15on-1.49.jar" />
- <classpathentry kind="lib" path="ext/bcpkix-jdk15on-1.49.jar" sourcepath="ext/src/bcpkix-jdk15on-1.49.jar" />
- <classpathentry kind="lib" path="ext/sshd-core-0.12.0.jar" sourcepath="ext/src/sshd-core-0.12.0.jar" />
- <classpathentry kind="lib" path="ext/mina-core-2.0.7.jar" sourcepath="ext/src/mina-core-2.0.7.jar" />
+ <classpathentry kind="lib" path="ext/org.eclipse.jdt.annotation-1.1.0.jar" />
+ <classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar" sourcepath="ext/src/org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar" />
+ <classpathentry kind="lib" path="ext/bcprov-jdk15on-1.52.jar" sourcepath="ext/src/bcprov-jdk15on-1.52.jar" />
+ <classpathentry kind="lib" path="ext/bcmail-jdk15on-1.52.jar" sourcepath="ext/src/bcmail-jdk15on-1.52.jar" />
+ <classpathentry kind="lib" path="ext/bcpkix-jdk15on-1.52.jar" sourcepath="ext/src/bcpkix-jdk15on-1.52.jar" />
+ <classpathentry kind="lib" path="ext/sshd-core-1.0.0.jar" sourcepath="ext/src/sshd-core-1.0.0.jar" />
+ <classpathentry kind="lib" path="ext/mina-core-2.0.9.jar" sourcepath="ext/src/mina-core-2.0.9.jar" />
<classpathentry kind="lib" path="ext/rome-0.9.jar" sourcepath="ext/src/rome-0.9.jar" />
<classpathentry kind="lib" path="ext/jdom-1.0.jar" sourcepath="ext/src/jdom-1.0.jar" />
- <classpathentry kind="lib" path="ext/gson-1.7.2.jar" sourcepath="ext/src/gson-1.7.2.jar" />
- <classpathentry kind="lib" path="ext/groovy-all-1.8.8.jar" sourcepath="ext/src/groovy-all-1.8.8.jar" />
- <classpathentry kind="lib" path="ext/unboundid-ldapsdk-2.3.0.jar" sourcepath="ext/src/unboundid-ldapsdk-2.3.0.jar" />
+ <classpathentry kind="lib" path="ext/gson-2.3.1.jar" sourcepath="ext/src/gson-2.3.1.jar" />
+ <classpathentry kind="lib" path="ext/groovy-all-2.4.4.jar" sourcepath="ext/src/groovy-all-2.4.4.jar" />
+ <classpathentry kind="lib" path="ext/unboundid-ldapsdk-2.3.8.jar" sourcepath="ext/src/unboundid-ldapsdk-2.3.8.jar" />
<classpathentry kind="lib" path="ext/ivy-2.2.0.jar" sourcepath="ext/src/ivy-2.2.0.jar" />
<classpathentry kind="lib" path="ext/jcalendar-1.3.2.jar" />
<classpathentry kind="lib" path="ext/commons-compress-1.4.1.jar" sourcepath="ext/src/commons-compress-1.4.1.jar" />
@@ -66,16 +68,15 @@
<classpathentry kind="lib" path="ext/force-partner-api-24.0.0.jar" sourcepath="ext/src/force-partner-api-24.0.0.jar" />
<classpathentry kind="lib" path="ext/force-wsc-24.0.0.jar" sourcepath="ext/src/force-wsc-24.0.0.jar" />
<classpathentry kind="lib" path="ext/js-1.7R2.jar" sourcepath="ext/src/js-1.7R2.jar" />
- <classpathentry kind="lib" path="ext/freemarker-2.3.19.jar" sourcepath="ext/src/freemarker-2.3.19.jar" />
- <classpathentry kind="lib" path="ext/waffle-jna-1.5.jar" sourcepath="ext/src/waffle-jna-1.5.jar" />
- <classpathentry kind="lib" path="ext/platform-3.5.0.jar" sourcepath="ext/src/platform-3.5.0.jar" />
- <classpathentry kind="lib" path="ext/jna-3.5.0.jar" sourcepath="ext/src/jna-3.5.0.jar" />
- <classpathentry kind="lib" path="ext/guava-13.0.1.jar" sourcepath="ext/src/guava-13.0.1.jar" />
- <classpathentry kind="lib" path="ext/libpam4j-1.7.jar" sourcepath="ext/src/libpam4j-1.7.jar" />
- <classpathentry kind="lib" path="ext/args4j-2.0.26.jar" sourcepath="ext/src/args4j-2.0.26.jar" />
- <classpathentry kind="lib" path="ext/jedis-2.3.1.jar" sourcepath="ext/src/jedis-2.3.1.jar" />
+ <classpathentry kind="lib" path="ext/freemarker-2.3.22.jar" sourcepath="ext/src/freemarker-2.3.22.jar" />
+ <classpathentry kind="lib" path="ext/waffle-jna-1.7.3.jar" sourcepath="ext/src/waffle-jna-1.7.3.jar" />
+ <classpathentry kind="lib" path="ext/jna-4.1.0.jar" sourcepath="ext/src/jna-4.1.0.jar" />
+ <classpathentry kind="lib" path="ext/jna-platform-4.1.0.jar" sourcepath="ext/src/jna-platform-4.1.0.jar" />
+ <classpathentry kind="lib" path="ext/libpam4j-1.8.jar" sourcepath="ext/src/libpam4j-1.8.jar" />
+ <classpathentry kind="lib" path="ext/args4j-2.0.29.jar" sourcepath="ext/src/args4j-2.0.29.jar" />
+ <classpathentry kind="lib" path="ext/jedis-2.6.2.jar" sourcepath="ext/src/jedis-2.6.2.jar" />
<classpathentry kind="lib" path="ext/commons-pool2-2.0.jar" sourcepath="ext/src/commons-pool2-2.0.jar" />
- <classpathentry kind="lib" path="ext/pf4j-0.8.0.jar" sourcepath="ext/src/pf4j-0.8.0.jar" />
+ <classpathentry kind="lib" path="ext/pf4j-0.9.0.jar" sourcepath="ext/src/pf4j-0.9.0.jar" />
<classpathentry kind="lib" path="ext/tika-core-1.5.jar" sourcepath="ext/src/tika-core-1.5.jar" />
<classpathentry kind="lib" path="ext/jsoup-1.7.3.jar" sourcepath="ext/src/jsoup-1.7.3.jar" />
<classpathentry kind="lib" path="ext/junit-4.11.jar" sourcepath="ext/src/junit-4.11.jar" />
@@ -88,6 +89,9 @@
<classpathentry kind="lib" path="ext/json-20080701.jar" sourcepath="ext/src/json-20080701.jar" />
<classpathentry kind="lib" path="ext/selenium-api-2.28.0.jar" sourcepath="ext/src/selenium-api-2.28.0.jar" />
<classpathentry kind="lib" path="ext/commons-exec-1.1.jar" sourcepath="ext/src/commons-exec-1.1.jar" />
+ <classpathentry kind="lib" path="ext/platform-3.4.0.jar" sourcepath="ext/src/platform-3.4.0.jar" />
+ <classpathentry kind="lib" path="ext/mockito-core-1.10.19.jar" sourcepath="ext/src/mockito-core-1.10.19.jar" />
+ <classpathentry kind="lib" path="ext/objenesis-2.1.jar" sourcepath="ext/src/objenesis-2.1.jar" />
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" />
<classpathentry kind="src" path="src/main/dagger">
<attributes>
diff --git a/.gitignore b/.gitignore
index 6c262bb..e268ccb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,11 @@
+tags
/temp
/lib
/ext
/build
/site
/git
+/lucene
/build.properties
/federation.properties
/mailtest.properties
@@ -27,3 +29,4 @@
/**/.idea
/**/init.lua
/**/session
+/nbproject/private
diff --git a/.gitmodules b/.gitmodules
index 01eaa2c..25cab4a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
[submodule "src/main/distrib/data/gitignore"]
path = src/main/distrib/data/gitignore
url = https://github.com/github/gitignore.git
+[submodule "src/main/js/prosemirror"]
+ path = src/main/js/prosemirror
+ url = https://github.com/ProseMirror/prosemirror.git
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..e47f177
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,2 @@
+James Moger <james.moger@gitblit.com> James Moger <james.moger@gmail.com>
+James Moger <james.moger@gitblit.com> James Moger <jmoger@vas.com>
diff --git a/HOME.md b/HOME.md
index 7c5449d..89527ee 100644
--- a/HOME.md
+++ b/HOME.md
@@ -33,6 +33,8 @@
[[src/site/setup_viewer.mkd]]
[[src/site/administration.mkd]]
[[src/site/setup_scaling.mkd]]
+[[src/site/setup_filestore.mkd]]
+
### Gitblit Tickets
diff --git a/NOTICE b/NOTICE
index da61b20..69d7c74 100644
--- a/NOTICE
+++ b/NOTICE
@@ -358,3 +358,12 @@
Apache License 2.0
https://github.com/decebals/pf4j
+
+---------------------------------------------------------------------------
+google-guice
+---------------------------------------------------------------------------
+ google-guice, release under the
+ Apache License 2.0
+
+ https://code.google.com/p/google-guice
+
\ No newline at end of file
diff --git a/README.markdown b/README.markdown
index a226049..53776f8 100644
--- a/README.markdown
+++ b/README.markdown
@@ -24,7 +24,6 @@
| Source | Location |
| ------------- |--------------------------------------------------------|
| Documentation | [Gitblit website](http://gitblit.com) |
-| Issues | [Google Code](http://code.google.com/p/gitblit) |
| Forums | [Google Groups](https://groups.google.com/forum/#!forum/gitblit) |
| Twitter | @gitblit or @jamesmoger |
| Google+ | +gitblit or +jamesmoger |
@@ -32,27 +31,7 @@
Contributing
------------
-GitHub pull requests or Gitblit Tickets are preferred. Any contributions must be distributed under the terms of the [Apache Software Foundation license, version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
-
-**Workflow**
-
-Gitblit practices the [git-flow][1] branching model.
-
-- **master** is the current stable release + fixes accumulated since release.
-- **develop** is the integration branch for the next major release.
-- **ticket/N** are feature or hotfix branches to be merged to **master** or **develop**, as appropriate.
-
-**Feature Development**
-
-Development of new features is mostly done using [Gitblit Tickets][2] hosted at [dev.gitblit.com][3]. This allows continuous dogfooding and improvement of Gitbit's own issue-tracker and pull-request mechanism.
-
-**Release Planning**
-
-Release planning is mostly done using Gitblit Milestones and Gitblit Tickets hosted at [dev.gitblit.com][3].
-
-**Releasing**
-
-When Gitblit is preparing for a release, a **release-{milestone}** branch will be created, tested, & fixed until it is ready to be merged to **master** and tagged as the next major release. After the release is tagged, the **release-{milestone}** branch will also be merged back into **develop** and then the release branch will be removed.
+GitHub pull requests are preferred. Any contributions must be distributed under the terms of the [Apache Software Foundation license, version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
Building Gitblit
----------------
@@ -76,6 +55,3 @@
1. If you are running Ant from an ANSI-capable console, consider setting the `MX_COLOR` environment variable before executing Ant.<pre>set MX_COLOR=true</pre>
2. The build script will honor your Maven proxy settings. If you need to fine-tune this, please review the [settings.moxie](http://gitblit.github.io/moxie/settings.html) documentation.
-[1]: http://nvie.com/posts/a-successful-git-branching-model
-[2]: http://gitblit.com/tickets_overview.html
-[3]: https://dev.gitblit.com
diff --git a/build.moxie b/build.moxie
index 8dcb975..8eb2bbf 100644
--- a/build.moxie
+++ b/build.moxie
@@ -3,23 +3,23 @@
#
# Specify minimum Moxie version required to build
-requires: 0.9.3
+requires: 0.9.4
# Project Metadata
name: Gitblit
description: pure Java Git solution
groupId: com.gitblit
artifactId: gitblit
-version: 1.6.3-SNAPSHOT
+version: 1.7.2-SNAPSHOT
inceptionYear: 2011
# Current stable release
-releaseVersion: 1.6.2
-releaseDate: 2014-10-28
+releaseVersion: 1.7.1
+releaseDate: 2015-11-23
# Project urls
url: 'http://gitblit.com'
-issuesUrl: 'http://code.google.com/p/gitblit/issues/list'
+issuesUrl: 'https://github.com/gitblit/gitblit'
socialNetworkUrl: 'https://plus.google.com/114464678392593421684'
forumUrl: 'http://groups.google.com/group/gitblit'
mavenUrl: 'http://gitblit.github.io/gitblit-maven'
@@ -58,7 +58,7 @@
sourceDirectories:
- compile 'src/main/java'
- compile 'src/main/bugtraq'
-- compile 'src/main/dagger' apt
+- compile 'src/main/gen' apt
- test 'src/test/java'
- test 'src/test/bugtraq'
# Moxie supports one site-scoped directory for mx:doc
@@ -95,23 +95,27 @@
registeredRepositories:
- { id: eclipse, url: 'http://repo.eclipse.org/content/groups/releases' }
- { id: eclipse-snapshots, url: 'http://repo.eclipse.org/content/groups/snapshots' }
-- { id: atlassian-contrib, url: 'https://maven.atlassian.com/content/repositories/atlassian-3rdparty' }
+- { id: gitblit, url: 'http://gitblit.github.io/gitblit-maven' }
# Source all dependencies from the following repositories in the specified order
-repositories: central, eclipse-snapshots, eclipse, atlassian-contrib
+repositories: central, eclipse-snapshots, eclipse, gitblit
# Convenience properties for dependencies
properties: {
- jetty.version : 9.2.9.v20150224
- wicket.version : 1.4.21
- lucene.version : 4.6.0
- jgit.version : 3.5.1.201410131835-r
- groovy.version : 1.8.8
- bouncycastle.version : 1.49
+ jetty.version : 9.2.13.v20150730
+ slf4j.version : 1.7.12
+ wicket.version : 1.4.22
+ lucene.version : 4.10.4
+ jgit.version : 4.1.1.201511131810-r
+ groovy.version : 2.4.4
+ bouncycastle.version : 1.52
selenium.version : 2.28.0
wikitext.version : 1.4
- sshd.version: 0.12.0
- mina.version: 2.0.7
+ sshd.version: 1.0.0
+ mina.version: 2.0.9
+ guice.version : 4.0
+ # Gitblit maintains a fork of guice-servlet
+ guice-servlet.version : 4.0-gb2
}
# Dependencies
@@ -126,15 +130,14 @@
#
dependencies:
-# Dagger dependency injection library (annotation processor)
-- compile 'com.squareup.dagger:dagger:1.1.0' :war apt
-- compile 'com.squareup.dagger:dagger-compiler:1.1.0' :war optional apt
-# Standard dependencies
+- compile 'com.google.inject:guice:${guice.version}' :war :fedclient
+- compile 'com.google.inject.extensions:guice-servlet:${guice-servlet.version}' :war
+- compile 'com.google.guava:guava:18.0' :war :fedclient
- compile 'com.intellij:annotations:12.0' :war
-- compile 'log4j:log4j:1.2.17' :war :fedclient :authority
-- compile 'org.slf4j:slf4j-api:1.7.5' :war :fedclient :authority
-- compile 'org.slf4j:slf4j-log4j12:1.7.5' :war :fedclient :authority
-- compile 'com.sun.mail:javax.mail:1.5.1' :war :authority
+- compile 'log4j:log4j:1.2.17' :war :fedclient :manager
+- compile 'org.slf4j:slf4j-api:${slf4j.version}' :war :fedclient :manager
+- compile 'org.slf4j:slf4j-log4j12:${slf4j.version}' :war :fedclient :manager
+- compile 'com.sun.mail:javax.mail:1.5.1' :war
- compile 'javax.servlet:javax.servlet-api:3.1.0' :fedclient
- compile 'org.eclipse.jetty.aggregate:jetty-all:${jetty.version}' @jar
- compile 'org.apache.wicket:wicket:${wicket.version}' :war !org.mockito
@@ -145,36 +148,36 @@
- compile 'org.apache.lucene:lucene-highlighter:${lucene.version}' :war :fedclient
- compile 'org.apache.lucene:lucene-memory:${lucene.version}' :war :fedclient
- compile 'org.apache.lucene:lucene-queryparser:${lucene.version}' :war :fedclient
-- compile 'org.pegdown:pegdown:1.4.2' :war
+- compile 'org.pegdown:pegdown:1.5.0' :war
- compile 'org.fusesource.wikitext:wikitext-core:${wikitext.version}' :war
- compile 'org.fusesource.wikitext:twiki-core:${wikitext.version}' :war
- compile 'org.fusesource.wikitext:textile-core:${wikitext.version}' :war
- compile 'org.fusesource.wikitext:tracwiki-core:${wikitext.version}' :war
- compile 'org.fusesource.wikitext:mediawiki-core:${wikitext.version}' :war
- compile 'org.fusesource.wikitext:confluence-core:${wikitext.version}' :war
-- compile 'org.eclipse.jgit:org.eclipse.jgit:${jgit.version}' :war :fedclient :manager :authority !junit
-- compile 'org.eclipse.jgit:org.eclipse.jgit.http.server:${jgit.version}' :war :manager :authority !junit
-- compile 'org.bouncycastle:bcprov-jdk15on:${bouncycastle.version}' :war :authority
-- compile 'org.bouncycastle:bcmail-jdk15on:${bouncycastle.version}' :war :authority
-- compile 'org.bouncycastle:bcpkix-jdk15on:${bouncycastle.version}' :war :authority
+- compile 'org.eclipse.jgit:org.eclipse.jgit:${jgit.version}' :war :fedclient :manager !junit
+- compile 'org.eclipse.jgit:org.eclipse.jgit.http.server:${jgit.version}' :war :manager !junit
+- compile 'org.bouncycastle:bcprov-jdk15on:${bouncycastle.version}' :war
+- compile 'org.bouncycastle:bcmail-jdk15on:${bouncycastle.version}' :war
+- compile 'org.bouncycastle:bcpkix-jdk15on:${bouncycastle.version}' :war
- compile 'org.apache.sshd:sshd-core:${sshd.version}' :war !org.easymock
- compile 'org.apache.mina:mina-core:${mina.version}' :war !org.easymock
- compile 'rome:rome:0.9' :war :manager :api
-- compile 'com.google.code.gson:gson:1.7.2' :war :fedclient :manager :api
+- compile 'com.google.code.gson:gson:2.3.1' :war :fedclient :manager :api
- compile 'org.codehaus.groovy:groovy-all:${groovy.version}' :war
-- compile 'com.unboundid:unboundid-ldapsdk:2.3.0' :war
+- compile 'com.unboundid:unboundid-ldapsdk:2.3.8' :war
- compile 'org.apache.ivy:ivy:2.2.0' :war
- compile 'com.toedter:jcalendar:1.3.2' :authority
- compile 'org.apache.commons:commons-compress:1.4.1' :war
- compile 'commons-io:commons-io:2.2' :war
- compile 'com.force.api:force-partner-api:24.0.0' :war
-- compile 'org.freemarker:freemarker:2.3.19' :war
-- compile 'com.github.dblock.waffle:waffle-jna:1.5' :war
-- compile 'org.kohsuke:libpam4j:1.7' :war
-- compile 'args4j:args4j:2.0.26' :war :fedclient :authority
+- compile 'org.freemarker:freemarker:2.3.22' :war
+- compile 'com.github.dblock.waffle:waffle-jna:1.7.3' :war
+- compile 'org.kohsuke:libpam4j:1.8' :war
+- compile 'args4j:args4j:2.0.29' :war :fedclient
- compile 'commons-codec:commons-codec:1.7' :war
-- compile 'redis.clients:jedis:2.3.1' :war
-- compile 'ro.fortsoft.pf4j:pf4j:0.8.0' :war
+- compile 'redis.clients:jedis:2.6.2' :war
+- compile 'ro.fortsoft.pf4j:pf4j:0.9.0' :war
- compile 'org.apache.tika:tika-core:1.5' :war
- compile 'org.jsoup:jsoup:1.7.3' :war
- test 'junit'
@@ -182,6 +185,7 @@
- test 'org.seleniumhq.selenium:selenium-java:${selenium.version}' @jar
- test 'org.seleniumhq.selenium:selenium-support:${selenium.version}' @jar
- test 'org.seleniumhq.selenium:selenium-firefox-driver:${selenium.version}'
+- test 'org.mockito:mockito-core:1.10.19'
# Dependencies with the "build" scope are retrieved
# and injected into the Ant runtime classpath
- build 'jacoco'
diff --git a/build.xml b/build.xml
index 99c2966..f3414d7 100644
--- a/build.xml
+++ b/build.xml
@@ -8,7 +8,7 @@
documentation @ http://gitblit.github.io/moxie
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-->
- <property name="moxie.version" value="0.9.3" />
+ <property name="moxie.version" value="0.9.4" />
<property name="moxie.url" value="http://gitblit.github.io/moxie/maven" />
<property name="moxie.jar" value="moxie-toolkit-${moxie.version}.jar" />
<property name="moxie.dir" value="${user.home}/.moxie" />
@@ -41,9 +41,10 @@
<mx:init verbose="no" mxroot="${moxie.dir}" />
<!-- Set Ant project properties -->
- <property name="distribution.zipfile" value="gitblit-${project.version}.zip" />
- <property name="distribution.tgzfile" value="gitblit-${project.version}.tar.gz" />
- <property name="distribution.warfile" value="gitblit-${project.version}.war" />
+ <property name="release.name" value="gitblit-${project.version}"/>
+ <property name="distribution.zipfile" value="${release.name}.zip" />
+ <property name="distribution.tgzfile" value="${release.name}.tar.gz" />
+ <property name="distribution.warfile" value="${release.name}.war" />
<property name="fedclient.zipfile" value="fedclient-${project.version}.zip" />
<property name="manager.zipfile" value="manager-${project.version}.zip" />
<property name="authority.zipfile" value="authority-${project.version}.zip" />
@@ -81,10 +82,9 @@
<fileset dir="${project.distrib.dir}/data" />
</copy>
- <!-- copy gitblit.properties to the source directory.
- this file is only used for parsing setting descriptions. -->
- <copy tofile="${project.src.dir}/reference.properties" overwrite="true"
- file="${project.distrib.dir}/data/gitblit.properties" />
+ <!-- copy defaults.properties to the source directory -->
+ <copy tofile="${project.src.dir}/defaults.properties" overwrite="true"
+ file="${project.distrib.dir}/data/defaults.properties" />
<!-- copy clientapps.json to the source directory.
this file is only used if a local file is not provided. -->
@@ -101,8 +101,8 @@
-->
<target name="compile" depends="setup" description="compiles Gitblit from source">
- <!-- Generate the Keys class from the properties file -->
- <mx:keys propertiesfile="${project.distrib.dir}/data/gitblit.properties"
+ <!-- Generate the Keys class from the defaults.properties file -->
+ <mx:keys propertiesfile="${project.distrib.dir}/data/defaults.properties"
outputclass="com.gitblit.Keys"
todir="${project.src.dir}" />
@@ -170,14 +170,17 @@
<echo>Building Gitblit GO ${project.version}</echo>
- <local name="go.dir" />
- <property name="go.dir" value="${project.outputDirectory}/go" />
+ <local name="go.dir"/>
+ <property name="go.dir" value="${project.outputDirectory}/go"/>
<delete dir="${go.dir}" />
+
+ <local name="go.release.dir" />
+ <property name="go.release.dir" value="${go.dir}/${release.name}" />
<local name="webinf" />
<property name="webinf" value="${project.compileOutputDirectory}/WEB-INF" />
- <prepareDataDirectory toDir="${go.dir}/data" />
+ <prepareDataDirectory toDir="${go.release.dir}/data" />
<!-- Copy the web.xml from the prototype web.xml -->
<copy todir="${webinf}" overwrite="true">
@@ -188,42 +191,43 @@
</copy>
<!-- Build jar -->
- <mx:jar destfile="${go.dir}/gitblit.jar" includeresources="true">
+ <mx:jar destfile="${go.release.dir}/gitblit.jar" includeresources="true">
<mainclass name="com.gitblit.GitBlitServer" />
<launcher paths="ext" />
</mx:jar>
<!-- Generate the docs for the GO build -->
- <generateDocs toDir="${go.dir}/docs" />
-
+ <generateDocs toDir="${go.release.dir}/docs" />
+
<!-- Create GO Windows Zip deployment -->
<mx:zip basedir="${go.dir}">
<!-- LICENSE and NOTICE -->
- <fileset dir="${basedir}" >
+ <zipfileset dir="${basedir}" prefix="${release.name}">
<include name="LICENSE" />
<include name="NOTICE" />
- </fileset>
+ </zipfileset>
<!-- Windows distrib files -->
- <zipfileset dir="${project.distrib.dir}/win" />
+ <zipfileset dir="${project.distrib.dir}/win" prefix="${release.name}"/>
<!-- Gitblit Authority data -->
- <zipfileset dir="${project.distrib.dir}/data/certs" prefix="data/certs" />
+ <zipfileset dir="${project.distrib.dir}/data/certs" prefix="${release.name}/data/certs" />
+
<!-- include all dependencies -->
- <dependencies prefix="ext" />
+ <dependencies prefix="${release.name}/ext" />
</mx:zip>
<!-- Create GO Linux/OSX tar.gz deployment -->
<mx:tar basedir="${go.dir}" longfile="gnu" compression="gzip">
<!-- LICENSE and NOTICE -->
- <fileset dir="${basedir}" >
+ <zipfileset dir="${basedir}" prefix="${release.name}">
<include name="LICENSE" />
<include name="NOTICE" />
- </fileset>
+ </zipfileset>
<!-- Linux/OSX distrib files -->
- <tarfileset dir="${project.distrib.dir}/linux" filemode="755" />
+ <tarfileset dir="${project.distrib.dir}/linux" filemode="755" prefix="${release.name}"/>
<!-- Gitblit Authority data -->
- <zipfileset dir="${project.distrib.dir}/data/certs" prefix="data/certs" />
+ <zipfileset dir="${project.distrib.dir}/data/certs" prefix="${release.name}/data/certs" />
<!-- include all dependencies -->
- <dependencies prefix="ext" />
+ <dependencies prefix="${release.name}/ext" />
</mx:tar>
</target>
@@ -257,7 +261,7 @@
</mx:webxml>
<!-- Gitblit jar -->
- <mx:jar destfile="${webinf}/lib/gitblit.jar" includeresources="false" />
+ <mx:jar destfile="${webinf}/lib/gitblit-${project.version}.jar" includeresources="false" />
<!-- Build the WAR file -->
<mx:zip basedir="${war.dir}" destfile="${project.targetDirectory}/${distribution.warfile}" compress="true" >
@@ -290,7 +294,7 @@
classes, exclude any classes in classpath jars -->
<mx:genjar tag="" includeresources="false" excludeClasspathJars="true"
destfile="${project.targetDirectory}/fedclient.jar"
- excludes="**/.class,**/*.java, **/Thumbs.db, **/*.mkd, com/gitblit/wicket/**">
+ excludes="**/.class, **/*.java, **/Thumbs.db, **/*.mkd, **/*.md, **/*.css, com/gitblit/wicket/**">
<mainclass name="com.gitblit.FederationClient" />
<class name="com.gitblit.Keys" />
<launcher paths="ext" />
@@ -330,7 +334,8 @@
<!-- generate jar by traversing the class hierarchy of the specified
classes, exclude any classes in classpath jars -->
<mx:genjar tag="" includeResources="false" excludeClasspathJars="true"
- destfile="${project.targetDirectory}/manager.jar">
+ destfile="${project.targetDirectory}/manager.jar"
+ excludes="**/.class, **/*.java, **/Thumbs.db, **/*.mkd, **/*.md, **/*.css, com/gitblit/wicket/**">
<resource file="${project.src.dir}/com/gitblit/client/splash.png" />
<resource file="${project.resources.dir}/gitblt-favicon.png" />
<resource file="${project.resources.dir}/gitweb-favicon.png" />
@@ -406,9 +411,10 @@
<!-- Build API Library jar -->
<mx:genjar tag="" includeResources="false" excludeClasspathJars="true"
- destfile="${project.targetDirectory}/gbapi-${project.version}.jar">
+ destfile="${project.targetDirectory}/gbapi-${project.version}.jar"
+ excludes="**/.class, **/*.java, **/Thumbs.db, **/*.mkd, **/*.md, **/*.css, com/gitblit/wicket/**">
+ <mainclass name="com.gitblit.client.GitblitClient" />
<class name="com.gitblit.Keys" />
- <class name="com.gitblit.client.GitblitClient" />
<class name="com.gitblit.models.FederationModel" />
<class name="com.gitblit.models.FederationProposal" />
<class name="com.gitblit.models.FederationSet" />
@@ -427,7 +433,7 @@
</zip>
<!-- Build API JavaDoc jar -->
- <mx:javadoc destdir="${javadoc.dir}" redirect="true">
+ <mx:javadoc destdir="${javadoc.dir}" charset="utf-8" encoding="utf-8" docencoding="utf-8" redirect="true">
<fileset dir="${project.src.dir}" defaultexcludes="yes">
<include name="com/gitblit/Constants.java"/>
<include name="com/gitblit/GitBlitException.java"/>
@@ -507,6 +513,8 @@
<page name="bugtraq" src="setup_bugtraq.mkd" />
<page name="mirrors" src="setup_mirrors.mkd" />
<page name="scaling" src="setup_scaling.mkd" />
+ <page name="fail2ban" src="setup_fail2ban.mkd" />
+ <page name="filestore (Git LFS)" src="setup_filestore.mkd" />
<divider />
<page name="Gitblit as a viewer" src="setup_viewer.mkd" />
</menu>
@@ -546,8 +554,6 @@
<page name="release history" out="releases.html">
<template src="releasehistory.ftl" data="${releaselog}" />
</page>
- <divider />
- <page name="roadmap" src="roadmap.mkd" />
</menu>
<menu name="downloads">
@@ -578,7 +584,6 @@
<link name="Github" src="${project.scmUrl}" />
<link name="Issues" src="${project.issuesUrl}" />
<link name="Discussion" src="${project.forumUrl}" />
- <link name="Google+" src="${project.socialNetworkUrl}" />
<link name="Twitter" src="https://twitter.com/gitblit" />
<link name="Ohloh" src="http://www.ohloh.net/p/gitblit" />
<divider />
@@ -586,7 +591,6 @@
<link name="Gitblit SSH and Plugin Management asciicast" src="https://asciinema.org/a/9342" />
<link name="GitMinutes #29: James Moger on Gitblit" src="http://episodes.gitminutes.com/2014/05/gitminutes-29-james-moger-on-gitblit.html" />
<divider />
- <link name="+JamesMoger" src="https://plus.google.com/+JamesMoger" />
<link name="@JamesMoger" src="https://twitter.com/JamesMoger" />
</menu>
<divider />
@@ -594,7 +598,7 @@
<replace token="%GCURL%" value="${gc.url}" />
- <properties token="%PROPERTIES%" file="${project.distrib.dir}/data/gitblit.properties" />
+ <properties token="%PROPERTIES%" file="${project.distrib.dir}/data/defaults.properties" />
<regex searchPattern="\b(issue)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='http://code.google.com/p/gitblit/issues/detail?id=$3'>issue $3</a>" />
<regex searchPattern="\b(pr|pull request)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='https://github.com/gitblit/gitblit/pull/$3'>pull request #$3</a>" />
@@ -890,12 +894,11 @@
<link name="Github" src="${project.scmUrl}" />
<link name="Issues" src="${project.issuesUrl}" />
<link name="Discussion" src="${project.forumUrl}" />
- <link name="Google+" src="${project.socialNetworkUrl}" />
<link name="Ohloh" src="http://www.ohloh.net/p/gitblit" />
</menu>
</structure>
- <properties token="%PROPERTIES%" file="${project.distrib.dir}/data/gitblit.properties" />
+ <properties token="%PROPERTIES%" file="${project.distrib.dir}/data/defaults.properties" />
<regex searchPattern="\b(issue)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='http://code.google.com/p/gitblit/issues/detail?id=$3'>issue $3</a>" />
<regex searchPattern="\b(pr|pull request)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='https://github.com/gitblit/gitblit/pull/$3'>pull request #$3</a>" />
@@ -935,6 +938,7 @@
<fileset dir="${project.distrib.dir}/data">
<include name="users.conf" />
<include name="projects.conf" />
+ <include name="defaults.properties" />
<include name="gitblit.properties" />
</fileset>
</copy>
@@ -1052,4 +1056,21 @@
<mx:install />
</target>
+
+ <!--
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ Build Gitblit UI via npm
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ -->
+ <target name="buildUI" description="Build Gitblit UI via npm">
+ <exec executable="npm" dir="src/main/js/" failonerror="true" vmlauncher="false" searchpath="true" >
+ <arg value="install" />
+ </exec>
+
+ <exec executable="npm" dir="src/main/js/" failonerror="true" vmlauncher="false" searchpath="true" >
+ <arg value="run" />
+ <arg value="build" />
+ </exec>
+ </target>
+
</project>
diff --git a/gitblit.iml b/gitblit.iml
index d6f84df..93331b2 100644
--- a/gitblit.iml
+++ b/gitblit.iml
@@ -7,20 +7,20 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/bugtraq" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/main/dagger" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/main/gen" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/bugtraq" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library">
- <library name="dagger-1.1.0.jar">
+ <library name="guice-4.0.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/dagger-1.1.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/guice-4.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/dagger-1.1.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/guice-4.0.jar!/" />
</SOURCES>
</library>
</orderEntry>
@@ -36,24 +36,35 @@
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="dagger-compiler-1.1.0.jar">
+ <library name="aopalliance-1.0.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/dagger-compiler-1.1.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/aopalliance-1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/dagger-compiler-1.1.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/aopalliance-1.0.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="javawriter-2.1.1.jar">
+ <library name="guava-18.0.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/javawriter-2.1.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/guava-18.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/javawriter-2.1.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/guava-18.0.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library">
+ <library name="guice-servlet-4.0-gb2.jar">
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/ext/guice-servlet-4.0-gb2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/ext/src/guice-servlet-4.0-gb2.jar!/" />
</SOURCES>
</library>
</orderEntry>
@@ -80,24 +91,24 @@
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="slf4j-api-1.7.5.jar">
+ <library name="slf4j-api-1.7.12.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/slf4j-api-1.7.5.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/slf4j-api-1.7.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/slf4j-api-1.7.5.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/slf4j-api-1.7.12.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="slf4j-log4j12-1.7.5.jar">
+ <library name="slf4j-log4j12-1.7.12.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/slf4j-log4j12-1.7.5.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/slf4j-log4j12-1.7.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/slf4j-log4j12-1.7.5.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/slf4j-log4j12-1.7.12.jar!/" />
</SOURCES>
</library>
</orderEntry>
@@ -124,123 +135,123 @@
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="jetty-all-9.2.3.v20140905.jar">
+ <library name="jetty-all-9.2.13.v20150730.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/jetty-all-9.2.3.v20140905.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/jetty-all-9.2.13.v20150730.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/jetty-all-9.2.3.v20140905.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/jetty-all-9.2.13.v20150730.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="wicket-1.4.21.jar">
+ <library name="wicket-1.4.22.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/wicket-1.4.21.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/wicket-1.4.22.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/wicket-1.4.21.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/wicket-1.4.22.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="wicket-auth-roles-1.4.21.jar">
+ <library name="wicket-auth-roles-1.4.22.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/wicket-auth-roles-1.4.21.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/wicket-auth-roles-1.4.22.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/wicket-auth-roles-1.4.21.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/wicket-auth-roles-1.4.22.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="wicket-extensions-1.4.21.jar">
+ <library name="wicket-extensions-1.4.22.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/wicket-extensions-1.4.21.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/wicket-extensions-1.4.22.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/wicket-extensions-1.4.21.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/wicket-extensions-1.4.22.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="lucene-core-4.6.0.jar">
+ <library name="lucene-core-4.10.4.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/lucene-core-4.6.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/lucene-core-4.10.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/lucene-core-4.6.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/lucene-core-4.10.4.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="lucene-analyzers-common-4.6.0.jar">
+ <library name="lucene-analyzers-common-4.10.4.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/lucene-analyzers-common-4.6.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/lucene-analyzers-common-4.10.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/lucene-analyzers-common-4.6.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/lucene-analyzers-common-4.10.4.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="lucene-highlighter-4.6.0.jar">
+ <library name="lucene-highlighter-4.10.4.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/lucene-highlighter-4.6.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/lucene-highlighter-4.10.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/lucene-highlighter-4.6.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/lucene-highlighter-4.10.4.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="lucene-memory-4.6.0.jar">
+ <library name="lucene-memory-4.10.4.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/lucene-memory-4.6.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/lucene-memory-4.10.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/lucene-memory-4.6.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/lucene-memory-4.10.4.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="lucene-queries-4.6.0.jar">
+ <library name="lucene-queries-4.10.4.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/lucene-queries-4.6.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/lucene-queries-4.10.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/lucene-queries-4.6.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/lucene-queries-4.10.4.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="lucene-queryparser-4.6.0.jar">
+ <library name="lucene-queryparser-4.10.4.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/lucene-queryparser-4.6.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/lucene-queryparser-4.10.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/lucene-queryparser-4.6.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/lucene-queryparser-4.10.4.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="lucene-sandbox-4.6.0.jar">
+ <library name="lucene-sandbox-4.10.4.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/lucene-sandbox-4.6.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/lucene-sandbox-4.10.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/lucene-sandbox-4.6.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/lucene-sandbox-4.10.4.jar!/" />
</SOURCES>
</library>
</orderEntry>
@@ -254,79 +265,79 @@
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="pegdown-1.4.2.jar">
+ <library name="pegdown-1.5.0.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/pegdown-1.4.2.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/pegdown-1.5.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/pegdown-1.4.2.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/pegdown-1.5.0.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="parboiled-java-1.1.6.jar">
+ <library name="parboiled-java-1.1.7.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/parboiled-java-1.1.6.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/parboiled-java-1.1.7.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/parboiled-java-1.1.6.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/parboiled-java-1.1.7.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="parboiled-core-1.1.6.jar">
+ <library name="parboiled-core-1.1.7.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/parboiled-core-1.1.6.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/parboiled-core-1.1.7.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/parboiled-core-1.1.6.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/parboiled-core-1.1.7.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="asm-4.1.jar">
+ <library name="asm-5.0.3.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/asm-4.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/asm-5.0.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/asm-4.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/asm-5.0.3.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="asm-tree-4.1.jar">
+ <library name="asm-tree-5.0.3.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/asm-tree-4.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/asm-tree-5.0.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/asm-tree-4.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/asm-tree-5.0.3.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="asm-analysis-4.1.jar">
+ <library name="asm-analysis-5.0.3.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/asm-analysis-4.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/asm-analysis-5.0.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/asm-analysis-4.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/asm-analysis-5.0.3.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="asm-util-4.1.jar">
+ <library name="asm-util-5.0.3.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/asm-util-4.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/asm-util-5.0.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/asm-util-4.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/asm-util-5.0.3.jar!/" />
</SOURCES>
</library>
</orderEntry>
@@ -397,24 +408,24 @@
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="org.eclipse.jgit-3.5.1.201410131835-r.jar">
+ <library name="org.eclipse.jgit-4.1.1.201511131810-r.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit-3.5.1.201410131835-r.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit-4.1.1.201511131810-r.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit-3.5.1.201410131835-r.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit-4.1.1.201511131810-r.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="jsch-0.1.50.jar">
+ <library name="jsch-0.1.53.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/jsch-0.1.50.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/jsch-0.1.53.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/jsch-0.1.50.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/jsch-0.1.53.jar!/" />
</SOURCES>
</library>
</orderEntry>
@@ -430,35 +441,35 @@
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="httpclient-4.1.3.jar">
+ <library name="httpclient-4.3.6.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/httpclient-4.1.3.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/httpclient-4.3.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/httpclient-4.1.3.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/httpclient-4.3.6.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="httpcore-4.1.4.jar">
+ <library name="httpcore-4.3.3.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/httpcore-4.1.4.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/httpcore-4.3.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/httpcore-4.1.4.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/httpcore-4.3.3.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="commons-logging-1.1.1.jar">
+ <library name="commons-logging-1.1.3.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/commons-logging-1.1.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/commons-logging-1.1.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/commons-logging-1.1.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/commons-logging-1.1.3.jar!/" />
</SOURCES>
</library>
</orderEntry>
@@ -474,68 +485,77 @@
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar">
+ <library name="org.eclipse.jdt.annotation-1.1.0.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/org.eclipse.jdt.annotation-1.1.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library">
+ <library name="org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar">
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="bcprov-jdk15on-1.49.jar">
+ <library name="bcprov-jdk15on-1.52.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/bcprov-jdk15on-1.49.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/bcprov-jdk15on-1.52.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/bcprov-jdk15on-1.49.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/bcprov-jdk15on-1.52.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="bcmail-jdk15on-1.49.jar">
+ <library name="bcmail-jdk15on-1.52.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/bcmail-jdk15on-1.49.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/bcmail-jdk15on-1.52.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/bcmail-jdk15on-1.49.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/bcmail-jdk15on-1.52.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="bcpkix-jdk15on-1.49.jar">
+ <library name="bcpkix-jdk15on-1.52.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/bcpkix-jdk15on-1.49.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/bcpkix-jdk15on-1.52.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/bcpkix-jdk15on-1.49.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/bcpkix-jdk15on-1.52.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="sshd-core-0.12.0.jar">
+ <library name="sshd-core-1.0.0.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/sshd-core-0.12.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/sshd-core-1.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/sshd-core-0.12.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/sshd-core-1.0.0.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="mina-core-2.0.7.jar">
+ <library name="mina-core-2.0.9.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/mina-core-2.0.7.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/mina-core-2.0.9.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/mina-core-2.0.7.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/mina-core-2.0.9.jar!/" />
</SOURCES>
</library>
</orderEntry>
@@ -562,35 +582,35 @@
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="gson-1.7.2.jar">
+ <library name="gson-2.3.1.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/gson-1.7.2.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/gson-2.3.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/gson-1.7.2.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/gson-2.3.1.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="groovy-all-1.8.8.jar">
+ <library name="groovy-all-2.4.4.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/groovy-all-1.8.8.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/groovy-all-2.4.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/groovy-all-1.8.8.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/groovy-all-2.4.4.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="unboundid-ldapsdk-2.3.0.jar">
+ <library name="unboundid-ldapsdk-2.3.8.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/unboundid-ldapsdk-2.3.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/unboundid-ldapsdk-2.3.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/unboundid-ldapsdk-2.3.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/unboundid-ldapsdk-2.3.8.jar!/" />
</SOURCES>
</library>
</orderEntry>
@@ -681,90 +701,79 @@
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="freemarker-2.3.19.jar">
+ <library name="freemarker-2.3.22.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/freemarker-2.3.19.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/freemarker-2.3.22.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/freemarker-2.3.19.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/freemarker-2.3.22.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="waffle-jna-1.5.jar">
+ <library name="waffle-jna-1.7.3.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/waffle-jna-1.5.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/waffle-jna-1.7.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/waffle-jna-1.5.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/waffle-jna-1.7.3.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="platform-3.5.0.jar">
+ <library name="jna-4.1.0.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/platform-3.5.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/jna-4.1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/platform-3.5.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/jna-4.1.0.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="jna-3.5.0.jar">
+ <library name="jna-platform-4.1.0.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/jna-3.5.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/jna-platform-4.1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/jna-3.5.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/jna-platform-4.1.0.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="guava-13.0.1.jar">
+ <library name="libpam4j-1.8.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/guava-13.0.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/libpam4j-1.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/guava-13.0.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/libpam4j-1.8.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="libpam4j-1.7.jar">
+ <library name="args4j-2.0.29.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/libpam4j-1.7.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/args4j-2.0.29.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/libpam4j-1.7.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/args4j-2.0.29.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="args4j-2.0.26.jar">
+ <library name="jedis-2.6.2.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/args4j-2.0.26.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/jedis-2.6.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/args4j-2.0.26.jar!/" />
- </SOURCES>
- </library>
- </orderEntry>
- <orderEntry type="module-library">
- <library name="jedis-2.3.1.jar">
- <CLASSES>
- <root url="jar://$MODULE_DIR$/ext/jedis-2.3.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/jedis-2.3.1.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/jedis-2.6.2.jar!/" />
</SOURCES>
</library>
</orderEntry>
@@ -780,13 +789,13 @@
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="pf4j-0.8.0.jar">
+ <library name="pf4j-0.9.0.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/pf4j-0.8.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/pf4j-0.9.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/pf4j-0.8.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/pf4j-0.9.0.jar!/" />
</SOURCES>
</library>
</orderEntry>
@@ -922,6 +931,39 @@
</SOURCES>
</library>
</orderEntry>
+ <orderEntry type="module-library" scope="TEST">
+ <library name="platform-3.4.0.jar">
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/ext/platform-3.4.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/ext/src/platform-3.4.0.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" scope="TEST">
+ <library name="mockito-core-1.10.19.jar">
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/ext/mockito-core-1.10.19.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/ext/src/mockito-core-1.10.19.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" scope="TEST">
+ <library name="objenesis-2.1.jar">
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/ext/objenesis-2.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/ext/src/objenesis-2.1.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
<orderEntry type="inheritedJdk" />
</component>
</module>
diff --git a/release.template b/release.template
index 0fd61c5..1322519 100644
--- a/release.template
+++ b/release.template
@@ -60,23 +60,12 @@
# merge to master
echo ""
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
-echo "Merging release ${project.version} to master"
+echo "Updating build identifier for next release cycle"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo ""
git checkout master
-git merge --no-ff -m "Merge release ${project.version}" ${project.commitId}
ant nextPointReleaseCycle
-# merge to develop
-echo ""
-echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
-echo "Merging release ${project.version} to develop"
-echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
-echo ""
-git checkout develop
-git merge --no-ff -m "Merge release ${project.version}" ${project.commitId}
-ant nextMinorReleaseCycle
-
# push Maven repository to origin
echo ""
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
@@ -90,7 +79,7 @@
# push project branches
echo ""
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
-echo "Pushing master, develop, gh-pages, and tag ${project.tag}"
+echo "Pushing master, gh-pages, and tag ${project.tag}"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo ""
-git push origin master develop gh-pages ${project.tag}
+git push origin master gh-pages ${project.tag}
diff --git a/releases.moxie b/releases.moxie
index 2241384..f18834b 100644
--- a/releases.moxie
+++ b/releases.moxie
@@ -1,7 +1,7 @@
#
# ${project.version} release
#
-r27: {
+r29: {
title: ${project.name} ${project.version} released
id: ${project.version}
date: ${project.buildDate}
@@ -9,11 +9,328 @@
html: ~
text: ~
security: ~
- fixes: ~
- changes: ~
- additions: ~
- dependencyChanges: ~
- contributors: ~
+ fixes:
+ - Fix exception when viewing a ticket with a patchset where the integration branch does not exist (issue-521, ticket-212)
+ - Fix exception when deleting a repository using the FileTicketService (issue-522, ticket-213)
+ - Do not inject team repository permissions as explicit user permissions when editing a user (issue-462, ticket-214)
+ - Whitelist the target link attribute in the XSS filter (ticket-216)
+ - Strip line breaks from pasted SSH keys (ticket-245)
+ - Fix project sorting (pr-287)
+ - Fix Lucene indexing of tags (pr-291)
+ - Prevent session fixation for external authentication (pr-908)
+ - Encode email subject as UTF-8 (pr-929)
+ - Do not automatically trim passwords (pr-932)
+ - Fix nested repository detection in raw servlet (pr-950, pr-957)
+ - Raw servlet will now assume text/plain for dot files (pr-956)
+ changes:
+ - Replaced Dagger with Guice (ticket-80)
+ - Use release name as root directory in Gitblit GO artifacts (ticket-109)
+ - Split gitblit.properties into gitblit.properties & defaults.properties (ticket-110)
+ - Show team type in teams page (pr-217, ticket-168)
+ - Relocate the repository Delete button (ticket-225)
+ - Improve diff performance by gracefully limiting large diffs (pr-226)
+ - Add granular settings to disable display of git transport urls (pr-274)
+ - Use author date to be consistent with other tools (pr-919)
+ additions:
+ - Add GitHub Octicons (ticket-106)
+ - Support for chain-loading properties files (ticket-110)
+ - Add Priority & Severity fields for tickets (pr-220, ticket-157)
+ - Add Maintenance ticket type (pr-223, ticket-206)
+ - Add commitdiff option to ignore whitespace (ticket-233)
+ - Add configurable tab length for blob views (ticket-253)
+ - Implement image diffs (pr-229)
+ - Add support for configurable HTTP proxy host/port in PluginManager (pr-235)
+ - Implement collapsed empty folder navigation (pr-241)
+ - Implement hashing to detect usermodel changes and reduce users.conf file I/O (pr-246)
+ - Add support for Kerberos5/GSS authentication to SSH (pr-254)
+ - Allow extraction of additional user metadata in request headers when using external or container authentication (pr-255)
+ - Allow custom host & port specification for advertised SSH urls (pr-268)
+ - Improve logging for fail2ban usage (pr-296)
+ - Initial implementation of Git-LFS (pr-921)
+ - Add "all" repositories parameter to Search page (pr-935)
+ dependencyChanges:
+ - Guice 4.0 (ticket-80, ticket-219)
+ - SLF4j 1.7.12
+ - gson 2.3.1
+ - Freemarker 2.3.22
+ - Lucene 4.10.0 (ticket-159)
+ - SSHD 1.0.0
+ - JGit 4.1.1
+ - Groovy 2.4.4
+ - Wicket 1.4.22
+ - BouncyCastle 1.52
+ - Pegdown 1.5.0
+ - Jetty 9.2.13
+ settings:
+ - { name: web.displayUserPanel, defaultValue: 'true' }
+ - { name: web.tabLength, defaultValue: 4 }
+ - { name: web.avatarClass, defaultValue: '' }
+ - { name: web.showHttpServletUrls, defaultValue: 'true' }
+ - { name: web.showGitDaemonUrls, defaultValue: 'true' }
+ - { name: web.showSshDaemonUrls, defaultValue: 'true' }
+ - { name: web.advertiseAccessPermissionForOtherUrls, defaultValue: 'false' }
+ - { name: web.maxDiffLinesPerFile, defaultValue: '4000' }
+ - { name: web.maxDiffLines, defaultValue: '20000' }
+ - { name: ssh.advertisedHost, defaultValue: '' }
+ - { name: ssh.advertisedPort, defaultValue: '' }
+ - { name: git.sshWithKrb5, defaultValue: '' }
+ - { name: git.sshKrb5Keytab, defaultValue: '' }
+ - { name: git.sshKrb5ServicePrincipalName, defaultValue: '' }
+ - { name: git.sshKrb5StripDomain, defaultValue: 'true' }
+ - { name: filestore.storageFolder, defaultValue: '${baseFolder}/lfs' }
+ - { name: filestore.maxUploadSize, defaultValue: '-1' }
+ - { name: plugins.httpProxyHost, defaultValue: '' }
+ - { name: plugins.httpProxyPort, defaultValue: '' }
+ - { name: plugins.httpProxyAuthorization, defaultValue: '' }
+ - { name: realm.container.autoAccounts.displayName, defaultValue: '' }
+ - { name: realm.container.autoAccounts.emailAddress, defaultValue: '' }
+ - { name: realm.container.autoAccounts.locale, defaultValue: '' }
+ - { name: realm.container.autoAccounts.adminRole, defaultValue: '' }
+
+ contributors:
+ - James Moger
+ - David Ostrovsky
+ - Alex Lewis
+ - Florian Zschocke
+ - Paul Martin
+ - razzard
+ - Alexander Zabluda
+ - Marcin Cieślak
+ - Rainer W
+ - Vitaliy Filippov
+ - willyann
+ - enrico204
+ - mrjoel
+ - Fabrice Bacchella
+ - Milos Cubrilo
+ - Thomas Wolf
+ - Morten Bøgeskov
+ - Steven Oliver
+ - Dariusz Bywalec
+ - Jan Šmucr
+ -paladox
+}
+
+#
+# 1.7.1 release
+#
+r28: {
+ title: Gitblit 1.7.1 released
+ id: 1.7.1
+ date: 2015-11-23
+ note: This is a re-build of 1.7.0 with a fix for failed WAR deployments.
+ html: ~
+ text: ~
+ security: ~
+ fixes:
+ - Fix exception when viewing a ticket with a patchset where the integration branch does not exist (issue-521, ticket-212)
+ - Fix exception when deleting a repository using the FileTicketService (issue-522, ticket-213)
+ - Do not inject team repository permissions as explicit user permissions when editing a user (issue-462, ticket-214)
+ - Whitelist the target link attribute in the XSS filter (ticket-216)
+ - Strip line breaks from pasted SSH keys (ticket-245)
+ - Fix project sorting (pr-287)
+ - Fix Lucene indexing of tags (pr-291)
+ - Prevent session fixation for external authentication (pr-908)
+ - Encode email subject as UTF-8 (pr-929)
+ - Do not automatically trim passwords (pr-932)
+ - Fix nested repository detection in raw servlet (pr-950)
+ changes:
+ - Replaced Dagger with Guice (ticket-80)
+ - Use release name as root directory in Gitblit GO artifacts (ticket-109)
+ - Split gitblit.properties into gitblit.properties & defaults.properties (ticket-110)
+ - Show team type in teams page (pr-217, ticket-168)
+ - Relocate the repository Delete button (ticket-225)
+ - Improve diff performance by gracefully limiting large diffs (pr-226)
+ - Add granular settings to disable display of git transport urls (pr-274)
+ - Use author date to be consistent with other tools (pr-919)
+ additions:
+ - Add GitHub Octicons (ticket-106)
+ - Support for chain-loading properties files (ticket-110)
+ - Add Priority & Severity fields for tickets (pr-220, ticket-157)
+ - Add Maintenance ticket type (pr-223, ticket-206)
+ - Add commitdiff option to ignore whitespace (ticket-233)
+ - Add configurable tab length for blob views (ticket-253)
+ - Implement image diffs (pr-229)
+ - Add support for configurable HTTP proxy host/port in PluginManager (pr-235)
+ - Implement collapsed empty folder navigation (pr-241)
+ - Implement hashing to detect usermodel changes and reduce users.conf file I/O (pr-246)
+ - Add support for Kerberos5/GSS authentication to SSH (pr-254)
+ - Allow extraction of additional user metadata in request headers when using external or container authentication (pr-255)
+ - Allow custom host & port specification for advertised SSH urls (pr-268)
+ - Improve logging for fail2ban usage (pr-296)
+ - Initial implementation of Git-LFS (pr-921)
+ - Add "all" repositories parameter to Search page (pr-935)
+ dependencyChanges:
+ - Guice 4.0 (ticket-80, ticket-219)
+ - SLF4j 1.7.12
+ - gson 2.3.1
+ - Freemarker 2.3.22
+ - Lucene 4.10.0 (ticket-159)
+ - SSHD 1.0.0
+ - JGit 4.1.1
+ - Groovy 2.4.4
+ - Wicket 1.4.22
+ - BouncyCastle 1.52
+ - Pegdown 1.5.0
+ - Jetty 9.2.13
+ settings:
+ - { name: web.displayUserPanel, defaultValue: 'true' }
+ - { name: web.tabLength, defaultValue: 4 }
+ - { name: web.avatarClass, defaultValue: '' }
+ - { name: web.showHttpServletUrls, defaultValue: 'true' }
+ - { name: web.showGitDaemonUrls, defaultValue: 'true' }
+ - { name: web.showSshDaemonUrls, defaultValue: 'true' }
+ - { name: web.advertiseAccessPermissionForOtherUrls, defaultValue: 'false' }
+ - { name: web.maxDiffLinesPerFile, defaultValue: '4000' }
+ - { name: web.maxDiffLines, defaultValue: '20000' }
+ - { name: ssh.advertisedHost, defaultValue: '' }
+ - { name: ssh.advertisedPort, defaultValue: '' }
+ - { name: git.sshWithKrb5, defaultValue: '' }
+ - { name: git.sshKrb5Keytab, defaultValue: '' }
+ - { name: git.sshKrb5ServicePrincipalName, defaultValue: '' }
+ - { name: git.sshKrb5StripDomain, defaultValue: 'true' }
+ - { name: filestore.storageFolder, defaultValue: '${baseFolder}/lfs' }
+ - { name: filestore.maxUploadSize, defaultValue: '-1' }
+ - { name: plugins.httpProxyHost, defaultValue: '' }
+ - { name: plugins.httpProxyPort, defaultValue: '' }
+ - { name: plugins.httpProxyAuthorization, defaultValue: '' }
+ - { name: realm.container.autoAccounts.displayName, defaultValue: '' }
+ - { name: realm.container.autoAccounts.emailAddress, defaultValue: '' }
+ - { name: realm.container.autoAccounts.locale, defaultValue: '' }
+ - { name: realm.container.autoAccounts.adminRole, defaultValue: '' }
+
+ contributors:
+ - James Moger
+ - David Ostrovsky
+ - Alex Lewis
+ - Florian Zschocke
+ - Paul Martin
+ - razzard
+ - Alexander Zabluda
+ - Marcin Cieślak
+ - Rainer W
+ - Vitaliy Filippov
+ - willyann
+ - enrico204
+ - mrjoel
+ - Fabrice Bacchella
+ - Milos Cubrilo
+ - Thomas Wolf
+ - Morten Bøgeskov
+ - Steven Oliver
+ - Dariusz Bywalec
+ - Jan Šmucr
+}
+
+#
+# 1.7.0 release
+#
+r27: {
+ title: Gitblit 1.7.0 released
+ id: 1.7.0
+ date: 2015-11-22
+ note: ~
+ html: ~
+ text: ~
+ security: ~
+ fixes:
+ - Fix exception when viewing a ticket with a patchset where the integration branch does not exist (issue-521, ticket-212)
+ - Fix exception when deleting a repository using the FileTicketService (issue-522, ticket-213)
+ - Do not inject team repository permissions as explicit user permissions when editing a user (issue-462, ticket-214)
+ - Whitelist the target link attribute in the XSS filter (ticket-216)
+ - Strip line breaks from pasted SSH keys (ticket-245)
+ - Fix project sorting (pr-287)
+ - Fix Lucene indexing of tags (pr-291)
+ - Prevent session fixation for external authentication (pr-908)
+ - Encode email subject as UTF-8 (pr-929)
+ - Do not automatically trim passwords (pr-932)
+ - Fix nested repository detection in raw servlet (pr-950)
+ changes:
+ - Replaced Dagger with Guice (ticket-80)
+ - Use release name as root directory in Gitblit GO artifacts (ticket-109)
+ - Split gitblit.properties into gitblit.properties & defaults.properties (ticket-110)
+ - Show team type in teams page (pr-217, ticket-168)
+ - Relocate the repository Delete button (ticket-225)
+ - Improve diff performance by gracefully limiting large diffs (pr-226)
+ - Add granular settings to disable display of git transport urls (pr-274)
+ - Use author date to be consistent with other tools (pr-919)
+ additions:
+ - Add GitHub Octicons (ticket-106)
+ - Support for chain-loading properties files (ticket-110)
+ - Add Priority & Severity fields for tickets (pr-220, ticket-157)
+ - Add Maintenance ticket type (pr-223, ticket-206)
+ - Add commitdiff option to ignore whitespace (ticket-233)
+ - Add configurable tab length for blob views (ticket-253)
+ - Implement image diffs (pr-229)
+ - Add support for configurable HTTP proxy host/port in PluginManager (pr-235)
+ - Implement collapsed empty folder navigation (pr-241)
+ - Implement hashing to detect usermodel changes and reduce users.conf file I/O (pr-246)
+ - Add support for Kerberos5/GSS authentication to SSH (pr-254)
+ - Allow extraction of additional user metadata in request headers when using external or container authentication (pr-255)
+ - Allow custom host & port specification for advertised SSH urls (pr-268)
+ - Improve logging for fail2ban usage (pr-296)
+ - Initial implementation of Git-LFS (pr-921)
+ - Add "all" repositories parameter to Search page (pr-935)
+ dependencyChanges:
+ - Guice 4.0 (ticket-80, ticket-219)
+ - SLF4j 1.7.12
+ - gson 2.3.1
+ - Freemarker 2.3.22
+ - Lucene 4.10.0 (ticket-159)
+ - SSHD 1.0.0
+ - JGit 4.1.1
+ - Groovy 2.4.4
+ - Wicket 1.4.22
+ - BouncyCastle 1.52
+ - Pegdown 1.5.0
+ - Jetty 9.2.13
+ settings:
+ - { name: web.displayUserPanel, defaultValue: 'true' }
+ - { name: web.tabLength, defaultValue: 4 }
+ - { name: web.avatarClass, defaultValue: '' }
+ - { name: web.showHttpServletUrls, defaultValue: 'true' }
+ - { name: web.showGitDaemonUrls, defaultValue: 'true' }
+ - { name: web.showSshDaemonUrls, defaultValue: 'true' }
+ - { name: web.advertiseAccessPermissionForOtherUrls, defaultValue: 'false' }
+ - { name: web.maxDiffLinesPerFile, defaultValue: '4000' }
+ - { name: web.maxDiffLines, defaultValue: '20000' }
+ - { name: ssh.advertisedHost, defaultValue: '' }
+ - { name: ssh.advertisedPort, defaultValue: '' }
+ - { name: git.sshWithKrb5, defaultValue: '' }
+ - { name: git.sshKrb5Keytab, defaultValue: '' }
+ - { name: git.sshKrb5ServicePrincipalName, defaultValue: '' }
+ - { name: git.sshKrb5StripDomain, defaultValue: 'true' }
+ - { name: filestore.storageFolder, defaultValue: '${baseFolder}/lfs' }
+ - { name: filestore.maxUploadSize, defaultValue: '-1' }
+ - { name: plugins.httpProxyHost, defaultValue: '' }
+ - { name: plugins.httpProxyPort, defaultValue: '' }
+ - { name: plugins.httpProxyAuthorization, defaultValue: '' }
+ - { name: realm.container.autoAccounts.displayName, defaultValue: '' }
+ - { name: realm.container.autoAccounts.emailAddress, defaultValue: '' }
+ - { name: realm.container.autoAccounts.locale, defaultValue: '' }
+ - { name: realm.container.autoAccounts.adminRole, defaultValue: '' }
+
+ contributors:
+ - James Moger
+ - David Ostrovsky
+ - Alex Lewis
+ - Florian Zschocke
+ - Paul Martin
+ - razzard
+ - Alexander Zabluda
+ - Marcin Cieślak
+ - Rainer W
+ - Vitaliy Filippov
+ - willyann
+ - enrico204
+ - mrjoel
+ - Fabrice Bacchella
+ - Milos Cubrilo
+ - Thomas Wolf
+ - Morten Bøgeskov
+ - Steven Oliver
+ - Dariusz Bywalec
+ - Jan Šmucr
}
#
@@ -1574,6 +1891,6 @@
- James Moger
}
-snapshot: &r27
-release: &r26
-releases: &r[1..26]
+snapshot: &r29
+release: &r28
+releases: &r[1..28]
diff --git a/src/main/.gitignore b/src/main/.gitignore
index 01c48ab..0d04688 100644
--- a/src/main/.gitignore
+++ b/src/main/.gitignore
@@ -1 +1,2 @@
/dagger
+/gen
diff --git a/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java b/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java
index 7776e6f..60b4ecc 100644
--- a/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java
+++ b/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java
@@ -214,7 +214,7 @@
}
finally {
rw.dispose();
- tw.release();
+ tw.close();
}
if (content == null) {
diff --git a/src/main/distrib/data/defaults.properties b/src/main/distrib/data/defaults.properties
new file mode 100644
index 0000000..403b741
--- /dev/null
+++ b/src/main/distrib/data/defaults.properties
@@ -0,0 +1,2083 @@
+#
+# DEFAULTS.PROPERTIES
+#
+# The default Gitblit settings.
+#
+
+# This settings file supports parameterization from the command-line for the
+# following command-line parameters:
+#
+# --baseFolder ${baseFolder} SINCE 1.2.1
+#
+# Settings that support ${baseFolder} parameter substitution are indicated with the
+# BASEFOLDER attribute. If the --baseFolder argument is unspecified, ${baseFolder}
+# and it's trailing / will be discarded from the setting value leaving a relative
+# path that is equivalent to pre-1.2.1 releases.
+#
+# e.g. "${baseFolder}/git" becomes "git", if --baseFolder is unspecified
+#
+# Git Servlet Settings
+#
+
+# Base folder for repositories.
+# This folder may contain bare and non-bare repositories but Gitblit will only
+# allow you to push to bare repositories.
+# Use forward slashes even on Windows!!
+# e.g. c:/gitrepos
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+# BASEFOLDER
+git.repositoriesFolder = ${baseFolder}/git
+
+# Build the available repository list at startup and cache this list for reuse.
+# This reduces disk io when presenting the repositories page, responding to rpcs,
+# etc, but it means that Gitblit will not automatically identify repositories
+# added or deleted by external tools.
+#
+# For this case you can use curl, wget, etc to issue an rpc request to clear the
+# cache (e.g. https://localhost/rpc?req=CLEAR_REPOSITORY_CACHE)
+#
+# SINCE 1.1.0
+git.cacheRepositoryList = true
+
+# Search the repositories folder subfolders for other repositories.
+# Repositories MAY NOT be nested (i.e. one repository within another)
+# but they may be grouped together in subfolders.
+# e.g. c:/gitrepos/libraries/mylibrary.git
+# c:/gitrepos/libraries/myotherlibrary.git
+#
+# SINCE 0.5.0
+git.searchRepositoriesSubfolders = true
+
+# Maximum number of folders to recurse into when searching for repositories.
+# The default value, -1, disables depth limits.
+#
+# SINCE 1.1.0
+git.searchRecursionDepth = -1
+
+# List of regex exclusion patterns to match against folders found in
+# *git.repositoriesFolder*.
+# Use forward slashes even on Windows!!
+# e.g. test/jgit\.git
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.1.0
+git.searchExclusions =
+
+# List of regex url patterns for extracting a repository name when locating
+# submodules.
+# e.g. git.submoduleUrlPatterns = .*?://github.com/(.*) will extract
+# *gitblit/gitblit.git* from *git://github.com/gitblit/gitblit.git*
+# If no matches are found then the submodule repository name is assumed to be
+# whatever trails the last / character. (e.g. gitblit.git).
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.1.0
+git.submoduleUrlPatterns = .*?://github.com/(.*)
+
+# Specify the interface for Git Daemon to bind it's service.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 1.3.0
+# RESTART REQUIRED
+git.daemonBindInterface =
+
+# port for serving the Git Daemon service. <= 0 disables this service.
+# On Unix/Linux systems, ports < 1024 require root permissions.
+# Recommended value: 9418
+#
+# SINCE 1.3.0
+# RESTART REQUIRED
+git.daemonPort = 9418
+
+# The port for serving the SSH service. <= 0 disables this service.
+# On Unix/Linux systems, ports < 1024 require root permissions.
+# Recommended value: 29418
+#
+# SINCE 1.5.0
+# RESTART REQUIRED
+git.sshPort = 29418
+
+# Specify the interface for the SSH daemon to bind its service.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 1.5.0
+# RESTART REQUIRED
+git.sshBindInterface =
+
+# Manually specify the hostname to use in advertised SSH repository urls.
+# This may be useful in complex forwarding setups.
+#
+# SINCE 1.7.0
+git.sshAdvertisedHost =
+
+# Manually specify the port to use in advertised SSH repository urls.
+# This may be useful in complex forwarding setups.
+#
+# SINCE 1.7.0
+git.sshAdvertisedPort =
+
+# Specify the SSH key manager to use for retrieving, storing, and removing
+# SSH keys.
+#
+# Valid key managers are:
+# com.gitblit.transport.ssh.FileKeyManager
+#
+# SINCE 1.5.0
+git.sshKeysManager = com.gitblit.transport.ssh.FileKeyManager
+
+# Directory for storing user SSH keys when using the FileKeyManager.
+#
+# SINCE 1.5.0
+git.sshKeysFolder= ${baseFolder}/ssh
+
+# Use Kerberos5 (GSS) authentication
+#
+# SINCE 1.7.0
+git.sshWithKrb5 = false
+
+# The path to a Kerberos 5 keytab.
+#
+# SINCE 1.7.0
+git.sshKrb5Keytab =
+
+# The service principal name to be used for Kerberos5.
+# The default is host/hostname.
+#
+# SINCE 1.7.0
+git.sshKrb5ServicePrincipalName =
+
+# Strip the domain suffix from a kerberos username.
+# e.g. james@bigbox would be "james"
+#
+# SINCE 1.7.0
+git.sshKrb5StripDomain = true
+
+# SSH backend NIO2|MINA.
+#
+# The Apache Mina project recommends using the NIO2 backend.
+#
+# SINCE 1.5.0
+git.sshBackend = NIO2
+
+# Number of threads used to parse a command line submitted by a client over SSH
+# for execution, create the internal data structures used by that command,
+# and schedule it for execution on another thread.
+#
+# SINCE 1.5.0
+git.sshCommandStartThreads = 2
+
+
+# Allow push/pull over http/https with JGit servlet.
+# If you do NOT want to allow Git clients to clone/push to Gitblit set this
+# to false. You might want to do this if you are only using ssh:// or git://.
+# If you set this false, consider changing the *web.otherUrls* setting to
+# indicate your clone/push urls.
+#
+# SINCE 0.5.0
+git.enableGitServlet = true
+
+# If you want to restrict all git servlet access to those with valid X509 client
+# certificates then set this value to true.
+#
+# SINCE 1.2.0
+git.requiresClientCertificate = false
+
+# Enforce date checks on client certificates to ensure that they are not being
+# used prematurely and that they have not expired.
+#
+# SINCE 1.2.0
+git.enforceCertificateValidity = true
+
+# List of OIDs to extract from a client certificate DN to map a certificate to
+# an account username.
+#
+# e.g. git.certificateUsernameOIDs = CN
+# e.g. git.certificateUsernameOIDs = FirstName LastName
+#
+# SPACE-DELIMITED
+# SINCE 1.2.0
+git.certificateUsernameOIDs = CN
+
+# Only serve/display bare repositories.
+# If there are non-bare repositories in git.repositoriesFolder and this setting
+# is true, they will be excluded from the ui.
+#
+# SINCE 0.9.0
+git.onlyAccessBareRepositories = false
+
+
+# Specify the list of acceptable transports for pushes.
+# If this setting is empty, all transports are acceptable.
+#
+# Valid choices are: GIT HTTP HTTPS SSH
+#
+# SINCE 1.5.0
+# SPACE-DELIMITED
+git.acceptedPushTransports = HTTP HTTPS SSH
+
+# Allow an authenticated user to create a destination repository on a push if
+# the repository does not already exist.
+#
+# Administrator accounts can create a repository in any project.
+# These repositories are created with the default access restriction and authorization
+# control values. The pushing account is set as the owner.
+#
+# Non-administrator accounts with the CREATE role may create personal repositories.
+# These repositories are created as VIEW restricted for NAMED users.
+# The pushing account is set as the owner.
+#
+# SINCE 1.2.0
+git.allowCreateOnPush = true
+
+# Global setting to control anonymous pushes.
+#
+# This setting allows/rejects anonymous pushes at the level of the receive pack.
+# This trumps all repository config settings. While anonymous pushes are convenient
+# on your own box when you are a lone developer, they are not recommended for
+# any multi-user installation where accountability is required. Since Gitblit
+# tracks pushes and user accounts, allowing anonymous pushes compromises that
+# information.
+#
+# SINCE 1.4.0
+git.allowAnonymousPushes = false
+
+# The default access restriction for new repositories.
+# Valid values are NONE, PUSH, CLONE, VIEW
+# NONE = anonymous view, clone, & push
+# PUSH = anonymous view & clone and authenticated push
+# CLONE = anonymous view, authenticated clone & push
+# VIEW = authenticated view, clone, & push
+#
+# SINCE 1.0.0
+git.defaultAccessRestriction = PUSH
+
+# The default authorization control for new repositories.
+# Valid values are AUTHENTICATED and NAMED
+# AUTHENTICATED = any authenticated user is granted restricted access
+# NAMED = only named users/teams are granted restricted access
+#
+# SINCE 1.1.0
+git.defaultAuthorizationControl = NAMED
+
+# The prefix for a users personal repository directory.
+#
+# Personal user repositories are created in this directory, named by the user name
+# prefixed with the userRepositoryPrefix. For eaxmple, a user 'john' would have his
+# personal repositories in the directory '~john'.
+#
+# Cannot be an empty string. Also, absolute paths are changed to relative paths by
+# removing the first directory separator.
+#
+# It is not recommended to change this value AFTER your user's have created
+# personal repositories because it will break all permissions, ownership, and
+# repository push/pull operations.
+#
+# RESTART REQUIRED
+# SINCE 1.4.0
+git.userRepositoryPrefix = ~
+
+# The default incremental push tag prefix. Tag prefix applied to a repository
+# that has automatic push tags enabled and does not specify a custom tag prefix.
+#
+# If incremental push tags are enabled, the tips of each branch in the push will
+# be tagged with an increasing revision integer.
+#
+# e.g. refs/tags/r2345 or refs/tags/rev_2345
+#
+# SINCE 1.3.0
+git.defaultIncrementalPushTagPrefix = r
+
+# Controls creating a repository as --shared on Unix servers.
+#
+# In an Unix environment where mixed access methods exist for shared repositories,
+# the repository should be created with 'git init --shared' to make sure that
+# it can be accessed e.g. via ssh (user git) and http (user www-data).
+#
+# Valid values are the values available for the '--shared' option. The the manual
+# page for 'git init' for more information on shared repositories.
+#
+# SINCE 1.4.0
+git.createRepositoriesShared = false
+
+# Directory for gitignore templates used during repository creation.
+#
+# SINCE 1.6.0
+git.gitignoreFolder = ${baseFolder}/gitignore
+
+# Enable JGit-based garbage collection. (!!EXPERIMENTAL!!)
+#
+# USE AT YOUR OWN RISK!
+#
+# If enabled, the garbage collection executor scans all repositories once a day
+# at the hour of your choosing. The GC executor will take each repository "offline",
+# one-at-a-time, to check if the repository satisfies it's GC trigger requirements.
+#
+# While the repository is offline it will be inaccessible from the web UI or from
+# any of the other services (git, rpc, rss, etc).
+#
+# Gitblit's GC Executor MAY NOT PLAY NICE with the other Git kids on the block,
+# especially on Windows systems, so if you are using other tools please coordinate
+# their usage with your GC Executor schedule or do not use this feature.
+#
+# The GC algorithm complex and the JGit team advises caution when using their
+# young implementation of GC.
+#
+# http://wiki.eclipse.org/EGit/New_and_Noteworthy/2.1#Garbage_Collector_and_Repository_Storage_Statistics
+#
+# EXPERIMENTAL
+# SINCE 1.2.0
+# RESTART REQUIRED
+git.enableGarbageCollection = false
+
+# Hour of the day for the GC Executor to scan repositories.
+# This value is in 24-hour time.
+#
+# SINCE 1.2.0
+git.garbageCollectionHour = 0
+
+# The default minimum total filesize of loose objects to trigger early garbage
+# collection.
+#
+# You may specify a custom threshold for a repository in the repository's settings.
+# Common unit suffixes of k, m, or g are supported.
+#
+# SINCE 1.2.0
+git.defaultGarbageCollectionThreshold = 500k
+
+# The default period, in days, between GCs for a repository. If the total filesize
+# of the loose object exceeds *git.garbageCollectionThreshold* or the repository's
+# custom threshold, this period will be short-circuited.
+#
+# e.g. if a repository collects 100KB of loose objects every day with a 500KB
+# threshold and a period of 7 days, it will take 5 days for the loose objects to
+# be collected, packed, and pruned.
+#
+# OR
+#
+# if a repository collects 10KB of loose objects every day with a 500KB threshold
+# and a period of 7 days, it will take the full 7 days for the loose objects to be
+# collected, packed, and pruned.
+#
+# You may specify a custom period for a repository in the repository's settings.
+#
+# The minimum value is 1 day since the GC Executor only runs once a day.
+#
+# SINCE 1.2.0
+git.defaultGarbageCollectionPeriod = 7
+
+# Gitblit can automatically fetch ref updates for a properly configured mirror
+# repository.
+#
+# Requirements:
+# 1. you must manually clone the repository using native git
+# git clone --mirror git://somewhere.com/myrepo.git
+# 2. the "origin" remote must be the mirror source
+# 3. the "origin" repository must be accessible without authentication OR the
+# credentials must be embedded in the origin url (not recommended)
+#
+# Notes:
+# 1. "origin" SSH urls are untested and not likely to work
+# 2. mirrors cloned while Gitblit is running are likely to require clearing the
+# gitblit cache (link on the repositories page of an administrator account)
+# 3. Gitblit will automatically repair any invalid fetch refspecs with a "//"
+# sequence.
+#
+# SINCE 1.4.0
+# RESTART REQUIRED
+git.enableMirroring = false
+
+# Specify the period between update checks for mirrored repositories.
+# The shortest period you may specify between mirror update checks is 5 mins.
+#
+# SINCE 1.4.0
+# RESTART REQUIRED
+git.mirrorPeriod = 30 mins
+
+# Number of bytes of a pack file to load into memory in a single read operation.
+# This is the "page size" of the JGit buffer cache, used for all pack access
+# operations. All disk IO occurs as single window reads. Setting this too large
+# may cause the process to load more data than is required; setting this too small
+# may increase the frequency of read() system calls.
+#
+# Default on JGit is 8 KiB on all platforms.
+#
+# Common unit suffixes of k, m, or g are supported.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.packedGitWindowSize = 8k
+
+# Maximum number of bytes to load and cache in memory from pack files. If JGit
+# needs to access more than this many bytes it will unload less frequently used
+# windows to reclaim memory space within the process. As this buffer must be shared
+# with the rest of the JVM heap, it should be a fraction of the total memory available.
+#
+# The JGit team recommends setting this value larger than the size of your biggest
+# repository. This ensures you can serve most requests from memory.
+#
+# Default on JGit is 10 MiB on all platforms.
+#
+# Common unit suffixes of k, m, or g are supported.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.packedGitLimit = 10m
+
+# Maximum number of bytes to reserve for caching base objects that multiple deltafied
+# objects reference. By storing the entire decompressed base object in a cache Git
+# is able to avoid unpacking and decompressing frequently used base objects multiple times.
+#
+# Default on JGit is 10 MiB on all platforms. You probably do not need to adjust
+# this value.
+#
+# Common unit suffixes of k, m, or g are supported.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.deltaBaseCacheLimit = 10m
+
+# Maximum number of pack files to have open at once. A pack file must be opened
+# in order for any of its data to be available in a cached window.
+#
+# If you increase this to a larger setting you may need to also adjust the ulimit
+# on file descriptors for the host JVM, as Gitblit needs additional file descriptors
+# available for network sockets and other repository data manipulation.
+#
+# Default on JGit is 128 file descriptors on all platforms.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.packedGitOpenFiles = 128
+
+# When true, JGit will use mmap() rather than malloc()+read() to load data from
+# pack files. The use of mmap can be problematic on some JVMs as the garbage
+# collector must deduce that a memory mapped segment is no longer in use before
+# a call to munmap() can be made by the JVM native code.
+#
+# In server applications (such as Gitblit) that need to access many pack files,
+# setting this to true risks artificially running out of virtual address space,
+# as the garbage collector cannot reclaim unused mapped spaces fast enough.
+#
+# Default on JGit is false. Although potentially slower, it yields much more
+# predictable behavior.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.packedGitMmap = false
+
+# Validate all received (pushed) objects are valid.
+#
+# SINCE 1.5.0
+git.checkReceivedObjects = true
+
+# Validate all referenced but not supplied objects are reachable.
+#
+# If enabled, Gitblit will verify that references to objects not contained
+# within the received pack are already reachable through at least one other
+# reference advertised to clients.
+#
+# This feature is useful when Gitblit doesn't trust the client to not provide a
+# forged SHA-1 reference to an object, in an attempt to access parts of the DAG
+# that they aren't allowed to see and which have been hidden from them via the
+# configured AdvertiseRefsHook or RefFilter.
+#
+# Enabling this feature may imply at least some, if not all, of the same functionality
+# performed by git.checkReceivedObjects.
+#
+# SINCE 1.5.0
+git.checkReferencedObjectsAreReachable = true
+
+# Set the maximum allowed Git object size.
+#
+# If an object is larger than the given size the pack-parsing will throw an exception
+# aborting the receive-pack operation. The default value, 0, disables maximum
+# object size checking.
+#
+# SINCE 1.5.0
+git.maxObjectSizeLimit = 0
+
+# Set the maximum allowed pack size.
+#
+# A pack exceeding this size will be rejected. The default value, -1, disables
+# maximum pack size checking.
+#
+# SINCE 1.5.0
+git.maxPackSizeLimit = -1
+
+# Use the Gitblit patch receive pack for processing contributions and tickets.
+# This allows the user to push a patch using the familiar Gerrit syntax:
+#
+# git push <remote> HEAD:refs/for/<targetBranch>
+#
+# NOTE:
+# This requires git.enableGitServlet = true AND it requires an authenticated
+# git transport connection (http/https) when pushing from a client.
+#
+# Valid services include:
+# com.gitblit.tickets.FileTicketService
+# com.gitblit.tickets.BranchTicketService
+# com.gitblit.tickets.RedisTicketService
+#
+# SINCE 1.4.0
+# RESTART REQUIRED
+tickets.service =
+
+# Globally enable or disable creation of new bug, enhancement, task, etc tickets
+# for all repositories.
+#
+# If false, no tickets can be created through the ui for any repositories.
+# If true, each repository can control if they allow new tickets to be created.
+#
+# NOTE:
+# If a repository is accepting patchsets, new proposal tickets can be created
+# regardless of this setting.
+#
+# SINCE 1.4.0
+tickets.acceptNewTickets = true
+
+# Globally enable or disable pushing patchsets to all repositories.
+#
+# If false, no patchsets will be accepted for any repositories.
+# If true, each repository can control if they accept new patchsets.
+#
+# NOTE:
+# If a repository is accepting patchsets, new proposal tickets can be created
+# regardless of the acceptNewTickets setting.
+#
+# SINCE 1.4.0
+tickets.acceptNewPatchsets = true
+
+# Default setting to control patchset merge through the web ui. If true, patchsets
+# must have an approval score to enable the merge button. This setting can be
+# overriden per-repository.
+#
+# SINCE 1.4.0
+tickets.requireApproval = false
+
+# The case-insensitive regular expression used to identify and close tickets on
+# push to the integration branch for commits that are NOT already referenced as
+# a patchset tip.
+#
+# SINCE 1.5.0
+tickets.closeOnPushCommitMessageRegex = (?:fixes|closes)[\\s-]+#?(\\d+)
+
+# Specify the location of the Lucene Ticket index
+#
+# SINCE 1.4.0
+# RESTART REQUIRED
+tickets.indexFolder = ${baseFolder}/tickets/lucene
+
+# Define the url for the Redis server.
+#
+# e.g. redis://localhost:6379
+# redis://:foobared@localhost:6379/2
+#
+# SINCE 1.4.0
+# RESTART REQUIRED
+tickets.redis.url =
+
+# The number of tickets to display on a page.
+#
+# SINCE 1.4.0
+tickets.perPage = 25
+
+# The folder where plugins are loaded from.
+#
+# SINCE 1.5.0
+# RESTART REQUIRED
+# BASEFOLDER
+plugins.folder = ${baseFolder}/plugins
+
+# The registry of available plugins.
+#
+# SINCE 1.5.0
+plugins.registry = http://plugins.gitblit.com/plugins.json
+
+# The HTTP proxy host for plugin manager.
+#
+# SINCE 1.7.0
+plugins.httpProxyHost =
+
+# The HTTP proxy port for plugin manager.
+#
+# SINCE 1.7.0
+plugins.httpProxyPort =
+
+# The HTTP proxy authorization header for plugin manager.
+#
+# SINCE 1.7.0
+plugins.httpProxyAuthorization =
+
+# Number of threads used to handle miscellaneous tasks in the background.
+#
+# SINCE 1.6.0
+# RESTART REQUIRED
+execution.defaultThreadPoolSize = 1
+
+#
+# Groovy Integration
+#
+
+# Location of Groovy scripts to use for Pre and Post receive hooks.
+# Use forward slashes even on Windows!!
+# e.g. c:/groovy
+#
+# RESTART REQUIRED
+# SINCE 0.8.0
+# BASEFOLDER
+groovy.scriptsFolder = ${baseFolder}/groovy
+
+# Specify the directory Grape uses for downloading libraries.
+# http://groovy.codehaus.org/Grape
+#
+# RESTART REQUIRED
+# SINCE 1.0.0
+# BASEFOLDER
+groovy.grapeFolder = ${baseFolder}/groovy/grape
+
+# Scripts to execute on Pre-Receive.
+#
+# These scripts execute after an incoming push has been parsed and validated
+# but BEFORE the changes are applied to the repository. You might reject a
+# push in this script based on the repository and branch the push is attempting
+# to change.
+#
+# Script names are case-sensitive on case-sensitive file systems. You may omit
+# the traditional ".groovy" from this list if your file extension is ".groovy"
+#
+# NOTE:
+# These scripts are only executed when pushing to *Gitblit*, not to other Git
+# tooling you may be using. Also note that these scripts are shared between
+# repositories. These are NOT repository-specific scripts! Within the script
+# you may customize the control-flow for a specific repository by checking the
+# *repository* variable.
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 0.8.0
+groovy.preReceiveScripts =
+
+# Scripts to execute on Post-Receive.
+#
+# These scripts execute AFTER an incoming push has been applied to a repository.
+# You might trigger a continuous-integration build here or send a notification.
+#
+# Script names are case-sensitive on case-sensitive file systems. You may omit
+# the traditional ".groovy" from this list if your file extension is ".groovy"
+#
+# NOTE:
+# These scripts are only executed when pushing to *Gitblit*, not to other Git
+# tooling you may be using. Also note that these scripts are shared between
+# repositories. These are NOT repository-specific scripts! Within the script
+# you may customize the control-flow for a specific repository by checking the
+# *repository* variable.
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 0.8.0
+groovy.postReceiveScripts =
+
+# Repository custom fields for Groovy Hook mechanism
+#
+# List of key=label pairs of custom fields to prompt for in the Edit Repository
+# page. These keys are stored in the repository's git config file in the
+# section [gitblit "customFields"]. Key names are alphanumeric only. These
+# fields are intended to be used for the Groovy hook mechanism where a script
+# can adjust it's execution based on the custom fields stored in the repository
+# config.
+#
+# e.g. "commitMsgRegex=Commit Message Regular Expression" anotherProperty=Another
+#
+# SPACE-DELIMITED
+# SINCE 1.0.0
+groovy.customFields =
+
+#
+# Fanout Settings
+#
+
+# Fanout is a PubSub notification service that can be used by Sparkleshare
+# to eliminate repository change polling. The fanout service runs in a separate
+# thread on a separate port from the Gitblit http/https application.
+# This service is provided so that Sparkleshare may be used with Gitblit in
+# firewalled environments or where reliance on Sparkleshare's default notifications
+# server (notifications.sparkleshare.org) is unwanted.
+#
+# This service maintains an open socket connection from the client to the
+# Fanout PubSub service. This service may not work properly behind a proxy server.
+
+# Specify the interface for Fanout to bind it's service.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 1.2.1
+# RESTART REQUIRED
+fanout.bindInterface =
+
+# port for serving the Fanout PubSub service. <= 0 disables this service.
+# On Unix/Linux systems, ports < 1024 require root permissions.
+# Recommended value: 17000
+#
+# SINCE 1.2.1
+# RESTART REQUIRED
+fanout.port = 0
+
+# Use Fanout NIO service. If false, a multi-threaded socket service will be used.
+# Be advised, the socket implementation spawns a thread per connection plus the
+# connection acceptor thread. The NIO implementation is completely single-threaded.
+#
+# SINCE 1.2.1
+# RESTART REQUIRED
+fanout.useNio = true
+
+# Concurrent connection limit. <= 0 disables concurrent connection throttling.
+# If > 0, only the specified number of concurrent connections will be allowed
+# and all other connections will be rejected.
+#
+# SINCE 1.2.1
+# RESTART REQUIRED
+fanout.connectionLimit = 0
+
+#
+# Authentication Settings
+#
+
+# Require authentication to see everything but the admin pages
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.authenticateViewPages = false
+
+# If web.authenticateViewPages=true you may optionally require a client-side
+# basic authentication prompt instead of the standard form-based login.
+#
+# SINCE 1.3.0
+web.enforceHttpBasicAuthentication = false
+
+# Require admin authentication for the admin functions and pages
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.authenticateAdminPages = true
+
+# Allow Gitblit to store a cookie in the user's browser for automatic
+# authentication. The cookie is generated by the user service.
+#
+# SINCE 0.5.0
+web.allowCookieAuthentication = true
+
+# Allow deletion of non-empty repositories. This is enforced for all delete vectors.
+#
+# SINCE 1.6.0
+web.allowDeletingNonEmptyRepositories = true
+
+# Setting to include personal repositories in the main repositories list.
+#
+# SINCE 1.6.0
+web.includePersonalRepositories = false
+
+# Config file for storing project metadata
+#
+# SINCE 1.2.0
+# BASEFOLDER
+web.projectsFile = ${baseFolder}/projects.conf
+
+# Defines the tab length for all blob views
+#
+# SINCE 1.7.0
+web.tabLength = 4
+
+# Either the full path to a user config file (users.conf)
+# OR a fully qualified class name that implements the IUserService interface.
+#
+# Any custom user service implementation must have a public default constructor.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+# BASEFOLDER
+realm.userService = ${baseFolder}/users.conf
+
+# Ordered list of external authentication providers which will be used if
+# authentication against the local user service fails.
+#
+# Valid providers are:
+#
+# htpasswd
+# httpheader
+# ldap
+# pam
+# redmine
+# salesforce
+# windows
+
+# e.g. realm.authenticationProviders = htpasswd windows
+#
+# SINCE 1.4.0
+# RESTART REQUIRED
+# SPACE-DELIMITED
+realm.authenticationProviders =
+
+# How to store passwords.
+# Valid values are plain, md5, or combined-md5. md5 is the hash of password.
+# combined-md5 is the hash of username.toLowerCase()+password.
+# Default is md5.
+#
+# SINCE 0.5.0
+realm.passwordStorage = md5
+
+# Minimum valid length for a plain text password.
+# Default value is 5. Absolute minimum is 4.
+#
+# SINCE 0.5.0
+realm.minPasswordLength = 5
+
+#
+# Gitblit Web Settings
+#
+# If blank Gitblit is displayed.
+#
+# SINCE 0.5.0
+web.siteName =
+
+# The canonical url of your Gitblit server to be used in repository url generation,
+# RSS feeds, and all embedded links in email and plugin-based notifications.
+#
+# If you are running Gitblit on a non-standard http port (i.e. not 80 and not 443)
+# then you must specify that port in this url otherwise your generated urls will be
+# incorrect.
+#
+# The hostname of this url will be extracted for SSH and GIT protocol repository
+# url generation.
+#
+# e.g. web.canonicalUrl = https://dev.gitblit.com
+# web.canonicalUrl = https://dev.gitblit.com:8443
+#
+# SINCE 1.4.0
+web.canonicalUrl =
+
+# You may specify a different logo image for the header but it must be 120x45px.
+# If the specified file does not exist, the default Gitblit logo will be used.
+#
+# SINCE 1.3.0
+# BASEFOLDER
+web.headerLogo = ${baseFolder}/logo.png
+
+# You may specify a different link URL for the logo image anchor.
+# If blank the Gitblit main page URL is used.
+#
+# SINCE 1.3.0
+# BASEFOLDER
+web.rootLink =
+
+# You may specify a custom header background CSS color. If unspecified, the
+# default color will be used.
+#
+# e.g. web.headerBackgroundColor = #002060
+#
+# SINCE 1.3.0
+web.headerBackgroundColor =
+
+# You may specify a custom header foreground CSS color. If unspecified, the
+# default color will be used.
+#
+# e.g. web.headerForegroundColor = white
+#
+# SINCE 1.3.0
+web.headerForegroundColor =
+
+# You may specify a custom header foreground hover CSS color. If unspecified, the
+# default color will be used.
+#
+# e.g. web.headerHoverColor = white
+#
+# SINCE 1.3.0
+web.headerHoverColor =
+
+# You may specify a custom header border CSS color. If unspecified, the default
+# color will be used.
+#
+# e.g. web.headerBorderColor = #002060
+#
+# SINCE 1.3.0
+web.headerBorderColor =
+
+# You may specify a custom header border CSS color. If unspecified, the default
+# color will be used.
+#
+# e.g. web.headerBorderFocusColor = #ff9900
+#
+# SINCE 1.3.0
+web.headerBorderFocusColor =
+
+# If *web.authenticateAdminPages*=true, users with "admin" role can create
+# repositories, create users, and edit repository metadata.
+#
+# If *web.authenticateAdminPages*=false, any user can execute the aforementioned
+# functions.
+#
+# SINCE 0.5.0
+web.allowAdministration = true
+
+# Setting to disable rendering the top-level navigation header which includes
+# the login form, top-level links like dashboard, repositories, search, etc.
+# This setting is only useful if you plan to embed Gitblit within another page
+# or system.
+#
+# SINCE 1.4.0
+web.hideHeader = false
+
+# Allows rpc clients to list repositories and possibly manage or administer the
+# Gitblit server, if the authenticated account has administrator permissions.
+# See *web.enableRpcManagement* and *web.enableRpcAdministration*.
+#
+# SINCE 0.7.0
+web.enableRpcServlet = true
+
+# Allows rpc clients to manage repositories and users of the Gitblit instance,
+# if the authenticated account has administrator permissions.
+# Requires *web.enableRpcServlet=true*.
+#
+# SINCE 0.7.0
+web.enableRpcManagement = false
+
+# Allows rpc clients to control the server settings and monitor the health of this
+# this Gitblit instance, if the authenticated account has administrator permissions.
+# Requires *web.enableRpcServlet=true* and *web.enableRpcManagement*.
+#
+# SINCE 0.7.0
+web.enableRpcAdministration = false
+
+# Full path to a configurable robots.txt file. With this file you can control
+# what parts of your Gitblit server respectable robots are allowed to traverse.
+# http://googlewebmastercentral.blogspot.com/2008/06/improving-on-robots-exclusion-protocol.html
+#
+# SINCE 1.0.0
+# BASEFOLDER
+web.robots.txt = ${baseFolder}/robots.txt
+
+# The number of minutes to cache a page in the browser since the last request.
+# The default value is 0 minutes. A value <= 0 disables all page caching which
+# is the default behavior for Gitblit <= 1.3.0.
+#
+# SINCE 1.3.1
+web.pageCacheExpires = 0
+
+# If true, the web ui layout will respond and adapt to the browser's dimensions.
+# if false, the web ui will use a 940px fixed-width layout.
+# http://twitter.github.com/bootstrap/scaffolding.html#responsive
+#
+# SINCE 1.0.0
+web.useResponsiveLayout = true
+
+# Allow Gravatar images to be displayed in Gitblit pages.
+#
+# SINCE 0.8.0
+web.allowGravatar = true
+
+# Define which class will generate the avatar URL.
+#
+# SINCE 1.7.0
+web.avatarClass = com.gitblit.GravatarGenerator
+
+# Allow dynamic zip downloads.
+#
+# SINCE 0.5.0
+web.allowZipDownloads = true
+
+# If *web.allowZipDownloads=true* the following formats will be displayed for
+# download compressed archive links:
+#
+# zip = standard .zip
+# tar = standard tar format (preserves *nix permissions and symlinks)
+# gz = gz-compressed tar
+# xz = xz-compressed tar
+# bzip2 = bzip2-compressed tar
+#
+# SPACE-DELIMITED
+# SINCE 1.2.0
+web.compressedDownloads = zip gz
+
+# Allow optional Lucene integration. Lucene indexing is an opt-in feature.
+# A repository may specify branches to index with Lucene instead of using Git
+# commit traversal. There are scenarios where you may want to completely disable
+# Lucene indexing despite a repository specifying indexed branches. One such
+# scenario is on a resource-constrained federated Gitblit mirror.
+#
+# SINCE 0.9.0
+web.allowLuceneIndexing = true
+
+# Control the frequency of Lucene repository indexing.
+# The default setting is to check for updated refs every 2 mins.
+#
+# SINCE 1.6.1
+web.luceneFrequency = 2 mins
+
+# Allows an authenticated user to create forks of a repository
+#
+# set this to false if you want to disable all fork controls on the web site
+#
+web.allowForking = true
+
+# Controls the length of shortened commit hash ids
+#
+# SINCE 1.2.0
+web.shortCommitIdLength = 6
+
+# Use Clippy (Flash solution) to provide a copy-to-clipboard button.
+# If false, a button with a more primitive JavaScript-based prompt box will
+# offer a 3-step (click, ctrl+c, enter) copy-to-clipboard alternative.
+#
+# SINCE 0.8.0
+web.allowFlashCopyToClipboard = true
+
+# Default maximum number of commits that a repository may contribute to the
+# activity page, regardless of the selected duration. This setting may be valuable
+# for an extremely busy server. This value may also be configed per-repository
+# in Edit Repository. 0 disables this throttle.
+#
+# SINCE 1.2.0
+web.maxActivityCommits = 0
+
+# Default number of entries to include in RSS Syndication links
+#
+# SINCE 0.5.0
+web.syndicationEntries = 25
+
+# Show the size of each repository on the repositories page.
+# This requires recursive traversal of each repository folder. This may be
+# non-performant on some operating systems and/or filesystems.
+#
+# SINCE 0.5.2
+web.showRepositorySizes = true
+
+# List of custom regex expressions that can be displayed in the Filters menu
+# of the Repositories and Activity pages. Keep them very simple because you
+# are likely to run into encoding issues if they are too complex.
+#
+# Use !!! to separate the filters
+#
+# SINCE 0.8.0
+web.customFilters =
+
+# Show federation registrations (without token) and the current pull status
+# to non-administrator users.
+#
+# SINCE 0.6.0
+web.showFederationRegistrations = false
+
+# This is the message displayed when *web.authenticateViewPages=true*.
+# This can point to a file with Markdown content.
+# Specifying "gitblit" uses the internal login message.
+#
+# SINCE 0.7.0
+# BASEFOLDER
+web.loginMessage = gitblit
+
+# This is the message displayed above the repositories table.
+# This can point to a file with Markdown content.
+# Specifying "gitblit" uses the internal welcome message.
+#
+# SINCE 0.5.0
+# BASEFOLDER
+web.repositoriesMessage = gitblit
+
+# Ordered list of charsets/encodings to use when trying to display a blob.
+# If empty, UTF-8 and ISO-8859-1 are used. The server's default charset
+# is always appended to the encoding list. If all encodings fail to cleanly
+# decode the blob content, UTF-8 will be used with the standard malformed
+# input/unmappable character replacement strings.
+#
+# SPACE-DELIMITED
+# SINCE 1.0.0
+web.blobEncodings = UTF-8 ISO-8859-1
+
+# Manually set the default timezone to be used by Gitblit for display in the
+# web ui. This value is independent of the JVM timezone. Specifying a blank
+# value will default to the JVM timezone.
+# e.g. America/New_York, US/Pacific, UTC, Europe/Berlin
+#
+# SINCE 0.9.0
+# RESTART REQUIRED
+web.timezone =
+
+# Use the client timezone when formatting dates.
+# This uses AJAX to determine the browser's timezone and may require more
+# server overhead because a Wicket session is created. All Gitblit pages
+# attempt to be stateless, if possible.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.useClientTimezone = false
+
+# Time format
+# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
+#
+# SINCE 0.8.0
+web.timeFormat = HH:mm
+
+# Short date format
+# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
+#
+# SINCE 0.5.0
+web.datestampShortFormat = yyyy-MM-dd
+
+# Long date format
+#
+# SINCE 0.8.0
+web.datestampLongFormat = EEEE, MMMM d, yyyy
+
+# Long timestamp format
+# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
+#
+# SINCE 0.5.0
+web.datetimestampLongFormat = EEEE, MMMM d, yyyy HH:mm Z
+
+# Mount URL parameters
+# This setting controls if pretty or parameter URLs are used.
+# i.e.
+# if true:
+# http://localhost/commit/myrepo/abcdef
+# if false:
+# http://localhost/commit/?r=myrepo&h=abcdef
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.mountParameters = true
+
+# Some servlet containers (e.g. Tomcat >= 6.0.10) disallow '/' (%2F) encoding
+# in URLs as a security precaution for proxies. This setting tells Gitblit
+# to preemptively replace '/' with '*' or '!' for url string parameters.
+#
+# <https://issues.apache.org/jira/browse/WICKET-1303>
+# <http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10>
+# Add *-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true* to your
+# *CATALINA_OPTS* or to your JVM launch parameters
+#
+# SINCE 0.5.2
+web.forwardSlashCharacter = /
+
+# Show other URLs on the summary page for accessing your git repositories
+# Use spaces to separate urls.
+#
+# {0} is the token for the repository name
+# {1} is the token for the username
+#
+# The username is only practical if you have setup your other git serving
+# solutions accounts to have the same username as the Gitblit account.
+#
+# e.g.
+# web.otherUrls = ssh://localhost/git/{0} git://localhost/git/{0} https://{1}@localhost/r/{0}
+#
+# SPACE-DELIMITED
+# SINCE 0.5.0
+web.otherUrls =
+
+# Should HTTP/HTTPS URLs be displayed if the git servlet is enabled?
+# default: true
+#
+# SINCE 1.7.0
+web.showHttpServletUrls = true
+
+# Should git URLs be displayed if the git daemon is enabled?
+# default: true
+#
+# SINCE 1.7.0
+web.showGitDaemonUrls = true
+
+# Should SSH URLs be displayed if the SSH daemon is enabled?
+# default: true
+#
+# SINCE 1.7.0
+web.showSshDaemonUrls = true
+
+# Should effective permissions be advertised for access paths defined in web.otherUrls?
+# If false, gitblit will indicate unknown permissions for the external link. If true,
+# gitblit will indicate permissions as defined within gitblit (including limiting to clone
+# permission is the transport type is not a valid push mechaism in git.acceptedPushTransports).
+#
+# Configure with caution: Note that gitblit has no way of knowing if further restrictions
+# are imposed by an external forwarding agent, so this may cause user confusion due to
+# more rights being advertised than are available through the URL. It will NOT grant
+# additional rights, but may incorrectly offer actions that are unavailable externally.
+# default: false
+#
+# SINCE 1.7.0
+web.advertiseAccessPermissionForOtherUrls = false
+
+# Should app-specific clone links be displayed for SourceTree, SparkleShare, etc?
+#
+# SINCE 1.3.0
+web.allowAppCloneLinks = true
+
+# Choose how to present the repositories list.
+# grouped = group nested/subfolder repositories together (no sorting)
+# flat = flat list of repositories (sorting allowed)
+#
+# SINCE 0.5.0
+web.repositoryListType = grouped
+
+# If using a grouped repository list and there are repositories at the
+# root level of your repositories folder, you may specify the displayed
+# group name with this setting. This value is only used for web presentation.
+#
+# SINCE 0.5.0
+web.repositoryRootGroupName = main
+
+# Display the repository swatch color next to the repository name link in the
+# repositories list.
+#
+# SINCE 0.8.0
+web.repositoryListSwatches = true
+
+# Defines the default commit message renderer. This can be configured
+# per-repository.
+#
+# Valid values are: plain, markdown
+#
+# SINCE 1.4.0
+web.commitMessageRenderer = plain
+
+# Control if email addresses are shown in web ui
+#
+# SINCE 0.5.0
+web.showEmailAddresses = true
+
+# Shows a combobox in the page links header with commit, committer, and author
+# search selection. Default search is commit.
+#
+# SINCE 0.5.0
+web.showSearchTypeSelection = false
+
+# Controls display of activity graphs on the dashboard, activity, and summary
+# pages. Charts are generated using Flotr2; an open source HTML5 library.
+#
+# SINCE 0.5.0
+web.generateActivityGraph = true
+
+# Displays the commits branch graph in the summary page and commits/log page.
+#
+# SINCE 1.4.0
+web.showBranchGraph = true
+
+# The default number of days to show on the activity page.
+# Value must exceed 0 else default of 7 is used
+#
+# SINCE 0.8.0
+web.activityDuration = 7
+
+# Choices for days of activity to display.
+#
+# SPACE-DELIMITED
+# SINCE 1.3.0
+web.activityDurationChoices = 1 3 7 14 21 28
+
+# Maximum number of days of activity that may be displayed on the activity page.
+#
+# SINCE 1.3.2
+web.activityDurationMaximum = 30
+
+# The number of days of commits to cache in memory for the dashboard, activity,
+# and project pages. A value of 0 will disable all caching and will parse commits
+# in each repository per-request. If the value > 0 these pages will try to fulfill
+# requests using the commit cache. If the request specifies a period which falls
+# outside the commit cache window, then the cache will be ignored and the request
+# will be fulfilled by brute-force parsing all relevant commits per-repository.
+#
+# Consider the values specified for *web.activityDurationChoices* when setting
+# the cache size AND consider adjusting the JVM -Xmx heap parameter appropriately.
+#
+# SINCE 1.3.0
+# RESTART REQUIRED
+web.activityCacheDays = 14
+
+# Case-insensitive list of authors to exclude from metrics. Useful for
+# eliminating bots.
+#
+# SPACE-DELIMITED
+# SINCE 1.3.0
+web.metricAuthorExclusions =
+
+# The number of commits to display on the summary page
+# Value must exceed 0 else default of 20 is used
+#
+# SINCE 0.5.0
+web.summaryCommitCount = 16
+
+# The number of tags/branches to display on the summary page.
+# -1 = all tags/branches
+# 0 = hide tags/branches
+# N = N tags/branches
+#
+# SINCE 0.5.0
+web.summaryRefsCount = 5
+
+# Show a README file, if available, on the summary page.
+#
+# SINCE 1.4.0
+web.summaryShowReadme = false
+
+# The number of items to show on a page before showing the first, prev, next
+# pagination links. A default of 50 is used for any invalid value.
+#
+# SINCE 0.5.0
+web.itemsPerPage = 50
+
+# The number of reflog changes to display on the overview page
+# Value must exceed 0 else default of 5 is used
+#
+# SINCE 1.3.0
+web.overviewReflogCount = 5
+
+# The number of reflog changes to show on a reflog page before show the first,
+# prev, next pagination links. A default of 10 is used for any invalid value.
+#
+# SINCE 1.3.0
+web.reflogChangesPerPage = 10
+
+# Specify the names of documents in the root of your repository to be displayed
+# in tabs on your repository docs page. If the name is not found in the root
+# then no tab is added. The order specified is the order displayed. Do not
+# specify a file extension as the aggregation of markup extensions + txt are used
+# in the search algorithm.
+#
+# SPACE-DELIMITED
+# SINCE 1.4.0
+web.documents = readme home index changelog contributing submitting_patches copying license notice authors
+
+# Registered file extensions to ignore during Lucene indexing
+#
+# SPACE-DELIMITED
+# SINCE 0.9.0
+web.luceneIgnoreExtensions = 7z arc arj bin bmp dll doc docx exe gif gz jar jpg lib lzh odg odf odt pdf ppt pptx png so swf tar xcf xls xlsx zip
+
+# Registered extensions for google-code-prettify
+#
+# SPACE-DELIMITED
+# SINCE 0.5.0
+web.prettyPrintExtensions = aea agc basic bat c cbm cl clj cmd cpp cs css dart el erl erlang frm fs go groovy h hpp hs htm html java js latex lisp ll llvm lsp lua ml moxie mumps n nemerle pascal php pl pm prefs properties proto py r R rb rd Rd rkt s S scala scm sh Splus sql ss tcl tex vb vbs vhd vhdl wiki xml xq xquery yaml yml ymlapollo
+
+# Registered extensions for markdown transformation
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 0.5.0
+web.markdownExtensions = md mkd markdown MD MKD
+
+# Registered extensions for mediawiki transformation
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.4.0
+web.mediawikiExtensions = mw mediawiki
+
+# Registered extensions for twiki transformation
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.4.0
+web.twikiExtensions = twiki
+
+# Registered extensions for textile transformation
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.4.0
+web.textileExtensions = textile
+
+# Registered extensions for confluence transformation
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.4.0
+web.confluenceExtensions = confluence
+
+# Registered extensions for tracwiki transformation
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.4.0
+web.tracwikiExtensions = tracwiki
+
+# Image extensions
+#
+# SPACE-DELIMITED
+# SINCE 0.5.0
+web.imageExtensions = bmp ico gif jpg jpeg png svg
+
+# Registered extensions for binary blobs
+#
+# SPACE-DELIMITED
+# SINCE 0.5.0
+web.binaryExtensions = 7z arc arj bin dll doc docx exe gz jar lib lzh odg odf odt pdf ppt pptx so tar xls xlsx zip
+
+# Aggressive heap management will run the garbage collector on every generated
+# page. This slows down page generation a little but improves heap consumption.
+#
+# SINCE 0.5.0
+web.aggressiveHeapManagement = false
+
+# Run the webapp in debug mode
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.debugMode = false
+
+# Allows to hide the user logon form or dropdown menu from the top pane
+# if it's not needed.
+#
+# SINCE 1.7.0
+web.displayUserPanel = true
+
+# Force a default locale for all users, ignoring the browser's settings.
+# An empty value allows Gitblit to use the translation preferred by the browser.
+#
+# Changing this value while the server is running will only affect new sessions.
+#
+# e.g. web.forceDefaultLocale = en
+#
+# SINCE 1.3.0
+web.forceDefaultLocale =
+
+# The following two settings serve to avoid browser overload when trying to
+# render very large diffs. Both limits apply to commitdiffs, not to single-file
+# diffs.
+
+# Maximum number of diff lines to display for a single file diff in a commitdiff.
+# Defaults to 4000; can be adjusted in the range [500 .. 4000]. Smaller values
+# set the limit to 500, larger values to 4000. The count includes context lines
+# in the diff.
+#
+# If a file diff in a commitdiff produces more lines, the diff for that file is
+# not shown in the commitdiff.
+#
+# SINCE 1.7.0
+web.maxDiffLinesPerFile = 4000
+
+# Total maximum number of diff lines to show in a commitdiff. Defaults to 20000;
+# can be adjusted in the range [1000 .. 20000]. Smaller values set the limit to
+# 1000, larger values to 20000. The count includes context lines in diffs.
+#
+# If a commitdiff produces more lines, it is truncated after the first file
+# that exceeds the limit. Diffs for subsequent files in the commit are not shown
+# at all in the commitdiff. Omitted files are listed, though.
+#
+# SINCE 1.7.0
+web.maxDiffLines = 20000
+
+# Enable/disable global regex substitutions (i.e. shared across repositories)
+#
+# SINCE 0.5.0
+# DEPRECATED 1.4.0 (migrate to bugtraq instead)
+regex.global = true
+
+# Example global regex substitutions
+# Use !!! to separate the search pattern and the replace pattern
+# searchpattern!!!replacepattern
+# SINCE 0.5.0
+
+# regex.global.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!Bug: <a href="http://somehost/bug/$3">$3</a>
+# SINCE 0.5.0
+
+# Example Gerrit links
+# regex.global.changeid = \\b(Change-Id:\\s*)([A-Za-z0-9]*)\\b!!!Change-Id: <a href="http://somehost/r/#q,$2,n,z">$2</a>
+# regex.global.reviewedon = \\b(Reviewed-on:\\s*)([A-Za-z0-9:/\\.]*)\\b!!!Reviewed-on: <a href="$2">$2</a>
+
+# Example per-repository regex substitutions overrides global
+# SINCE 0.5.0
+# regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!Bug: <a href="http://elsewhere/bug/$3">$3</a>
+
+#
+# Mail Settings
+# SINCE 0.6.0
+#
+# Mail settings are used to notify administrators of received federation proposals
+#
+
+# ip or hostname of smtp server
+#
+# SINCE 0.6.0
+mail.server =
+
+# port to use for smtp requests
+#
+# SINCE 0.6.0
+mail.port = 25
+
+# debug the mail executor
+#
+# SINCE 0.6.0
+mail.debug = false
+
+# use SMTPs flag
+mail.smtps = false
+
+# use STARTTLS flag
+#
+# SINCE 1.6.0
+mail.starttls = false
+
+# if your smtp server requires authentication, supply the credentials here
+#
+# SINCE 0.6.0
+mail.username =
+# SINCE 0.6.0
+mail.password =
+
+# from address for generated emails
+#
+# SINCE 0.6.0
+mail.fromAddress =
+
+# List of email addresses for the Gitblit administrators
+#
+# SPACE-DELIMITED
+# SINCE 0.6.0
+mail.adminAddresses =
+
+# List of email addresses for sending push email notifications.
+#
+# This key currently requires use of the sendemail.groovy hook script.
+# If you set sendemail.groovy in *groovy.postReceiveScripts* then email
+# notifications for all repositories (regardless of access restrictions!)
+# will be sent to these addresses.
+#
+# SPACE-DELIMITED
+# SINCE 0.8.0
+mail.mailingLists =
+
+#
+# Federation Settings
+# SINCE 0.6.0
+#
+# A Gitblit federation is a way to backup one Gitblit instance to another.
+#
+# *git.enableGitServlet* must be true to use this feature.
+
+# Your federation name is used for federation status acknowledgments. If it is
+# unset, and you elect to send a status acknowledgment, your Gitblit instance
+# will be identified by its hostname, if available, else your internal ip address.
+# The source Gitblit instance will also append your external IP address to your
+# identification to differentiate multiple pulling systems behind a single proxy.
+#
+# SINCE 0.6.0
+federation.name =
+
+# Specify the passphrase of this Gitblit instance.
+#
+# An unspecified (empty) passphrase disables processing federation requests.
+#
+# This value can be anything you want: an integer, a sentence, an haiku, etc.
+# Keep the value simple, though, to avoid Java properties file encoding issues.
+#
+# Changing your passphrase will break any registrations you have established with other
+# Gitblit instances.
+#
+# CASE-SENSITIVE
+# SINCE 0.6.0
+# RESTART REQUIRED *(only to enable or disable federation)*
+federation.passphrase =
+
+# Control whether or not this Gitblit instance can receive federation proposals
+# from another Gitblit instance. Registering a federated Gitblit is a manual
+# process. Proposals help to simplify that process by allowing a remote Gitblit
+# instance to send your Gitblit instance the federation pull data.
+#
+# SINCE 0.6.0
+federation.allowProposals = false
+
+# The destination folder for cached federation proposals.
+# Use forward slashes even on Windows!!
+#
+# SINCE 0.6.0
+# BASEFOLDER
+federation.proposalsFolder = ${baseFolder}/proposals
+
+# The default pull frequency if frequency is unspecified on a registration
+#
+# SINCE 0.6.0
+federation.defaultFrequency = 60 mins
+
+# Federation Sets are named groups of repositories. The Federation Sets are
+# available for selection in the repository settings page. You can assign a
+# repository to one or more sets and then distribute the token for the set.
+# This allows you to grant federation pull access to a subset of your available
+# repositories. Tokens for federation sets only grant repository pull access.
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 0.6.0
+federation.sets =
+
+# Federation pull registrations
+# Registrations are read once, at startup.
+#
+# RESTART REQUIRED
+#
+# frequency:
+# The shortest frequency allowed is every 5 minutes
+# Decimal frequency values are cast to integers
+# Frequency values may be specified in mins, hours, or days
+# Values that can not be parsed or are unspecified default to *federation.defaultFrequency*
+#
+# folder:
+# if unspecified, the folder is *git.repositoriesFolder*
+# if specified, the folder is relative to *git.repositoriesFolder*
+#
+# bare:
+# if true, each repository will be created as a *bare* repository and will not
+# have a working directory.
+#
+# if false, each repository will be created as a normal repository suitable
+# for local work.
+#
+# mirror:
+# if true, each repository HEAD is reset to *origin/master* after each pull.
+# The repository will be flagged *isFrozen* after the initial clone.
+#
+# if false, each repository HEAD will point to the FETCH_HEAD of the initial
+# clone from the origin until pushed to or otherwise manipulated.
+#
+# mergeAccounts:
+# if true, remote accounts and their permissions are merged into your
+# users.properties file
+#
+# notifyOnError:
+# if true and the mail configuration is properly set, administrators will be
+# notified by email of pull failures
+#
+# include and exclude:
+# Space-delimited list of repositories to include or exclude from pull
+# may be * wildcard to include or exclude all
+# may use fuzzy match (e.g. org.eclipse.*)
+
+#
+# (Nearly) Perfect Mirror example
+#
+
+#federation.example1.url = https://go.gitblit.com
+#federation.example1.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
+#federation.example1.frequency = 120 mins
+#federation.example1.folder =
+#federation.example1.bare = true
+#federation.example1.mirror = true
+#federation.example1.mergeAccounts = true
+
+#
+# Advanced Realm Settings
+#
+
+# Auto-creates user accounts based on the servlet container principal. This
+# assumes that your Gitblit install is a protected resource and your container's
+# authentication process intercepts all Gitblit requests.
+#
+# SINCE 1.3.0
+realm.container.autoCreateAccounts = false
+
+# A set of mapping used to map HTTP session attributes to user informations
+# They are used if realm.container.autoCreateAccounts is set to true and
+# the webapp container used can fill the session with user informations
+#
+# SINCE 1.7.0
+realm.container.autoAccounts.displayName =
+realm.container.autoAccounts.emailAddress =
+realm.container.autoAccounts.locale =
+
+# If the user's created by the webapp container is given this role,
+# the user created will be a admin user.
+#
+# SINCE 1.7.0
+realm.container.autoAccounts.adminRole =
+
+
+# Allow or prohibit Windows guest account logins
+#
+# SINCE 1.3.0
+realm.windows.allowGuests = false
+
+# Allow user accounts belonging to the BUILTIN\Administrators group to be
+# Gitblit administrators.
+#
+# SINCE 1.4.0
+realm.windows.permitBuiltInAdministrators = true
+
+# The default domain for authentication.
+#
+# If specified, this domain will be used for authentication UNLESS the supplied
+# login name manually specifies a domain (.e.g. mydomain\james or james@mydomain)
+#
+# If unspecified, the username must be specified in UPN format (name@domain).
+#
+# if "." (dot) is specified, ONLY the local account database will be used.
+#
+# SINCE 1.3.0
+realm.windows.defaultDomain =
+
+# The PAM service name for authentication.
+# default: system-auth
+#
+# SINCE 1.3.1
+realm.pam.serviceName = system-auth
+
+# The Apache htpasswd file that contains the users and passwords.
+# default: ${baseFolder}/htpasswd
+#
+# RESTART REQUIRED
+# BASEFOLDER
+# SINCE 1.3.2
+realm.htpasswd.userfile = ${baseFolder}/htpasswd
+
+# The name of the HTTP header containing the user name to trust as authenticated
+# default: none
+#
+# WARNING: only use this mechanism if your requests are coming from a trusted
+# and secure source such as a self managed reverse proxy!
+#
+# RESTART REQUIRED
+# SINCE 1.7.2
+realm.httpheader.userheader =
+
+# The name of the HTTP header containing the team names of which the user is a member.
+# If this is defined, then only groups from the headers will be available, whereas
+# if this remains undefined, then local groups will be used.
+#
+# This setting requires that you have configured realm.httpheader.userheader.
+#
+# default: none
+#
+# RESTART REQUIRED
+# SINCE 1.7.2
+realm.httpheader.teamheader =
+
+# The regular expression pattern used to separate team names in the team header value
+# default: ,
+#
+# This setting requires that you have configured realm.httpheader.teamheader
+#
+# RESTART REQUIRED
+# SINCE 1.7.2
+realm.httpheader.teamseparator = ,
+
+# Auto-creates user accounts when successfully authenticated based on HTTP headers.
+#
+# SINCE 1.7.2
+realm.httpheader.autoCreateAccounts = false
+
+# Restrict the Salesforce user to members of this org.
+# default: 0 (i.e. do not check the Org ID)
+#
+# SINCE 1.3.0
+realm.salesforce.orgId = 0
+
+# URL of the LDAP server.
+# To use encrypted transport, use either ldaps:// URL for SSL or ldap+tls:// to
+# send StartTLS command.
+#
+# SINCE 1.0.0
+realm.ldap.server = ldap://localhost
+
+# Login username for LDAP searches.
+# If this value is unspecified, anonymous LDAP login will be used.
+#
+# e.g. mydomain\\username
+#
+# SINCE 1.0.0
+realm.ldap.username = cn=Directory Manager
+
+# Login password for LDAP searches.
+#
+# SINCE 1.0.0
+realm.ldap.password = password
+
+# Bind pattern for Authentication.
+# Allow to directly authenticate an user without LDAP Searches.
+#
+# e.g. CN=${username},OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain
+#
+# SINCE 1.5.0
+realm.ldap.bindpattern =
+
+
+# Delegate team membership control to LDAP.
+#
+# If true, team user memberships will be specified by LDAP groups. This will
+# disable team selection in Edit User and user selection in Edit Team.
+#
+# If false, LDAP will only be used for authentication and Gitblit will maintain
+# team memberships with the *realm.ldap.backingUserService*.
+#
+# SINCE 1.0.0
+realm.ldap.maintainTeams = false
+
+# Root node for all LDAP users
+#
+# This is the root node from which subtree user searches will begin.
+# If blank, Gitblit will search ALL nodes.
+#
+# SINCE 1.0.0
+realm.ldap.accountBase = OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain
+
+# Filter criteria for LDAP users
+#
+# Query pattern to use when searching for a user account. This may be any valid
+# LDAP query expression, including the standard (&) and (|) operators.
+#
+# Variables may be injected via the ${variableName} syntax.
+# Recognized variables are:
+# ${username} - The text entered as the user name
+#
+# SINCE 1.0.0
+realm.ldap.accountPattern = (&(objectClass=person)(sAMAccountName=${username}))
+
+# Root node for all LDAP groups to be used as Gitblit Teams
+#
+# This is the root node from which subtree team searches will begin.
+# If blank, Gitblit will search ALL nodes.
+#
+# SINCE 1.0.0
+realm.ldap.groupBase = OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain
+
+# Filter criteria for LDAP groups
+#
+# Query pattern to use when searching for a team. This may be any valid
+# LDAP query expression, including the standard (&) and (|) operators.
+#
+# Variables may be injected via the ${variableName} syntax.
+# Recognized variables are:
+# ${username} - The text entered as the user name
+# ${dn} - The Distinguished Name of the user logged in
+#
+# All attributes from the LDAP User record are available. For example, if a user
+# has an attribute "fullName" set to "John", "(fn=${fullName})" will be
+# translated to "(fn=John)".
+#
+# SINCE 1.0.0
+realm.ldap.groupMemberPattern = (&(objectClass=group)(member=${dn}))
+
+# Filter criteria for empty LDAP groups
+#
+# Query pattern to use when searching for an empty team. This may be any valid
+# LDAP query expression, including the standard (&) and (|) operators.
+#
+# default: (&(objectClass=group)(!(member=*)))
+# SINCE 1.4.0
+realm.ldap.groupEmptyMemberPattern = (&(objectClass=group)(!(member=*)))
+
+# LDAP users or groups that should be given administrator privileges.
+#
+# Teams are specified with a leading '@' character. Groups with spaces in the
+# name can be entered as "@team name". This setting only applies when using
+# LDAP to maintain team memberships.
+#
+# e.g. realm.ldap.admins = john @git_admins "@git admins"
+#
+# SPACE-DELIMITED
+# SINCE 1.0.0
+realm.ldap.admins = @Git_Admins
+
+# Attribute(s) on the USER record that indicate their display (or full) name.
+# Leave blank for no mapping available in LDAP.
+#
+# This may be a single attribute, or a string of multiple attributes. Examples:
+# displayName - Uses the attribute 'displayName' on the user record
+# ${personalTitle}. ${givenName} ${surname} - Will concatenate the 3
+# attributes together, with a '.' after personalTitle
+#
+# SINCE 1.0.0
+realm.ldap.displayName = displayName
+
+# Attribute(s) on the USER record that indicate their email address.
+# Leave blank for no mapping available in LDAP.
+#
+# This may be a single attribute, or a string of multiple attributes. Examples:
+# email - Uses the attribute 'email' on the user record
+# ${givenName}.${surname}@gitblit.com -Will concatenate the 2 attributes
+# together with a '.' and '@' creating something like first.last@gitblit.com
+#
+# SINCE 1.0.0
+realm.ldap.email = email
+
+# Attribute on the USER record that indicate their username to be used in gitblit
+# when synchronizing users from LDAP
+# if blank, Gitblit will use uid
+# For MS Active Directory this may be sAMAccountName
+#
+# SINCE 1.0.0
+realm.ldap.uid = uid
+
+# Defines whether to synchronize all LDAP users and teams into the user service
+#
+# Valid values: true, false
+# If left blank, false is assumed
+#
+# SINCE 1.4.0
+realm.ldap.synchronize = false
+
+# Defines the period to be used when synchronizing users and teams from ldap.
+#
+# Must be of the form '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'
+
+# default: 5 MINUTES
+#
+# RESTART REQUIRED
+# SINCE 1.4.0
+realm.ldap.syncPeriod = 5 MINUTES
+
+# Defines whether to delete non-existent LDAP users from the user service
+# during synchronization. depends on realm.ldap.synchronize = true
+#
+# Valid values: true, false
+# If left blank, true is assumed
+#
+# SINCE 1.4.0
+realm.ldap.removeDeletedUsers = true
+
+# URL of the Redmine.
+#
+# SINCE 1.2.0
+realm.redmine.url = http://example.com/redmine
+
+#
+# Gitblit GO Server Settings
+# The following settings only affect the integrated GO variant.
+#
+
+# The temporary folder to decompress the embedded gitblit webapp.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+# BASEFOLDER
+server.tempFolder = ${baseFolder}/temp
+
+# Specify the maximum number of concurrent http/https Jetty worker
+# threads to allow. This setting does not affect other threaded
+# daemons and components of Gitblit.
+#
+# SINCE 1.3.0
+# RESTART REQUIRED
+server.threadPoolSize = 50
+
+# Context path for the GO application. You might want to change the context
+# path if running Gitblit behind a proxy layer such as mod_proxy.
+#
+# SINCE 0.7.0
+# RESTART REQUIRED
+server.contextPath = /
+
+# Standard http port to serve. <= 0 disables this connector.
+# On Unix/Linux systems, ports < 1024 require root permissions.
+# Recommended value: 80 or 8080
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.httpPort = 0
+
+# Secure/SSL https port to serve. <= 0 disables this connector.
+# On Unix/Linux systems, ports < 1024 require root permissions.
+# Recommended value: 443 or 8443
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.httpsPort = 8443
+
+# Automatically redirect http requests to the secure https connector.
+#
+# This setting requires that you have configured server.httpPort and server.httpsPort.
+# Unless you are on a private LAN where you trust all client connections, it is
+# recommended to use https for all communications.
+#
+# SINCE 1.4.0
+# RESTART REQUIRED
+server.redirectToHttpsPort = false
+
+# Specify the interface for Jetty to bind the standard connector.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.httpBindInterface =
+
+# Specify the interface for Jetty to bind the secure connector.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.httpsBindInterface =
+
+# Alias of certificate to use for https/SSL serving. If blank the first
+# certificate found in the keystore will be used.
+#
+# SINCE 1.2.0
+# RESTART REQUIRED
+server.certificateAlias = localhost
+
+# Password for SSL keystore.
+# Keystore password and certificate password must match.
+# This is provided for convenience, its probably more secure to set this value
+# using the --storePassword command line parameter.
+#
+# If you are using the official JRE or JDK from Oracle you may not have the
+# JCE Unlimited Strength Jurisdiction Policy files bundled with your JVM. Because
+# of this, your store/key password can not exceed 7 characters. If you require
+# longer passwords you may need to install the JCE Unlimited Strength Jurisdiction
+# Policy files from Oracle.
+#
+# http://www.oracle.com/technetwork/java/javase/downloads/index.html
+#
+# Gitblit and the Gitblit Certificate Authority will both indicate if Unlimited
+# Strength encryption is available.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.storePassword = gitblit
+
+# If serving over https (recommended) you might consider requiring clients to
+# authenticate with ssl certificates. If enabled, only https clients with the
+# a valid client certificate will be able to access Gitblit.
+#
+# If disabled, client certificate authentication is optional and will be tried
+# first before falling-back to form authentication or basic authentication.
+#
+# Requiring client certificates to access any of Gitblit may be too extreme,
+# consider this carefully.
+#
+# SINCE 1.2.0
+# RESTART REQUIRED
+server.requireClientCertificates = false
+
+# Port for shutdown monitor to listen on.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.shutdownPort = 8081
+
+#
+# Gitblit Filestore Settings
+#
+# The location to save the filestore blobs
+#
+# SINCE 1.7.0
+filestore.storageFolder = ${baseFolder}/lfs
+
+# Maximum allowable upload size
+# The default value, -1, disables upload limits.
+# Common unit suffixes of k, m, or g are supported.
+# SINCE 1.7.0
+filestore.maxUploadSize = -1
diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties
index f53cc11..a4202e0 100644
--- a/src/main/distrib/data/gitblit.properties
+++ b/src/main/distrib/data/gitblit.properties
@@ -1,1891 +1,21 @@
#
-# Gitblit Settings
+# GITBLIT.PROPERTIES
#
-
-# This settings file supports parameterization from the command-line for the
-# following command-line parameters:
-#
-# --baseFolder ${baseFolder} SINCE 1.2.1
-#
-# Settings that support ${baseFolder} parameter substitution are indicated with the
-# BASEFOLDER attribute. If the --baseFolder argument is unspecified, ${baseFolder}
-# and it's trailing / will be discarded from the setting value leaving a relative
-# path that is equivalent to pre-1.2.1 releases.
-#
-# e.g. "${baseFolder}/git" becomes "git", if --baseFolder is unspecified
-#
-# Git Servlet Settings
+# Define your custom settings in this file and/or include settings defined in
+# other properties files.
#
-# Base folder for repositories.
-# This folder may contain bare and non-bare repositories but Gitblit will only
-# allow you to push to bare repositories.
-# Use forward slashes even on Windows!!
-# e.g. c:/gitrepos
+# Include Gitblit's 'defaults.properties' within your configuration.
#
-# SINCE 0.5.0
-# RESTART REQUIRED
-# BASEFOLDER
-git.repositoriesFolder = ${baseFolder}/git
-
-# Build the available repository list at startup and cache this list for reuse.
-# This reduces disk io when presenting the repositories page, responding to rpcs,
-# etc, but it means that Gitblit will not automatically identify repositories
-# added or deleted by external tools.
+# NOTE: Gitblit will not automatically reload "included" properties. Gitblit
+# only watches the 'gitblit.properties' file for modifications.
#
-# For this case you can use curl, wget, etc to issue an rpc request to clear the
-# cache (e.g. https://localhost/rpc?req=CLEAR_REPOSITORY_CACHE)
+# Paths may be relative to the ${baseFolder} or they may be absolute.
#
-# SINCE 1.1.0
-git.cacheRepositoryList = true
+# COMMA-DELIMITED
+# SINCE 1.7.0
+include = defaults.properties
-# Search the repositories folder subfolders for other repositories.
-# Repositories MAY NOT be nested (i.e. one repository within another)
-# but they may be grouped together in subfolders.
-# e.g. c:/gitrepos/libraries/mylibrary.git
-# c:/gitrepos/libraries/myotherlibrary.git
#
-# SINCE 0.5.0
-git.searchRepositoriesSubfolders = true
-
-# Maximum number of folders to recurse into when searching for repositories.
-# The default value, -1, disables depth limits.
-#
-# SINCE 1.1.0
-git.searchRecursionDepth = -1
-
-# List of regex exclusion patterns to match against folders found in
-# *git.repositoriesFolder*.
-# Use forward slashes even on Windows!!
-# e.g. test/jgit\.git
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.1.0
-git.searchExclusions =
-
-# List of regex url patterns for extracting a repository name when locating
-# submodules.
-# e.g. git.submoduleUrlPatterns = .*?://github.com/(.*) will extract
-# *gitblit/gitblit.git* from *git://github.com/gitblit/gitblit.git*
-# If no matches are found then the submodule repository name is assumed to be
-# whatever trails the last / character. (e.g. gitblit.git).
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.1.0
-git.submoduleUrlPatterns = .*?://github.com/(.*)
-
-# Specify the interface for Git Daemon to bind it's service.
-# You may specify an ip or an empty value to bind to all interfaces.
-# Specifying localhost will result in Gitblit ONLY listening to requests to
-# localhost.
-#
-# SINCE 1.3.0
-# RESTART REQUIRED
-git.daemonBindInterface =
-
-# port for serving the Git Daemon service. <= 0 disables this service.
-# On Unix/Linux systems, ports < 1024 require root permissions.
-# Recommended value: 9418
-#
-# SINCE 1.3.0
-# RESTART REQUIRED
-git.daemonPort = 9418
-
-# The port for serving the SSH service. <= 0 disables this service.
-# On Unix/Linux systems, ports < 1024 require root permissions.
-# Recommended value: 29418
-#
-# SINCE 1.5.0
-# RESTART REQUIRED
-git.sshPort = 29418
-
-# Specify the interface for the SSH daemon to bind its service.
-# You may specify an ip or an empty value to bind to all interfaces.
-# Specifying localhost will result in Gitblit ONLY listening to requests to
-# localhost.
-#
-# SINCE 1.5.0
-# RESTART REQUIRED
-git.sshBindInterface =
-
-# Specify the SSH key manager to use for retrieving, storing, and removing
-# SSH keys.
-#
-# Valid key managers are:
-# com.gitblit.transport.ssh.FileKeyManager
-#
-# SINCE 1.5.0
-git.sshKeysManager = com.gitblit.transport.ssh.FileKeyManager
-
-# Directory for storing user SSH keys when using the FileKeyManager.
-#
-# SINCE 1.5.0
-git.sshKeysFolder= ${baseFolder}/ssh
-
-# SSH backend NIO2|MINA.
-#
-# The Apache Mina project recommends using the NIO2 backend.
-#
-# SINCE 1.5.0
-git.sshBackend = NIO2
-
-# Number of threads used to parse a command line submitted by a client over SSH
-# for execution, create the internal data structures used by that command,
-# and schedule it for execution on another thread.
-#
-# SINCE 1.5.0
-git.sshCommandStartThreads = 2
-
-
-# Allow push/pull over http/https with JGit servlet.
-# If you do NOT want to allow Git clients to clone/push to Gitblit set this
-# to false. You might want to do this if you are only using ssh:// or git://.
-# If you set this false, consider changing the *web.otherUrls* setting to
-# indicate your clone/push urls.
-#
-# SINCE 0.5.0
-git.enableGitServlet = true
-
-# If you want to restrict all git servlet access to those with valid X509 client
-# certificates then set this value to true.
-#
-# SINCE 1.2.0
-git.requiresClientCertificate = false
-
-# Enforce date checks on client certificates to ensure that they are not being
-# used prematurely and that they have not expired.
-#
-# SINCE 1.2.0
-git.enforceCertificateValidity = true
-
-# List of OIDs to extract from a client certificate DN to map a certificate to
-# an account username.
-#
-# e.g. git.certificateUsernameOIDs = CN
-# e.g. git.certificateUsernameOIDs = FirstName LastName
-#
-# SPACE-DELIMITED
-# SINCE 1.2.0
-git.certificateUsernameOIDs = CN
-
-# Only serve/display bare repositories.
-# If there are non-bare repositories in git.repositoriesFolder and this setting
-# is true, they will be excluded from the ui.
-#
-# SINCE 0.9.0
-git.onlyAccessBareRepositories = false
-
-
-# Specify the list of acceptable transports for pushes.
-# If this setting is empty, all transports are acceptable.
-#
-# Valid choices are: GIT HTTP HTTPS SSH
-#
-# SINCE 1.5.0
-# SPACE-DELIMITED
-git.acceptedPushTransports = HTTP HTTPS SSH
-
-# Allow an authenticated user to create a destination repository on a push if
-# the repository does not already exist.
-#
-# Administrator accounts can create a repository in any project.
-# These repositories are created with the default access restriction and authorization
-# control values. The pushing account is set as the owner.
-#
-# Non-administrator accounts with the CREATE role may create personal repositories.
-# These repositories are created as VIEW restricted for NAMED users.
-# The pushing account is set as the owner.
-#
-# SINCE 1.2.0
-git.allowCreateOnPush = true
-
-# Global setting to control anonymous pushes.
-#
-# This setting allows/rejects anonymous pushes at the level of the receive pack.
-# This trumps all repository config settings. While anonymous pushes are convenient
-# on your own box when you are a lone developer, they are not recommended for
-# any multi-user installation where accountability is required. Since Gitblit
-# tracks pushes and user accounts, allowing anonymous pushes compromises that
-# information.
-#
-# SINCE 1.4.0
-git.allowAnonymousPushes = false
-
-# The default access restriction for new repositories.
-# Valid values are NONE, PUSH, CLONE, VIEW
-# NONE = anonymous view, clone, & push
-# PUSH = anonymous view & clone and authenticated push
-# CLONE = anonymous view, authenticated clone & push
-# VIEW = authenticated view, clone, & push
-#
-# SINCE 1.0.0
-git.defaultAccessRestriction = PUSH
-
-# The default authorization control for new repositories.
-# Valid values are AUTHENTICATED and NAMED
-# AUTHENTICATED = any authenticated user is granted restricted access
-# NAMED = only named users/teams are granted restricted access
-#
-# SINCE 1.1.0
-git.defaultAuthorizationControl = NAMED
-
-# The prefix for a users personal repository directory.
-#
-# Personal user repositories are created in this directory, named by the user name
-# prefixed with the userRepositoryPrefix. For eaxmple, a user 'john' would have his
-# personal repositories in the directory '~john'.
-#
-# Cannot be an empty string. Also, absolute paths are changed to relative paths by
-# removing the first directory separator.
-#
-# It is not recommended to change this value AFTER your user's have created
-# personal repositories because it will break all permissions, ownership, and
-# repository push/pull operations.
-#
-# RESTART REQUIRED
-# SINCE 1.4.0
-git.userRepositoryPrefix = ~
-
-# The default incremental push tag prefix. Tag prefix applied to a repository
-# that has automatic push tags enabled and does not specify a custom tag prefix.
-#
-# If incremental push tags are enabled, the tips of each branch in the push will
-# be tagged with an increasing revision integer.
-#
-# e.g. refs/tags/r2345 or refs/tags/rev_2345
-#
-# SINCE 1.3.0
-git.defaultIncrementalPushTagPrefix = r
-
-# Controls creating a repository as --shared on Unix servers.
-#
-# In an Unix environment where mixed access methods exist for shared repositories,
-# the repository should be created with 'git init --shared' to make sure that
-# it can be accessed e.g. via ssh (user git) and http (user www-data).
-#
-# Valid values are the values available for the '--shared' option. The the manual
-# page for 'git init' for more information on shared repositories.
-#
-# SINCE 1.4.0
-git.createRepositoriesShared = false
-
-# Directory for gitignore templates used during repository creation.
-#
-# SINCE 1.6.0
-git.gitignoreFolder = ${baseFolder}/gitignore
-
-# Enable JGit-based garbage collection. (!!EXPERIMENTAL!!)
-#
-# USE AT YOUR OWN RISK!
-#
-# If enabled, the garbage collection executor scans all repositories once a day
-# at the hour of your choosing. The GC executor will take each repository "offline",
-# one-at-a-time, to check if the repository satisfies it's GC trigger requirements.
-#
-# While the repository is offline it will be inaccessible from the web UI or from
-# any of the other services (git, rpc, rss, etc).
-#
-# Gitblit's GC Executor MAY NOT PLAY NICE with the other Git kids on the block,
-# especially on Windows systems, so if you are using other tools please coordinate
-# their usage with your GC Executor schedule or do not use this feature.
-#
-# The GC algorithm complex and the JGit team advises caution when using their
-# young implementation of GC.
-#
-# http://wiki.eclipse.org/EGit/New_and_Noteworthy/2.1#Garbage_Collector_and_Repository_Storage_Statistics
-#
-# EXPERIMENTAL
-# SINCE 1.2.0
-# RESTART REQUIRED
-git.enableGarbageCollection = false
-
-# Hour of the day for the GC Executor to scan repositories.
-# This value is in 24-hour time.
-#
-# SINCE 1.2.0
-git.garbageCollectionHour = 0
-
-# The default minimum total filesize of loose objects to trigger early garbage
-# collection.
-#
-# You may specify a custom threshold for a repository in the repository's settings.
-# Common unit suffixes of k, m, or g are supported.
-#
-# SINCE 1.2.0
-git.defaultGarbageCollectionThreshold = 500k
-
-# The default period, in days, between GCs for a repository. If the total filesize
-# of the loose object exceeds *git.garbageCollectionThreshold* or the repository's
-# custom threshold, this period will be short-circuited.
-#
-# e.g. if a repository collects 100KB of loose objects every day with a 500KB
-# threshold and a period of 7 days, it will take 5 days for the loose objects to
-# be collected, packed, and pruned.
-#
-# OR
-#
-# if a repository collects 10KB of loose objects every day with a 500KB threshold
-# and a period of 7 days, it will take the full 7 days for the loose objects to be
-# collected, packed, and pruned.
-#
-# You may specify a custom period for a repository in the repository's settings.
-#
-# The minimum value is 1 day since the GC Executor only runs once a day.
-#
-# SINCE 1.2.0
-git.defaultGarbageCollectionPeriod = 7
-
-# Gitblit can automatically fetch ref updates for a properly configured mirror
-# repository.
-#
-# Requirements:
-# 1. you must manually clone the repository using native git
-# git clone --mirror git://somewhere.com/myrepo.git
-# 2. the "origin" remote must be the mirror source
-# 3. the "origin" repository must be accessible without authentication OR the
-# credentials must be embedded in the origin url (not recommended)
-#
-# Notes:
-# 1. "origin" SSH urls are untested and not likely to work
-# 2. mirrors cloned while Gitblit is running are likely to require clearing the
-# gitblit cache (link on the repositories page of an administrator account)
-# 3. Gitblit will automatically repair any invalid fetch refspecs with a "//"
-# sequence.
-#
-# SINCE 1.4.0
-# RESTART REQUIRED
-git.enableMirroring = false
-
-# Specify the period between update checks for mirrored repositories.
-# The shortest period you may specify between mirror update checks is 5 mins.
-#
-# SINCE 1.4.0
-# RESTART REQUIRED
-git.mirrorPeriod = 30 mins
-
-# Number of bytes of a pack file to load into memory in a single read operation.
-# This is the "page size" of the JGit buffer cache, used for all pack access
-# operations. All disk IO occurs as single window reads. Setting this too large
-# may cause the process to load more data than is required; setting this too small
-# may increase the frequency of read() system calls.
-#
-# Default on JGit is 8 KiB on all platforms.
-#
-# Common unit suffixes of k, m, or g are supported.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.packedGitWindowSize = 8k
-
-# Maximum number of bytes to load and cache in memory from pack files. If JGit
-# needs to access more than this many bytes it will unload less frequently used
-# windows to reclaim memory space within the process. As this buffer must be shared
-# with the rest of the JVM heap, it should be a fraction of the total memory available.
-#
-# The JGit team recommends setting this value larger than the size of your biggest
-# repository. This ensures you can serve most requests from memory.
-#
-# Default on JGit is 10 MiB on all platforms.
-#
-# Common unit suffixes of k, m, or g are supported.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.packedGitLimit = 10m
-
-# Maximum number of bytes to reserve for caching base objects that multiple deltafied
-# objects reference. By storing the entire decompressed base object in a cache Git
-# is able to avoid unpacking and decompressing frequently used base objects multiple times.
-#
-# Default on JGit is 10 MiB on all platforms. You probably do not need to adjust
-# this value.
-#
-# Common unit suffixes of k, m, or g are supported.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.deltaBaseCacheLimit = 10m
-
-# Maximum number of pack files to have open at once. A pack file must be opened
-# in order for any of its data to be available in a cached window.
-#
-# If you increase this to a larger setting you may need to also adjust the ulimit
-# on file descriptors for the host JVM, as Gitblit needs additional file descriptors
-# available for network sockets and other repository data manipulation.
-#
-# Default on JGit is 128 file descriptors on all platforms.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.packedGitOpenFiles = 128
-
-# When true, JGit will use mmap() rather than malloc()+read() to load data from
-# pack files. The use of mmap can be problematic on some JVMs as the garbage
-# collector must deduce that a memory mapped segment is no longer in use before
-# a call to munmap() can be made by the JVM native code.
-#
-# In server applications (such as Gitblit) that need to access many pack files,
-# setting this to true risks artificially running out of virtual address space,
-# as the garbage collector cannot reclaim unused mapped spaces fast enough.
-#
-# Default on JGit is false. Although potentially slower, it yields much more
-# predictable behavior.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.packedGitMmap = false
-
-# Validate all received (pushed) objects are valid.
-#
-# SINCE 1.5.0
-git.checkReceivedObjects = true
-
-# Validate all referenced but not supplied objects are reachable.
-#
-# If enabled, Gitblit will verify that references to objects not contained
-# within the received pack are already reachable through at least one other
-# reference advertised to clients.
-#
-# This feature is useful when Gitblit doesn't trust the client to not provide a
-# forged SHA-1 reference to an object, in an attempt to access parts of the DAG
-# that they aren't allowed to see and which have been hidden from them via the
-# configured AdvertiseRefsHook or RefFilter.
-#
-# Enabling this feature may imply at least some, if not all, of the same functionality
-# performed by git.checkReceivedObjects.
-#
-# SINCE 1.5.0
-git.checkReferencedObjectsAreReachable = true
-
-# Set the maximum allowed Git object size.
-#
-# If an object is larger than the given size the pack-parsing will throw an exception
-# aborting the receive-pack operation. The default value, 0, disables maximum
-# object size checking.
-#
-# SINCE 1.5.0
-git.maxObjectSizeLimit = 0
-
-# Set the maximum allowed pack size.
-#
-# A pack exceeding this size will be rejected. The default value, -1, disables
-# maximum pack size checking.
-#
-# SINCE 1.5.0
-git.maxPackSizeLimit = -1
-
-# Use the Gitblit patch receive pack for processing contributions and tickets.
-# This allows the user to push a patch using the familiar Gerrit syntax:
-#
-# git push <remote> HEAD:refs/for/<targetBranch>
-#
-# NOTE:
-# This requires git.enableGitServlet = true AND it requires an authenticated
-# git transport connection (http/https) when pushing from a client.
-#
-# Valid services include:
-# com.gitblit.tickets.FileTicketService
-# com.gitblit.tickets.BranchTicketService
-# com.gitblit.tickets.RedisTicketService
-#
-# SINCE 1.4.0
-# RESTART REQUIRED
-tickets.service =
-
-# Globally enable or disable creation of new bug, enhancement, task, etc tickets
-# for all repositories.
-#
-# If false, no tickets can be created through the ui for any repositories.
-# If true, each repository can control if they allow new tickets to be created.
-#
-# NOTE:
-# If a repository is accepting patchsets, new proposal tickets can be created
-# regardless of this setting.
-#
-# SINCE 1.4.0
-tickets.acceptNewTickets = true
-
-# Globally enable or disable pushing patchsets to all repositories.
-#
-# If false, no patchsets will be accepted for any repositories.
-# If true, each repository can control if they accept new patchsets.
-#
-# NOTE:
-# If a repository is accepting patchsets, new proposal tickets can be created
-# regardless of the acceptNewTickets setting.
-#
-# SINCE 1.4.0
-tickets.acceptNewPatchsets = true
-
-# Default setting to control patchset merge through the web ui. If true, patchsets
-# must have an approval score to enable the merge button. This setting can be
-# overriden per-repository.
-#
-# SINCE 1.4.0
-tickets.requireApproval = false
-
-# The case-insensitive regular expression used to identify and close tickets on
-# push to the integration branch for commits that are NOT already referenced as
-# a patchset tip.
-#
-# SINCE 1.5.0
-tickets.closeOnPushCommitMessageRegex = (?:fixes|closes)[\\s-]+#?(\\d+)
-
-# Specify the location of the Lucene Ticket index
-#
-# SINCE 1.4.0
-# RESTART REQUIRED
-tickets.indexFolder = ${baseFolder}/tickets/lucene
-
-# Define the url for the Redis server.
-#
-# e.g. redis://localhost:6379
-# redis://:foobared@localhost:6379/2
-#
-# SINCE 1.4.0
-# RESTART REQUIRED
-tickets.redis.url =
-
-# The number of tickets to display on a page.
-#
-# SINCE 1.4.0
-tickets.perPage = 25
-
-# The folder where plugins are loaded from.
-#
-# SINCE 1.5.0
-# RESTART REQUIRED
-# BASEFOLDER
-plugins.folder = ${baseFolder}/plugins
-
-# The registry of available plugins.
-#
-# SINCE 1.5.0
-plugins.registry = http://plugins.gitblit.com/plugins.json
-
-# Number of threads used to handle miscellaneous tasks in the background.
-#
-# SINCE 1.6.0
-# RESTART REQUIRED
-execution.defaultThreadPoolSize = 1
-
-#
-# Groovy Integration
-#
-
-# Location of Groovy scripts to use for Pre and Post receive hooks.
-# Use forward slashes even on Windows!!
-# e.g. c:/groovy
-#
-# RESTART REQUIRED
-# SINCE 0.8.0
-# BASEFOLDER
-groovy.scriptsFolder = ${baseFolder}/groovy
-
-# Specify the directory Grape uses for downloading libraries.
-# http://groovy.codehaus.org/Grape
-#
-# RESTART REQUIRED
-# SINCE 1.0.0
-# BASEFOLDER
-groovy.grapeFolder = ${baseFolder}/groovy/grape
-
-# Scripts to execute on Pre-Receive.
-#
-# These scripts execute after an incoming push has been parsed and validated
-# but BEFORE the changes are applied to the repository. You might reject a
-# push in this script based on the repository and branch the push is attempting
-# to change.
-#
-# Script names are case-sensitive on case-sensitive file systems. You may omit
-# the traditional ".groovy" from this list if your file extension is ".groovy"
-#
-# NOTE:
-# These scripts are only executed when pushing to *Gitblit*, not to other Git
-# tooling you may be using. Also note that these scripts are shared between
-# repositories. These are NOT repository-specific scripts! Within the script
-# you may customize the control-flow for a specific repository by checking the
-# *repository* variable.
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 0.8.0
-groovy.preReceiveScripts =
-
-# Scripts to execute on Post-Receive.
-#
-# These scripts execute AFTER an incoming push has been applied to a repository.
-# You might trigger a continuous-integration build here or send a notification.
-#
-# Script names are case-sensitive on case-sensitive file systems. You may omit
-# the traditional ".groovy" from this list if your file extension is ".groovy"
-#
-# NOTE:
-# These scripts are only executed when pushing to *Gitblit*, not to other Git
-# tooling you may be using. Also note that these scripts are shared between
-# repositories. These are NOT repository-specific scripts! Within the script
-# you may customize the control-flow for a specific repository by checking the
-# *repository* variable.
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 0.8.0
-groovy.postReceiveScripts =
-
-# Repository custom fields for Groovy Hook mechanism
-#
-# List of key=label pairs of custom fields to prompt for in the Edit Repository
-# page. These keys are stored in the repository's git config file in the
-# section [gitblit "customFields"]. Key names are alphanumeric only. These
-# fields are intended to be used for the Groovy hook mechanism where a script
-# can adjust it's execution based on the custom fields stored in the repository
-# config.
-#
-# e.g. "commitMsgRegex=Commit Message Regular Expression" anotherProperty=Another
-#
-# SPACE-DELIMITED
-# SINCE 1.0.0
-groovy.customFields =
-
-#
-# Fanout Settings
-#
-
-# Fanout is a PubSub notification service that can be used by Sparkleshare
-# to eliminate repository change polling. The fanout service runs in a separate
-# thread on a separate port from the Gitblit http/https application.
-# This service is provided so that Sparkleshare may be used with Gitblit in
-# firewalled environments or where reliance on Sparkleshare's default notifications
-# server (notifications.sparkleshare.org) is unwanted.
-#
-# This service maintains an open socket connection from the client to the
-# Fanout PubSub service. This service may not work properly behind a proxy server.
-
-# Specify the interface for Fanout to bind it's service.
-# You may specify an ip or an empty value to bind to all interfaces.
-# Specifying localhost will result in Gitblit ONLY listening to requests to
-# localhost.
-#
-# SINCE 1.2.1
-# RESTART REQUIRED
-fanout.bindInterface =
-
-# port for serving the Fanout PubSub service. <= 0 disables this service.
-# On Unix/Linux systems, ports < 1024 require root permissions.
-# Recommended value: 17000
-#
-# SINCE 1.2.1
-# RESTART REQUIRED
-fanout.port = 0
-
-# Use Fanout NIO service. If false, a multi-threaded socket service will be used.
-# Be advised, the socket implementation spawns a thread per connection plus the
-# connection acceptor thread. The NIO implementation is completely single-threaded.
-#
-# SINCE 1.2.1
-# RESTART REQUIRED
-fanout.useNio = true
-
-# Concurrent connection limit. <= 0 disables concurrent connection throttling.
-# If > 0, only the specified number of concurrent connections will be allowed
-# and all other connections will be rejected.
-#
-# SINCE 1.2.1
-# RESTART REQUIRED
-fanout.connectionLimit = 0
-
-#
-# Authentication Settings
-#
-
-# Require authentication to see everything but the admin pages
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.authenticateViewPages = false
-
-# If web.authenticateViewPages=true you may optionally require a client-side
-# basic authentication prompt instead of the standard form-based login.
-#
-# SINCE 1.3.0
-web.enforceHttpBasicAuthentication = false
-
-# Require admin authentication for the admin functions and pages
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.authenticateAdminPages = true
-
-# Allow Gitblit to store a cookie in the user's browser for automatic
-# authentication. The cookie is generated by the user service.
-#
-# SINCE 0.5.0
-web.allowCookieAuthentication = true
-
-# Allow deletion of non-empty repositories. This is enforced for all delete vectors.
-#
-# SINCE 1.6.0
-web.allowDeletingNonEmptyRepositories = true
-
-# Setting to include personal repositories in the main repositories list.
-#
-# SINCE 1.6.0
-web.includePersonalRepositories = false
-
-# Config file for storing project metadata
-#
-# SINCE 1.2.0
-# BASEFOLDER
-web.projectsFile = ${baseFolder}/projects.conf
-
-# Either the full path to a user config file (users.conf)
-# OR a fully qualified class name that implements the IUserService interface.
-#
-# Any custom user service implementation must have a public default constructor.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-# BASEFOLDER
-realm.userService = ${baseFolder}/users.conf
-
-# Ordered list of external authentication providers which will be used if
-# authentication against the local user service fails.
-#
-# Valid providers are:
-#
-# htpasswd
-# ldap
-# pam
-# redmine
-# salesforce
-# windows
-
-# e.g. realm.authenticationProviders = htpasswd windows
-#
-# SINCE 1.4.0
-# RESTART REQUIRED
-# SPACE-DELIMITED
-realm.authenticationProviders =
-
-# How to store passwords.
-# Valid values are plain, md5, or combined-md5. md5 is the hash of password.
-# combined-md5 is the hash of username.toLowerCase()+password.
-# Default is md5.
-#
-# SINCE 0.5.0
-realm.passwordStorage = md5
-
-# Minimum valid length for a plain text password.
-# Default value is 5. Absolute minimum is 4.
-#
-# SINCE 0.5.0
-realm.minPasswordLength = 5
-
-#
-# Gitblit Web Settings
-#
-# If blank Gitblit is displayed.
-#
-# SINCE 0.5.0
-web.siteName =
-
-# The canonical url of your Gitblit server to be used in repository url generation,
-# RSS feeds, and all embedded links in email and plugin-based notifications.
-#
-# If you are running Gitblit on a non-standard http port (i.e. not 80 and not 443)
-# then you must specify that port in this url otherwise your generated urls will be
-# incorrect.
-#
-# The hostname of this url will be extracted for SSH and GIT protocol repository
-# url generation.
-#
-# e.g. web.canonicalUrl = https://dev.gitblit.com
-# web.canonicalUrl = https://dev.gitblit.com:8443
-#
-# SINCE 1.4.0
-web.canonicalUrl =
-
-# You may specify a different logo image for the header but it must be 120x45px.
-# If the specified file does not exist, the default Gitblit logo will be used.
-#
-# SINCE 1.3.0
-# BASEFOLDER
-web.headerLogo = ${baseFolder}/logo.png
-
-# You may specify a different link URL for the logo image anchor.
-# If blank the Gitblit main page URL is used.
-#
-# SINCE 1.3.0
-# BASEFOLDER
-web.rootLink =
-
-# You may specify a custom header background CSS color. If unspecified, the
-# default color will be used.
-#
-# e.g. web.headerBackgroundColor = #002060
-#
-# SINCE 1.3.0
-web.headerBackgroundColor =
-
-# You may specify a custom header foreground CSS color. If unspecified, the
-# default color will be used.
-#
-# e.g. web.headerForegroundColor = white
-#
-# SINCE 1.3.0
-web.headerForegroundColor =
-
-# You may specify a custom header foreground hover CSS color. If unspecified, the
-# default color will be used.
-#
-# e.g. web.headerHoverColor = white
-#
-# SINCE 1.3.0
-web.headerHoverColor =
-
-# You may specify a custom header border CSS color. If unspecified, the default
-# color will be used.
-#
-# e.g. web.headerBorderColor = #002060
-#
-# SINCE 1.3.0
-web.headerBorderColor =
-
-# You may specify a custom header border CSS color. If unspecified, the default
-# color will be used.
-#
-# e.g. web.headerBorderFocusColor = #ff9900
-#
-# SINCE 1.3.0
-web.headerBorderFocusColor =
-
-# If *web.authenticateAdminPages*=true, users with "admin" role can create
-# repositories, create users, and edit repository metadata.
-#
-# If *web.authenticateAdminPages*=false, any user can execute the aforementioned
-# functions.
-#
-# SINCE 0.5.0
-web.allowAdministration = true
-
-# Setting to disable rendering the top-level navigation header which includes
-# the login form, top-level links like dashboard, repositories, search, etc.
-# This setting is only useful if you plan to embed Gitblit within another page
-# or system.
-#
-# SINCE 1.4.0
-web.hideHeader = false
-
-# Allows rpc clients to list repositories and possibly manage or administer the
-# Gitblit server, if the authenticated account has administrator permissions.
-# See *web.enableRpcManagement* and *web.enableRpcAdministration*.
-#
-# SINCE 0.7.0
-web.enableRpcServlet = true
-
-# Allows rpc clients to manage repositories and users of the Gitblit instance,
-# if the authenticated account has administrator permissions.
-# Requires *web.enableRpcServlet=true*.
-#
-# SINCE 0.7.0
-web.enableRpcManagement = false
-
-# Allows rpc clients to control the server settings and monitor the health of this
-# this Gitblit instance, if the authenticated account has administrator permissions.
-# Requires *web.enableRpcServlet=true* and *web.enableRpcManagement*.
-#
-# SINCE 0.7.0
-web.enableRpcAdministration = false
-
-# Full path to a configurable robots.txt file. With this file you can control
-# what parts of your Gitblit server respectable robots are allowed to traverse.
-# http://googlewebmastercentral.blogspot.com/2008/06/improving-on-robots-exclusion-protocol.html
-#
-# SINCE 1.0.0
-# BASEFOLDER
-web.robots.txt = ${baseFolder}/robots.txt
-
-# The number of minutes to cache a page in the browser since the last request.
-# The default value is 0 minutes. A value <= 0 disables all page caching which
-# is the default behavior for Gitblit <= 1.3.0.
-#
-# SINCE 1.3.1
-web.pageCacheExpires = 0
-
-# If true, the web ui layout will respond and adapt to the browser's dimensions.
-# if false, the web ui will use a 940px fixed-width layout.
-# http://twitter.github.com/bootstrap/scaffolding.html#responsive
-#
-# SINCE 1.0.0
-web.useResponsiveLayout = true
-
-# Allow Gravatar images to be displayed in Gitblit pages.
-#
-# SINCE 0.8.0
-web.allowGravatar = true
-
-# Allow dynamic zip downloads.
-#
-# SINCE 0.5.0
-web.allowZipDownloads = true
-
-# If *web.allowZipDownloads=true* the following formats will be displayed for
-# download compressed archive links:
-#
-# zip = standard .zip
-# tar = standard tar format (preserves *nix permissions and symlinks)
-# gz = gz-compressed tar
-# xz = xz-compressed tar
-# bzip2 = bzip2-compressed tar
-#
-# SPACE-DELIMITED
-# SINCE 1.2.0
-web.compressedDownloads = zip gz
-
-# Allow optional Lucene integration. Lucene indexing is an opt-in feature.
-# A repository may specify branches to index with Lucene instead of using Git
-# commit traversal. There are scenarios where you may want to completely disable
-# Lucene indexing despite a repository specifying indexed branches. One such
-# scenario is on a resource-constrained federated Gitblit mirror.
-#
-# SINCE 0.9.0
-web.allowLuceneIndexing = true
-
-# Control the frequency of Lucene repository indexing.
-# The default setting is to check for updated refs every 2 mins.
-#
-# SINCE 1.6.1
-web.luceneFrequency = 2 mins
-
-# Allows an authenticated user to create forks of a repository
-#
-# set this to false if you want to disable all fork controls on the web site
-#
-web.allowForking = true
-
-# Controls the length of shortened commit hash ids
-#
-# SINCE 1.2.0
-web.shortCommitIdLength = 6
-
-# Use Clippy (Flash solution) to provide a copy-to-clipboard button.
-# If false, a button with a more primitive JavaScript-based prompt box will
-# offer a 3-step (click, ctrl+c, enter) copy-to-clipboard alternative.
-#
-# SINCE 0.8.0
-web.allowFlashCopyToClipboard = true
-
-# Default maximum number of commits that a repository may contribute to the
-# activity page, regardless of the selected duration. This setting may be valuable
-# for an extremely busy server. This value may also be configed per-repository
-# in Edit Repository. 0 disables this throttle.
-#
-# SINCE 1.2.0
-web.maxActivityCommits = 0
-
-# Default number of entries to include in RSS Syndication links
-#
-# SINCE 0.5.0
-web.syndicationEntries = 25
-
-# Show the size of each repository on the repositories page.
-# This requires recursive traversal of each repository folder. This may be
-# non-performant on some operating systems and/or filesystems.
-#
-# SINCE 0.5.2
-web.showRepositorySizes = true
-
-# List of custom regex expressions that can be displayed in the Filters menu
-# of the Repositories and Activity pages. Keep them very simple because you
-# are likely to run into encoding issues if they are too complex.
-#
-# Use !!! to separate the filters
-#
-# SINCE 0.8.0
-web.customFilters =
-
-# Show federation registrations (without token) and the current pull status
-# to non-administrator users.
-#
-# SINCE 0.6.0
-web.showFederationRegistrations = false
-
-# This is the message displayed when *web.authenticateViewPages=true*.
-# This can point to a file with Markdown content.
-# Specifying "gitblit" uses the internal login message.
-#
-# SINCE 0.7.0
-# BASEFOLDER
-web.loginMessage = gitblit
-
-# This is the message displayed above the repositories table.
-# This can point to a file with Markdown content.
-# Specifying "gitblit" uses the internal welcome message.
-#
-# SINCE 0.5.0
-# BASEFOLDER
-web.repositoriesMessage = gitblit
-
-# Ordered list of charsets/encodings to use when trying to display a blob.
-# If empty, UTF-8 and ISO-8859-1 are used. The server's default charset
-# is always appended to the encoding list. If all encodings fail to cleanly
-# decode the blob content, UTF-8 will be used with the standard malformed
-# input/unmappable character replacement strings.
-#
-# SPACE-DELIMITED
-# SINCE 1.0.0
-web.blobEncodings = UTF-8 ISO-8859-1
-
-# Manually set the default timezone to be used by Gitblit for display in the
-# web ui. This value is independent of the JVM timezone. Specifying a blank
-# value will default to the JVM timezone.
-# e.g. America/New_York, US/Pacific, UTC, Europe/Berlin
-#
-# SINCE 0.9.0
-# RESTART REQUIRED
-web.timezone =
-
-# Use the client timezone when formatting dates.
-# This uses AJAX to determine the browser's timezone and may require more
-# server overhead because a Wicket session is created. All Gitblit pages
-# attempt to be stateless, if possible.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.useClientTimezone = false
-
-# Time format
-# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
-#
-# SINCE 0.8.0
-web.timeFormat = HH:mm
-
-# Short date format
-# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
-#
-# SINCE 0.5.0
-web.datestampShortFormat = yyyy-MM-dd
-
-# Long date format
-#
-# SINCE 0.8.0
-web.datestampLongFormat = EEEE, MMMM d, yyyy
-
-# Long timestamp format
-# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
-#
-# SINCE 0.5.0
-web.datetimestampLongFormat = EEEE, MMMM d, yyyy HH:mm Z
-
-# Mount URL parameters
-# This setting controls if pretty or parameter URLs are used.
-# i.e.
-# if true:
-# http://localhost/commit/myrepo/abcdef
-# if false:
-# http://localhost/commit/?r=myrepo&h=abcdef
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.mountParameters = true
-
-# Some servlet containers (e.g. Tomcat >= 6.0.10) disallow '/' (%2F) encoding
-# in URLs as a security precaution for proxies. This setting tells Gitblit
-# to preemptively replace '/' with '*' or '!' for url string parameters.
-#
-# <https://issues.apache.org/jira/browse/WICKET-1303>
-# <http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10>
-# Add *-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true* to your
-# *CATALINA_OPTS* or to your JVM launch parameters
-#
-# SINCE 0.5.2
-web.forwardSlashCharacter = /
-
-# Show other URLs on the summary page for accessing your git repositories
-# Use spaces to separate urls.
-#
-# {0} is the token for the repository name
-# {1} is the token for the username
-#
-# The username is only practical if you have setup your other git serving
-# solutions accounts to have the same username as the Gitblit account.
-#
-# e.g.
-# web.otherUrls = ssh://localhost/git/{0} git://localhost/git/{0} https://{1}@localhost/r/{0}
-#
-# SPACE-DELIMITED
-# SINCE 0.5.0
-web.otherUrls =
-
-# Should app-specific clone links be displayed for SourceTree, SparkleShare, etc?
-#
-# SINCE 1.3.0
-web.allowAppCloneLinks = true
-
-# Choose how to present the repositories list.
-# grouped = group nested/subfolder repositories together (no sorting)
-# flat = flat list of repositories (sorting allowed)
-#
-# SINCE 0.5.0
-web.repositoryListType = grouped
-
-# If using a grouped repository list and there are repositories at the
-# root level of your repositories folder, you may specify the displayed
-# group name with this setting. This value is only used for web presentation.
-#
-# SINCE 0.5.0
-web.repositoryRootGroupName = main
-
-# Display the repository swatch color next to the repository name link in the
-# repositories list.
-#
-# SINCE 0.8.0
-web.repositoryListSwatches = true
-
-# Defines the default commit message renderer. This can be configured
-# per-repository.
-#
-# Valid values are: plain, markdown
-#
-# SINCE 1.4.0
-web.commitMessageRenderer = plain
-
-# Control if email addresses are shown in web ui
-#
-# SINCE 0.5.0
-web.showEmailAddresses = true
-
-# Shows a combobox in the page links header with commit, committer, and author
-# search selection. Default search is commit.
-#
-# SINCE 0.5.0
-web.showSearchTypeSelection = false
-
-# Controls display of activity graphs on the dashboard, activity, and summary
-# pages. Charting makes use of the external Google Charts API.
-#
-# SINCE 0.5.0
-web.generateActivityGraph = true
-
-# Displays the commits branch graph in the summary page and commits/log page.
-#
-# SINCE 1.4.0
-web.showBranchGraph = true
-
-# The default number of days to show on the activity page.
-# Value must exceed 0 else default of 7 is used
-#
-# SINCE 0.8.0
-web.activityDuration = 7
-
-# Choices for days of activity to display.
-#
-# SPACE-DELIMITED
-# SINCE 1.3.0
-web.activityDurationChoices = 1 3 7 14 21 28
-
-# Maximum number of days of activity that may be displayed on the activity page.
-#
-# SINCE 1.3.2
-web.activityDurationMaximum = 30
-
-# The number of days of commits to cache in memory for the dashboard, activity,
-# and project pages. A value of 0 will disable all caching and will parse commits
-# in each repository per-request. If the value > 0 these pages will try to fulfill
-# requests using the commit cache. If the request specifies a period which falls
-# outside the commit cache window, then the cache will be ignored and the request
-# will be fulfilled by brute-force parsing all relevant commits per-repository.
-#
-# Consider the values specified for *web.activityDurationChoices* when setting
-# the cache size AND consider adjusting the JVM -Xmx heap parameter appropriately.
-#
-# SINCE 1.3.0
-# RESTART REQUIRED
-web.activityCacheDays = 14
-
-# Case-insensitive list of authors to exclude from metrics. Useful for
-# eliminating bots.
-#
-# SPACE-DELIMITED
-# SINCE 1.3.0
-web.metricAuthorExclusions =
-
-# The number of commits to display on the summary page
-# Value must exceed 0 else default of 20 is used
-#
-# SINCE 0.5.0
-web.summaryCommitCount = 16
-
-# The number of tags/branches to display on the summary page.
-# -1 = all tags/branches
-# 0 = hide tags/branches
-# N = N tags/branches
-#
-# SINCE 0.5.0
-web.summaryRefsCount = 5
-
-# Show a README file, if available, on the summary page.
-#
-# SINCE 1.4.0
-web.summaryShowReadme = false
-
-# The number of items to show on a page before showing the first, prev, next
-# pagination links. A default of 50 is used for any invalid value.
-#
-# SINCE 0.5.0
-web.itemsPerPage = 50
-
-# The number of reflog changes to display on the overview page
-# Value must exceed 0 else default of 5 is used
-#
-# SINCE 1.3.0
-web.overviewReflogCount = 5
-
-# The number of reflog changes to show on a reflog page before show the first,
-# prev, next pagination links. A default of 10 is used for any invalid value.
-#
-# SINCE 1.3.0
-web.reflogChangesPerPage = 10
-
-# Specify the names of documents in the root of your repository to be displayed
-# in tabs on your repository docs page. If the name is not found in the root
-# then no tab is added. The order specified is the order displayed. Do not
-# specify a file extension as the aggregation of markup extensions + txt are used
-# in the search algorithm.
-#
-# SPACE-DELIMITED
-# SINCE 1.4.0
-web.documents = readme home index changelog contributing submitting_patches copying license notice authors
-
-# Registered file extensions to ignore during Lucene indexing
-#
-# SPACE-DELIMITED
-# SINCE 0.9.0
-web.luceneIgnoreExtensions = 7z arc arj bin bmp dll doc docx exe gif gz jar jpg lib lzh odg odf odt pdf ppt pptx png so swf tar xcf xls xlsx zip
-
-# Registered extensions for google-code-prettify
-#
-# SPACE-DELIMITED
-# SINCE 0.5.0
-web.prettyPrintExtensions = aea agc basic c cbm cl clj cpp cs css dart el erl erlang frm fs go groovy h hpp hs htm html java js latex lisp ll llvm lsp lua ml moxie mumps n nemerle pascal php pl pm prefs properties proto py r R rb rd Rd rkt s S scala scm sh Splus sql ss tcl tex vb vbs vhd vhdl wiki xml xq xquery yaml yml ymlapollo
-
-# Registered extensions for markdown transformation
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 0.5.0
-web.markdownExtensions = md mkd markdown MD MKD
-
-# Registered extensions for mediawiki transformation
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.4.0
-web.mediawikiExtensions = mw mediawiki
-
-# Registered extensions for twiki transformation
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.4.0
-web.twikiExtensions = twiki
-
-# Registered extensions for textile transformation
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.4.0
-web.textileExtensions = textile
-
-# Registered extensions for confluence transformation
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.4.0
-web.confluenceExtensions = confluence
-
-# Registered extensions for tracwiki transformation
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.4.0
-web.tracwikiExtensions = tracwiki
-
-# Image extensions
-#
-# SPACE-DELIMITED
-# SINCE 0.5.0
-web.imageExtensions = bmp jpg jpeg gif png ico
-
-# Registered extensions for binary blobs
-#
-# SPACE-DELIMITED
-# SINCE 0.5.0
-web.binaryExtensions = 7z arc arj bin dll doc docx exe gz jar lib lzh odg odf odt pdf ppt pptx so tar xls xlsx zip
-
-# Aggressive heap management will run the garbage collector on every generated
-# page. This slows down page generation a little but improves heap consumption.
-#
-# SINCE 0.5.0
-web.aggressiveHeapManagement = false
-
-# Run the webapp in debug mode
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.debugMode = false
-
-# Force a default locale for all users, ignoring the browser's settings.
-# An empty value allows Gitblit to use the translation preferred by the browser.
-#
-# Changing this value while the server is running will only affect new sessions.
-#
-# e.g. web.forceDefaultLocale = en
-#
-# SINCE 1.3.0
-web.forceDefaultLocale =
-
-# Enable/disable global regex substitutions (i.e. shared across repositories)
-#
-# SINCE 0.5.0
-# DEPRECATED 1.4.0 (migrate to bugtraq instead)
-regex.global = true
-
-# Example global regex substitutions
-# Use !!! to separate the search pattern and the replace pattern
-# searchpattern!!!replacepattern
-# SINCE 0.5.0
-
-# regex.global.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!Bug: <a href="http://somehost/bug/$3">$3</a>
-# SINCE 0.5.0
-
-# Example Gerrit links
-# regex.global.changeid = \\b(Change-Id:\\s*)([A-Za-z0-9]*)\\b!!!Change-Id: <a href="http://somehost/r/#q,$2,n,z">$2</a>
-# regex.global.reviewedon = \\b(Reviewed-on:\\s*)([A-Za-z0-9:/\\.]*)\\b!!!Reviewed-on: <a href="$2">$2</a>
-
-# Example per-repository regex substitutions overrides global
-# SINCE 0.5.0
-# regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!Bug: <a href="http://elsewhere/bug/$3">$3</a>
-
-#
-# Mail Settings
-# SINCE 0.6.0
-#
-# Mail settings are used to notify administrators of received federation proposals
-#
-
-# ip or hostname of smtp server
-#
-# SINCE 0.6.0
-mail.server =
-
-# port to use for smtp requests
-#
-# SINCE 0.6.0
-mail.port = 25
-
-# debug the mail executor
-#
-# SINCE 0.6.0
-mail.debug = false
-
-# use SMTPs flag
-mail.smtps = false
-
-# use STARTTLS flag
-#
-# SINCE 1.6.0
-mail.starttls = false
-
-# if your smtp server requires authentication, supply the credentials here
-#
-# SINCE 0.6.0
-mail.username =
-# SINCE 0.6.0
-mail.password =
-
-# from address for generated emails
-#
-# SINCE 0.6.0
-mail.fromAddress =
-
-# List of email addresses for the Gitblit administrators
-#
-# SPACE-DELIMITED
-# SINCE 0.6.0
-mail.adminAddresses =
-
-# List of email addresses for sending push email notifications.
-#
-# This key currently requires use of the sendemail.groovy hook script.
-# If you set sendemail.groovy in *groovy.postReceiveScripts* then email
-# notifications for all repositories (regardless of access restrictions!)
-# will be sent to these addresses.
-#
-# SPACE-DELIMITED
-# SINCE 0.8.0
-mail.mailingLists =
-
-#
-# Federation Settings
-# SINCE 0.6.0
-#
-# A Gitblit federation is a way to backup one Gitblit instance to another.
-#
-# *git.enableGitServlet* must be true to use this feature.
-
-# Your federation name is used for federation status acknowledgments. If it is
-# unset, and you elect to send a status acknowledgment, your Gitblit instance
-# will be identified by its hostname, if available, else your internal ip address.
-# The source Gitblit instance will also append your external IP address to your
-# identification to differentiate multiple pulling systems behind a single proxy.
-#
-# SINCE 0.6.0
-federation.name =
-
-# Specify the passphrase of this Gitblit instance.
-#
-# An unspecified (empty) passphrase disables processing federation requests.
-#
-# This value can be anything you want: an integer, a sentence, an haiku, etc.
-# Keep the value simple, though, to avoid Java properties file encoding issues.
-#
-# Changing your passphrase will break any registrations you have established with other
-# Gitblit instances.
-#
-# CASE-SENSITIVE
-# SINCE 0.6.0
-# RESTART REQUIRED *(only to enable or disable federation)*
-federation.passphrase =
-
-# Control whether or not this Gitblit instance can receive federation proposals
-# from another Gitblit instance. Registering a federated Gitblit is a manual
-# process. Proposals help to simplify that process by allowing a remote Gitblit
-# instance to send your Gitblit instance the federation pull data.
-#
-# SINCE 0.6.0
-federation.allowProposals = false
-
-# The destination folder for cached federation proposals.
-# Use forward slashes even on Windows!!
-#
-# SINCE 0.6.0
-# BASEFOLDER
-federation.proposalsFolder = ${baseFolder}/proposals
-
-# The default pull frequency if frequency is unspecified on a registration
-#
-# SINCE 0.6.0
-federation.defaultFrequency = 60 mins
-
-# Federation Sets are named groups of repositories. The Federation Sets are
-# available for selection in the repository settings page. You can assign a
-# repository to one or more sets and then distribute the token for the set.
-# This allows you to grant federation pull access to a subset of your available
-# repositories. Tokens for federation sets only grant repository pull access.
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 0.6.0
-federation.sets =
-
-# Federation pull registrations
-# Registrations are read once, at startup.
-#
-# RESTART REQUIRED
-#
-# frequency:
-# The shortest frequency allowed is every 5 minutes
-# Decimal frequency values are cast to integers
-# Frequency values may be specified in mins, hours, or days
-# Values that can not be parsed or are unspecified default to *federation.defaultFrequency*
-#
-# folder:
-# if unspecified, the folder is *git.repositoriesFolder*
-# if specified, the folder is relative to *git.repositoriesFolder*
-#
-# bare:
-# if true, each repository will be created as a *bare* repository and will not
-# have a working directory.
-#
-# if false, each repository will be created as a normal repository suitable
-# for local work.
-#
-# mirror:
-# if true, each repository HEAD is reset to *origin/master* after each pull.
-# The repository will be flagged *isFrozen* after the initial clone.
-#
-# if false, each repository HEAD will point to the FETCH_HEAD of the initial
-# clone from the origin until pushed to or otherwise manipulated.
-#
-# mergeAccounts:
-# if true, remote accounts and their permissions are merged into your
-# users.properties file
-#
-# notifyOnError:
-# if true and the mail configuration is properly set, administrators will be
-# notified by email of pull failures
-#
-# include and exclude:
-# Space-delimited list of repositories to include or exclude from pull
-# may be * wildcard to include or exclude all
-# may use fuzzy match (e.g. org.eclipse.*)
-
-#
-# (Nearly) Perfect Mirror example
-#
-
-#federation.example1.url = https://go.gitblit.com
-#federation.example1.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
-#federation.example1.frequency = 120 mins
-#federation.example1.folder =
-#federation.example1.bare = true
-#federation.example1.mirror = true
-#federation.example1.mergeAccounts = true
-
-#
-# Advanced Realm Settings
-#
-
-# Auto-creates user accounts based on the servlet container principal. This
-# assumes that your Gitblit install is a protected resource and your container's
-# authentication process intercepts all Gitblit requests.
-#
-# SINCE 1.3.0
-realm.container.autoCreateAccounts = false
-
-# Allow or prohibit Windows guest account logins
-#
-# SINCE 1.3.0
-realm.windows.allowGuests = false
-
-# Allow user accounts belonging to the BUILTIN\Administrators group to be
-# Gitblit administrators.
-#
-# SINCE 1.4.0
-realm.windows.permitBuiltInAdministrators = true
-
-# The default domain for authentication.
-#
-# If specified, this domain will be used for authentication UNLESS the supplied
-# login name manually specifies a domain (.e.g. mydomain\james or james@mydomain)
-#
-# If unspecified, the username must be specified in UPN format (name@domain).
-#
-# if "." (dot) is specified, ONLY the local account database will be used.
-#
-# SINCE 1.3.0
-realm.windows.defaultDomain =
-
-# The PAM service name for authentication.
-# default: system-auth
-#
-# SINCE 1.3.1
-realm.pam.serviceName = system-auth
-
-# The Apache htpasswd file that contains the users and passwords.
-# default: ${baseFolder}/htpasswd
-#
-# RESTART REQUIRED
-# BASEFOLDER
-# SINCE 1.3.2
-realm.htpasswd.userfile = ${baseFolder}/htpasswd
-
-# Restrict the Salesforce user to members of this org.
-# default: 0 (i.e. do not check the Org ID)
-#
-# SINCE 1.3.0
-realm.salesforce.orgId = 0
-
-# URL of the LDAP server.
-# To use encrypted transport, use either ldaps:// URL for SSL or ldap+tls:// to
-# send StartTLS command.
-#
-# SINCE 1.0.0
-realm.ldap.server = ldap://localhost
-
-# Login username for LDAP searches.
-# If this value is unspecified, anonymous LDAP login will be used.
-#
-# e.g. mydomain\\username
-#
-# SINCE 1.0.0
-realm.ldap.username = cn=Directory Manager
-
-# Login password for LDAP searches.
-#
-# SINCE 1.0.0
-realm.ldap.password = password
-
-# Bind pattern for Authentication.
-# Allow to directly authenticate an user without LDAP Searches.
-#
-# e.g. CN=${username},OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain
-#
-# SINCE 1.5.0
-realm.ldap.bindpattern =
-
-
-# Delegate team membership control to LDAP.
-#
-# If true, team user memberships will be specified by LDAP groups. This will
-# disable team selection in Edit User and user selection in Edit Team.
-#
-# If false, LDAP will only be used for authentication and Gitblit will maintain
-# team memberships with the *realm.ldap.backingUserService*.
-#
-# SINCE 1.0.0
-realm.ldap.maintainTeams = false
-
-# Root node for all LDAP users
-#
-# This is the root node from which subtree user searches will begin.
-# If blank, Gitblit will search ALL nodes.
-#
-# SINCE 1.0.0
-realm.ldap.accountBase = OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain
-
-# Filter criteria for LDAP users
-#
-# Query pattern to use when searching for a user account. This may be any valid
-# LDAP query expression, including the standard (&) and (|) operators.
-#
-# Variables may be injected via the ${variableName} syntax.
-# Recognized variables are:
-# ${username} - The text entered as the user name
-#
-# SINCE 1.0.0
-realm.ldap.accountPattern = (&(objectClass=person)(sAMAccountName=${username}))
-
-# Root node for all LDAP groups to be used as Gitblit Teams
-#
-# This is the root node from which subtree team searches will begin.
-# If blank, Gitblit will search ALL nodes.
-#
-# SINCE 1.0.0
-realm.ldap.groupBase = OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain
-
-# Filter criteria for LDAP groups
-#
-# Query pattern to use when searching for a team. This may be any valid
-# LDAP query expression, including the standard (&) and (|) operators.
-#
-# Variables may be injected via the ${variableName} syntax.
-# Recognized variables are:
-# ${username} - The text entered as the user name
-# ${dn} - The Distinguished Name of the user logged in
-#
-# All attributes from the LDAP User record are available. For example, if a user
-# has an attribute "fullName" set to "John", "(fn=${fullName})" will be
-# translated to "(fn=John)".
-#
-# SINCE 1.0.0
-realm.ldap.groupMemberPattern = (&(objectClass=group)(member=${dn}))
-
-# Filter criteria for empty LDAP groups
-#
-# Query pattern to use when searching for an empty team. This may be any valid
-# LDAP query expression, including the standard (&) and (|) operators.
-#
-# default: (&(objectClass=group)(!(member=*)))
-# SINCE 1.4.0
-realm.ldap.groupEmptyMemberPattern = (&(objectClass=group)(!(member=*)))
-
-# LDAP users or groups that should be given administrator privileges.
-#
-# Teams are specified with a leading '@' character. Groups with spaces in the
-# name can be entered as "@team name". This setting only applies when using
-# LDAP to maintain team memberships.
-#
-# e.g. realm.ldap.admins = john @git_admins "@git admins"
-#
-# SPACE-DELIMITED
-# SINCE 1.0.0
-realm.ldap.admins = @Git_Admins
-
-# Attribute(s) on the USER record that indicate their display (or full) name.
-# Leave blank for no mapping available in LDAP.
-#
-# This may be a single attribute, or a string of multiple attributes. Examples:
-# displayName - Uses the attribute 'displayName' on the user record
-# ${personalTitle}. ${givenName} ${surname} - Will concatenate the 3
-# attributes together, with a '.' after personalTitle
-#
-# SINCE 1.0.0
-realm.ldap.displayName = displayName
-
-# Attribute(s) on the USER record that indicate their email address.
-# Leave blank for no mapping available in LDAP.
-#
-# This may be a single attribute, or a string of multiple attributes. Examples:
-# email - Uses the attribute 'email' on the user record
-# ${givenName}.${surname}@gitblit.com -Will concatenate the 2 attributes
-# together with a '.' and '@' creating something like first.last@gitblit.com
-#
-# SINCE 1.0.0
-realm.ldap.email = email
-
-# Attribute on the USER record that indicate their username to be used in gitblit
-# when synchronizing users from LDAP
-# if blank, Gitblit will use uid
-# For MS Active Directory this may be sAMAccountName
-#
-# SINCE 1.0.0
-realm.ldap.uid = uid
-
-# Defines whether to synchronize all LDAP users and teams into the user service
-#
-# Valid values: true, false
-# If left blank, false is assumed
-#
-# SINCE 1.4.0
-realm.ldap.synchronize = false
-
-# Defines the period to be used when synchronizing users and teams from ldap.
-#
-# Must be of the form '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'
-
-# default: 5 MINUTES
-#
-# RESTART REQUIRED
-# SINCE 1.4.0
-realm.ldap.syncPeriod = 5 MINUTES
-
-# Defines whether to delete non-existent LDAP users from the user service
-# during synchronization. depends on realm.ldap.synchronize = true
-#
-# Valid values: true, false
-# If left blank, true is assumed
-#
-# SINCE 1.4.0
-realm.ldap.removeDeletedUsers = true
-
-# URL of the Redmine.
-#
-# SINCE 1.2.0
-realm.redmine.url = http://example.com/redmine
-
-#
-# Gitblit GO Server Settings
-# The following settings only affect the integrated GO variant.
-#
-
-# The temporary folder to decompress the embedded gitblit webapp.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-# BASEFOLDER
-server.tempFolder = ${baseFolder}/temp
-
-# Specify the maximum number of concurrent http/https Jetty worker
-# threads to allow. This setting does not affect other threaded
-# daemons and components of Gitblit.
-#
-# SINCE 1.3.0
-# RESTART REQUIRED
-server.threadPoolSize = 50
-
-# Context path for the GO application. You might want to change the context
-# path if running Gitblit behind a proxy layer such as mod_proxy.
-#
-# SINCE 0.7.0
-# RESTART REQUIRED
-server.contextPath = /
-
-# Standard http port to serve. <= 0 disables this connector.
-# On Unix/Linux systems, ports < 1024 require root permissions.
-# Recommended value: 80 or 8080
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.httpPort = 0
-
-# Secure/SSL https port to serve. <= 0 disables this connector.
-# On Unix/Linux systems, ports < 1024 require root permissions.
-# Recommended value: 443 or 8443
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.httpsPort = 8443
-
-# Automatically redirect http requests to the secure https connector.
-#
-# This setting requires that you have configured server.httpPort and server.httpsPort.
-# Unless you are on a private LAN where you trust all client connections, it is
-# recommended to use https for all communications.
-#
-# SINCE 1.4.0
-# RESTART REQUIRED
-server.redirectToHttpsPort = false
-
-# Specify the interface for Jetty to bind the standard connector.
-# You may specify an ip or an empty value to bind to all interfaces.
-# Specifying localhost will result in Gitblit ONLY listening to requests to
-# localhost.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.httpBindInterface =
-
-# Specify the interface for Jetty to bind the secure connector.
-# You may specify an ip or an empty value to bind to all interfaces.
-# Specifying localhost will result in Gitblit ONLY listening to requests to
-# localhost.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.httpsBindInterface =
-
-# Alias of certificate to use for https/SSL serving. If blank the first
-# certificate found in the keystore will be used.
-#
-# SINCE 1.2.0
-# RESTART REQUIRED
-server.certificateAlias = localhost
-
-# Password for SSL keystore.
-# Keystore password and certificate password must match.
-# This is provided for convenience, its probably more secure to set this value
-# using the --storePassword command line parameter.
-#
-# If you are using the official JRE or JDK from Oracle you may not have the
-# JCE Unlimited Strength Jurisdiction Policy files bundled with your JVM. Because
-# of this, your store/key password can not exceed 7 characters. If you require
-# longer passwords you may need to install the JCE Unlimited Strength Jurisdiction
-# Policy files from Oracle.
-#
-# http://www.oracle.com/technetwork/java/javase/downloads/index.html
-#
-# Gitblit and the Gitblit Certificate Authority will both indicate if Unlimited
-# Strength encryption is available.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.storePassword = gitblit
-
-# If serving over https (recommended) you might consider requiring clients to
-# authenticate with ssl certificates. If enabled, only https clients with the
-# a valid client certificate will be able to access Gitblit.
-#
-# If disabled, client certificate authentication is optional and will be tried
-# first before falling-back to form authentication or basic authentication.
-#
-# Requiring client certificates to access any of Gitblit may be too extreme,
-# consider this carefully.
-#
-# SINCE 1.2.0
-# RESTART REQUIRED
-server.requireClientCertificates = false
-
-# Port for shutdown monitor to listen on.
+# Define your overrides or custom settings below
#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.shutdownPort = 8081
diff --git a/src/main/distrib/data/groovy/jenkins.groovy b/src/main/distrib/data/groovy/jenkins.groovy
index 422b220..f029b26 100644
--- a/src/main/distrib/data/groovy/jenkins.groovy
+++ b/src/main/distrib/data/groovy/jenkins.groovy
@@ -69,8 +69,11 @@
// gitblit.properties or web.xml
def jenkinsUrl = gitblit.getString('groovy.jenkinsServer', 'http://yourserver/jenkins')
+// define the repository base url
+def jenkinsGitbaseurl = gitblit.getString('groovy.jenkinsGitbaseurl', "${url}/r")
+
// define the trigger url
-def triggerUrl = jenkinsUrl + "/git/notifyCommit?url=${url}/r/${repository.name}"
+def triggerUrl = jenkinsUrl + "/git/notifyCommit?url=" + jenkinsGitbaseurl + "/${repository.name}"
// trigger the build
new URL(triggerUrl).getContent()
diff --git a/src/main/distrib/linux/install-service-fedora.sh b/src/main/distrib/linux/install-service-fedora.sh
index 281fa40..4fb43c6 100755
--- a/src/main/distrib/linux/install-service-fedora.sh
+++ b/src/main/distrib/linux/install-service-fedora.sh
@@ -1,5 +1,5 @@
#!/bin/bash -x
-# This script installes gitblit on a system running under systemd.
+# This script installs Gitblit on a system running under systemd.
# The script assumes the server is running as user giblit
# First create a file with the default settings
@@ -8,7 +8,6 @@
GITBLIT_BASE_FOLDER=/opt/gitblit/data
GITBLIT_HTTP_PORT=0
GITBLIT_HTTPS_PORT=8443
-GITBLIT_LOG=/var/log/gitblit.log
EOF
# Create a systemd service file
cat > /tmp/gitblit.service << EOF
@@ -30,7 +29,5 @@
EOF
# Finally copy the files to the destination and register the systemd unit.
-sudo su -c "cp /tmp/gitblit.defaults /etc/sysconfig/gitblit && cp /tmp/gitblit.service /usr/lib/systemd/system/"
+sudo su -c "cp /tmp/gitblit.defaults /etc/sysconfig/gitblit && cp /tmp/gitblit.service /etc/systemd/system/"
sudo su -c "systemctl daemon-reload && systemctl enable gitblit.service && systemctl start gitblit.service"
-# Prepare the logfile
-sudo su -c "touch /var/log/gitblit.log && chown gitblit:gitblit /var/log/gitblit.log"
diff --git a/src/main/java/.gitignore b/src/main/java/.gitignore
index b978906..92bb4af 100644
--- a/src/main/java/.gitignore
+++ b/src/main/java/.gitignore
@@ -1 +1,2 @@
/clientapps.json
+/defaults.properties
diff --git a/src/main/java/WEB-INF/web.xml b/src/main/java/WEB-INF/web.xml
index 13f612e..db77215 100644
--- a/src/main/java/WEB-INF/web.xml
+++ b/src/main/java/WEB-INF/web.xml
@@ -1,361 +1,52 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<web-app version="2.4"
- xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
-
- <!-- The base folder is used to specify the root location of your Gitblit data.
-
- ${baseFolder}/gitblit.properties
- ${baseFolder}/users.conf
- ${baseFolder}/projects.conf
- ${baseFolder}/robots.txt
- ${baseFolder}/git
- ${baseFolder}/groovy
- ${baseFolder}/groovy/grape
- ${baseFolder}/proposals
-
- By default, this location is WEB-INF/data. It is recommended to set this
- path to a location outside your webapps folder that is writable by your
- servlet container. Gitblit will copy the WEB-INF/data files to that
- location for you when it restarts. This approach makes upgrading simpler.
- All you have to do is set this parameter for the new release and then
- review the defaults for any new settings. Settings are always versioned
- with a SINCE x.y.z attribute and also noted in the release changelog.
- -->
- <env-entry>
- <description>The base folder is used to specify the root location of your Gitblit data.</description>
- <env-entry-name>baseFolder</env-entry-name>
- <env-entry-type>java.lang.String</env-entry-type>
- <env-entry-value>${contextFolder}/WEB-INF/data</env-entry-value>
- </env-entry>
-
- <!-- Gitblit Displayname -->
- <display-name>Gitblit - @gb.version@</display-name>
-
-
-<!-- Gitblit Context Listener --><!-- STRIP
- <listener>
- <listener-class>com.gitblit.servlet.GitblitContext</listener-class>
- </listener>STRIP -->
-
-
- <!-- Git Servlet
- <url-pattern> MUST match:
- * GitFilter
- * com.gitblit.Constants.GIT_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>GitServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.GitServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>GitServlet</servlet-name>
- <url-pattern>/git/*</url-pattern>
- </servlet-mapping>
- <servlet-mapping>
- <servlet-name>GitServlet</servlet-name>
- <url-pattern>/r/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- SparkleShare Invite Servlet
- <url-pattern> MUST match:
- * com.gitblit.Constants.SPARKLESHARE_INVITE_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>SparkleShareInviteServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.SparkleShareInviteServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>SparkleShareInviteServlet</servlet-name>
- <url-pattern>/sparkleshare/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- Syndication Servlet
- <url-pattern> MUST match:
- * SyndicationFilter
- * com.gitblit.Constants.SYNDICATION_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>SyndicationServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.SyndicationServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>SyndicationServlet</servlet-name>
- <url-pattern>/feed/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- Zip Servlet
- <url-pattern> MUST match:
- * ZipServlet
- * com.gitblit.Constants.ZIP_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>ZipServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.DownloadZipServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>ZipServlet</servlet-name>
- <url-pattern>/zip/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- Federation Servlet
- <url-pattern> MUST match:
- * com.gitblit.Constants.FEDERATION_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>FederationServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.FederationServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>FederationServlet</servlet-name>
- <url-pattern>/federation/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- Rpc Servlet
- <url-pattern> MUST match:
- * com.gitblit.Constants.RPC_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>RpcServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.RpcServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>RpcServlet</servlet-name>
- <url-pattern>/rpc/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- Raw Servlet
- <url-pattern> MUST match:
- * RawFilter
- * com.gitblit.Constants.RAW_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>RawServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.RawServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>RawServlet</servlet-name>
- <url-pattern>/raw/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- Pages Servlet
- <url-pattern> MUST match:
- * PagesFilter
- * com.gitblit.Constants.PAGES_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>PagesServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.PagesServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>PagesServlet</servlet-name>
- <url-pattern>/pages/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- Logo Servlet
- <url-pattern> MUST match:
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>LogoServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.LogoServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>LogoServlet</servlet-name>
- <url-pattern>/logo.png</url-pattern>
- </servlet-mapping>
-
-
- <!-- PT Servlet
- <url-pattern> MUST match:
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>PtServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.PtServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>PtServlet</servlet-name>
- <url-pattern>/pt</url-pattern>
- </servlet-mapping>
-
-
- <!-- Branch Graph Servlet
- <url-pattern> MUST match:
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>BranchGraphServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.BranchGraphServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>BranchGraphServlet</servlet-name>
- <url-pattern>/graph/*</url-pattern>
- </servlet-mapping>
-
- <!-- Robots.txt Servlet
- <url-pattern> MUST match:
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>RobotsTxtServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.RobotsTxtServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>RobotsTxtServlet</servlet-name>
- <url-pattern>/robots.txt</url-pattern>
- </servlet-mapping>
-
- <filter>
- <filter-name>ProxyFilter</filter-name>
- <filter-class>com.gitblit.servlet.ProxyFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>ProxyFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
-
- <!-- Git Access Restriction Filter
- <url-pattern> MUST match:
- * GitServlet
- * com.gitblit.Constants.GIT_PATH
- * Wicket Filter ignorePaths parameter -->
- <filter>
- <filter-name>GitFilter</filter-name>
- <filter-class>com.gitblit.servlet.GitFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>GitFilter</filter-name>
- <url-pattern>/git/*</url-pattern>
- </filter-mapping>
- <filter-mapping>
- <filter-name>GitFilter</filter-name>
- <url-pattern>/r/*</url-pattern>
- </filter-mapping>
-
-
- <!-- Syndication Restriction Filter
- <url-pattern> MUST match:
- * SyndicationServlet
- * com.gitblit.Constants.SYNDICATION_PATH
- * Wicket Filter ignorePaths parameter -->
- <filter>
- <filter-name>SyndicationFilter</filter-name>
- <filter-class>com.gitblit.servlet.SyndicationFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>SyndicationFilter</filter-name>
- <url-pattern>/feed/*</url-pattern>
- </filter-mapping>
-
-
- <!-- Download Zip Restriction Filter
- <url-pattern> MUST match:
- * DownloadZipServlet
- * com.gitblit.Constants.ZIP_PATH
- * Wicket Filter ignorePaths parameter -->
- <filter>
- <filter-name>ZipFilter</filter-name>
- <filter-class>com.gitblit.servlet.DownloadZipFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>ZipFilter</filter-name>
- <url-pattern>/zip/*</url-pattern>
- </filter-mapping>
-
-
- <!-- Rpc Restriction Filter
- <url-pattern> MUST match:
- * RpcServlet
- * com.gitblit.Constants.RPC_PATH
- * Wicket Filter ignorePaths parameter -->
- <filter>
- <filter-name>RpcFilter</filter-name>
- <filter-class>com.gitblit.servlet.RpcFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>RpcFilter</filter-name>
- <url-pattern>/rpc/*</url-pattern>
- </filter-mapping>
-
-
- <!-- Branch Restriction Filter
- <url-pattern> MUST match:
- * RawServlet
- * com.gitblit.Constants.BRANCH_PATH
- * Wicket Filter ignorePaths parameter -->
- <filter>
- <filter-name>RawFilter</filter-name>
- <filter-class>com.gitblit.servlet.RawFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>RawFilter</filter-name>
- <url-pattern>/raw/*</url-pattern>
- </filter-mapping>
-
-
- <!-- Pages Restriction Filter
- <url-pattern> MUST match:
- * PagesServlet
- * com.gitblit.Constants.PAGES_PATH
- * Wicket Filter ignorePaths parameter -->
- <filter>
- <filter-name>PagesFilter</filter-name>
- <filter-class>com.gitblit.servlet.PagesFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>PagesFilter</filter-name>
- <url-pattern>/pages/*</url-pattern>
- </filter-mapping>
-
- <filter>
- <filter-name>EnforceAuthenticationFilter</filter-name>
- <filter-class>com.gitblit.servlet.EnforceAuthenticationFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>EnforceAuthenticationFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
-
-
- <!-- Wicket Filter -->
- <filter>
- <filter-name>wicketFilter</filter-name>
- <filter-class>
- com.gitblit.wicket.GitblitWicketFilter
- </filter-class>
- <init-param>
- <param-name>ignorePaths</param-name>
- <!-- Paths should match
- * SyndicationFilter <url-pattern>
- * SyndicationServlet <url-pattern>
- * com.gitblit.Constants.SYNDICATION_PATH
- * GitFilter <url-pattern>
- * GitServlet <url-pattern>
- * com.gitblit.Constants.GIT_PATH
- * SparkleshareInviteServlet <url-pattern>
- * com.gitblit.Constants.SPARKLESHARE_INVITE_PATH
- * Zipfilter <url-pattern>
- * ZipServlet <url-pattern>
- * com.gitblit.Constants.ZIP_PATH
- * FederationServlet <url-pattern>
- * RpcFilter <url-pattern>
- * RpcServlet <url-pattern>
- * RawFilter <url-pattern>
- * RawServlet <url-pattern>
- * PagesFilter <url-pattern>
- * PagesServlet <url-pattern>
- * com.gitblit.Constants.PAGES_PATH -->
- <param-value>r/,git/,pt,feed/,zip/,federation/,rpc/,raw/,pages/,robots.txt,logo.png,graph/,sparkleshare/</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>wicketFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
-
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.4"
+ xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_3_0.xsd">
+
+ <!-- The base folder is used to specify the root location of your Gitblit data.
+
+ ${baseFolder}/gitblit.properties
+ ${baseFolder}/users.conf
+ ${baseFolder}/projects.conf
+ ${baseFolder}/robots.txt
+ ${baseFolder}/git
+ ${baseFolder}/groovy
+ ${baseFolder}/groovy/grape
+ ${baseFolder}/proposals
+
+ By default, this location is WEB-INF/data. It is recommended to set this
+ path to a location outside your webapps folder that is writable by your
+ servlet container. Gitblit will copy the WEB-INF/data files to that
+ location for you when it restarts. This approach makes upgrading simpler.
+ All you have to do is set this parameter for the new release and then
+ review the defaults for any new settings. Settings are always versioned
+ with a SINCE x.y.z attribute and also noted in the release changelog.
+ -->
+ <env-entry>
+ <description>The base folder is used to specify the root location of your Gitblit data.</description>
+ <env-entry-name>baseFolder</env-entry-name>
+ <env-entry-type>java.lang.String</env-entry-type>
+ <env-entry-value>${contextFolder}/WEB-INF/data</env-entry-value>
+ </env-entry>
+
+ <!-- Gitblit Displayname -->
+ <display-name>Gitblit - @gb.version@</display-name>
+
+ <listener>
+ <listener-class>com.gitblit.servlet.GitblitContext</listener-class>
+ </listener>
+
+ <filter>
+ <filter-name>guiceFilter</filter-name>
+ <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>guiceFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+ <session-config>
+ <tracking-mode>COOKIE</tracking-mode>
+ </session-config>
</web-app>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/AvatarGenerator.java b/src/main/java/com/gitblit/AvatarGenerator.java
new file mode 100644
index 0000000..0415f92
--- /dev/null
+++ b/src/main/java/com/gitblit/AvatarGenerator.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2015 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit;
+
+public interface AvatarGenerator {
+
+ String getURL(String username, String emailaddress, boolean identicon, int width);
+
+}
diff --git a/src/main/java/com/gitblit/ConfigUserService.java b/src/main/java/com/gitblit/ConfigUserService.java
index d7d6c14..6d7230f 100644
--- a/src/main/java/com/gitblit/ConfigUserService.java
+++ b/src/main/java/com/gitblit/ConfigUserService.java
@@ -37,6 +37,7 @@
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
import com.gitblit.Constants.Transport;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.TeamModel;
@@ -734,22 +735,22 @@
// user roles
List<String> roles = new ArrayList<String>();
if (model.canAdmin) {
- roles.add(Constants.ADMIN_ROLE);
+ roles.add(Role.ADMIN.getRole());
}
if (model.canFork) {
- roles.add(Constants.FORK_ROLE);
+ roles.add(Role.FORK.getRole());
}
if (model.canCreate) {
- roles.add(Constants.CREATE_ROLE);
+ roles.add(Role.CREATE.getRole());
}
if (model.excludeFromFederation) {
- roles.add(Constants.NOT_FEDERATED_ROLE);
+ roles.add(Role.NOT_FEDERATED.getRole());
}
if (roles.size() == 0) {
// we do this to ensure that user record with no password
// is written. otherwise, StoredConfig optimizes that account
// away. :(
- roles.add(Constants.NO_ROLE);
+ roles.add(Role.NONE.getRole());
}
config.setStringList(USER, model.username, ROLE, roles);
@@ -778,18 +779,18 @@
// team roles
List<String> roles = new ArrayList<String>();
if (model.canAdmin) {
- roles.add(Constants.ADMIN_ROLE);
+ roles.add(Role.ADMIN.getRole());
}
if (model.canFork) {
- roles.add(Constants.FORK_ROLE);
+ roles.add(Role.FORK.getRole());
}
if (model.canCreate) {
- roles.add(Constants.CREATE_ROLE);
+ roles.add(Role.CREATE.getRole());
}
if (roles.size() == 0) {
// we do this to ensure that team record is written.
// Otherwise, StoredConfig might optimizes that record away.
- roles.add(Constants.NO_ROLE);
+ roles.add(Role.NONE.getRole());
}
config.setStringList(TEAM, model.name, ROLE, roles);
if (model.accountType != null) {
@@ -889,9 +890,6 @@
user.displayName = config.getString(USER, username, DISPLAYNAME);
user.emailAddress = config.getString(USER, username, EMAILADDRESS);
user.accountType = AccountType.fromString(config.getString(USER, username, ACCOUNTTYPE));
- if (Constants.EXTERNAL_ACCOUNT.equals(user.password) && user.accountType.isLocal()) {
- user.accountType = AccountType.EXTERNAL;
- }
user.disabled = config.getBoolean(USER, username, DISABLED, false);
user.organizationalUnit = config.getString(USER, username, ORGANIZATIONALUNIT);
user.organization = config.getString(USER, username, ORGANIZATION);
@@ -911,10 +909,10 @@
// user roles
Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
USER, username, ROLE)));
- user.canAdmin = roles.contains(Constants.ADMIN_ROLE);
- user.canFork = roles.contains(Constants.FORK_ROLE);
- user.canCreate = roles.contains(Constants.CREATE_ROLE);
- user.excludeFromFederation = roles.contains(Constants.NOT_FEDERATED_ROLE);
+ user.canAdmin = roles.contains(Role.ADMIN.getRole());
+ user.canFork = roles.contains(Role.FORK.getRole());
+ user.canCreate = roles.contains(Role.CREATE.getRole());
+ user.excludeFromFederation = roles.contains(Role.NOT_FEDERATED.getRole());
// repository memberships
if (!user.canAdmin) {
@@ -947,9 +945,9 @@
TeamModel team = new TeamModel(teamname);
Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
TEAM, teamname, ROLE)));
- team.canAdmin = roles.contains(Constants.ADMIN_ROLE);
- team.canFork = roles.contains(Constants.FORK_ROLE);
- team.canCreate = roles.contains(Constants.CREATE_ROLE);
+ team.canAdmin = roles.contains(Role.ADMIN.getRole());
+ team.canFork = roles.contains(Role.FORK.getRole());
+ team.canCreate = roles.contains(Role.CREATE.getRole());
team.accountType = AccountType.fromString(config.getString(TEAM, teamname, ACCOUNTTYPE));
if (!team.canAdmin) {
diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java
index fa8af25..6232552 100644
--- a/src/main/java/com/gitblit/Constants.java
+++ b/src/main/java/com/gitblit/Constants.java
@@ -36,14 +36,19 @@
public static final String FULL_NAME = "Gitblit - a pure Java Git solution";
+ @Deprecated
public static final String ADMIN_ROLE = "#admin";
+ @Deprecated
public static final String FORK_ROLE = "#fork";
+ @Deprecated
public static final String CREATE_ROLE = "#create";
+ @Deprecated
public static final String NOT_FEDERATED_ROLE = "#notfederated";
+ @Deprecated
public static final String NO_ROLE = "#none";
public static final String EXTERNAL_ACCOUNT = "#externalAccount";
@@ -55,6 +60,8 @@
public static final String R_PATH = "/r/";
public static final String GIT_PATH = "/git/";
+
+ public static final String REGEX_SHA256 = "[a-fA-F0-9]{64}";
public static final String ZIP_PATH = "/zip/";
@@ -70,6 +77,8 @@
public static final String RAW_PATH = "/raw/";
+ public static final String PT_PATH = "/pt";
+
public static final String BRANCH_GRAPH_PATH = "/graph/";
public static final String BORDER = "*****************************************************************";
@@ -85,6 +94,10 @@
public static final int LEN_SHORTLOG = 78;
public static final int LEN_SHORTLOG_REFS = 60;
+
+ public static final int LEN_FILESTORE_META_MIN = 125;
+
+ public static final int LEN_FILESTORE_META_MAX = 146;
public static final String DEFAULT_BRANCH = "default";
@@ -130,7 +143,11 @@
public static final String DEVELOP = "develop";
- public static final String AUTHENTICATION_TYPE = "authentication-type";
+ public static final String ATTRIB_AUTHTYPE = NAME + ":authentication-type";
+
+ public static final String ATTRIB_AUTHUSER = NAME + ":authenticated-user";
+
+ public static final String R_LFS = "info/lfs/";
public static String getVersion() {
String v = Constants.class.getPackage().getImplementationVersion();
@@ -148,6 +165,17 @@
return getManifestValue("build-date", "PENDING");
}
+ public static String getASCIIArt() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(" _____ _ _ _ _ _ _").append('\n');
+ sb.append(" | __ \\(_)| | | | | |(_)| |").append('\n');
+ sb.append(" | | \\/ _ | |_ | |__ | | _ | |_").append('\n');
+ sb.append(" | | __ | || __|| '_ \\ | || || __|").append(" ").append("http://gitblit.com").append('\n');
+ sb.append(" | |_\\ \\| || |_ | |_) || || || |_").append(" ").append("@gitblit").append('\n');
+ sb.append(" \\____/|_| \\__||_.__/ |_||_| \\__|").append(" ").append(Constants.getVersion()).append('\n');
+ return sb.toString();
+ }
+
private static String getManifestValue(String attrib, String defaultValue) {
Class<?> clazz = Constants.class;
String className = clazz.getSimpleName() + ".class";
@@ -167,6 +195,19 @@
return defaultValue;
}
+ public static enum Role {
+ NONE, ADMIN, CREATE, FORK, NOT_FEDERATED;
+
+ public String getRole() {
+ return "#" + name().replace("_", "").toLowerCase();
+ }
+
+ @Override
+ public String toString() {
+ return getRole();
+ }
+ }
+
/**
* Enumeration representing the four access restriction levels.
*/
@@ -537,7 +578,7 @@
}
public static enum AuthenticationType {
- PUBLIC_KEY, CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER;
+ PUBLIC_KEY, CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER, HTTPHEADER;
public boolean isStandard() {
return ordinal() <= COOKIE.ordinal();
@@ -545,7 +586,7 @@
}
public static enum AccountType {
- LOCAL, EXTERNAL, CONTAINER, LDAP, REDMINE, SALESFORCE, WINDOWS, PAM, HTPASSWD;
+ LOCAL, CONTAINER, LDAP, REDMINE, SALESFORCE, WINDOWS, PAM, HTPASSWD, HTTPHEADER;
public static AccountType fromString(String value) {
for (AccountType type : AccountType.values()) {
diff --git a/src/main/java/com/gitblit/DaggerModule.java b/src/main/java/com/gitblit/DaggerModule.java
deleted file mode 100644
index dd7e1b2..0000000
--- a/src/main/java/com/gitblit/DaggerModule.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright 2013 gitblit.com.
- *
- * 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.
- */
-package com.gitblit;
-
-import javax.inject.Singleton;
-
-import com.gitblit.manager.AuthenticationManager;
-import com.gitblit.manager.FederationManager;
-import com.gitblit.manager.IAuthenticationManager;
-import com.gitblit.manager.IFederationManager;
-import com.gitblit.manager.IGitblit;
-import com.gitblit.manager.INotificationManager;
-import com.gitblit.manager.IPluginManager;
-import com.gitblit.manager.IProjectManager;
-import com.gitblit.manager.IRepositoryManager;
-import com.gitblit.manager.IRuntimeManager;
-import com.gitblit.manager.IUserManager;
-import com.gitblit.manager.NotificationManager;
-import com.gitblit.manager.PluginManager;
-import com.gitblit.manager.ProjectManager;
-import com.gitblit.manager.RepositoryManager;
-import com.gitblit.manager.RuntimeManager;
-import com.gitblit.manager.UserManager;
-import com.gitblit.transport.ssh.FileKeyManager;
-import com.gitblit.transport.ssh.IPublicKeyManager;
-import com.gitblit.transport.ssh.MemoryKeyManager;
-import com.gitblit.transport.ssh.NullKeyManager;
-import com.gitblit.utils.JSoupXssFilter;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.XssFilter;
-import com.gitblit.wicket.GitBlitWebApp;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * DaggerModule references all injectable objects.
- *
- * @author James Moger
- *
- */
-@Module(
- library = true,
- injects = {
- IStoredSettings.class,
- XssFilter.class,
-
- // core managers
- IRuntimeManager.class,
- IPluginManager.class,
- INotificationManager.class,
- IUserManager.class,
- IAuthenticationManager.class,
- IPublicKeyManager.class,
- IRepositoryManager.class,
- IProjectManager.class,
- IFederationManager.class,
-
- // the monolithic manager
- IGitblit.class,
-
- // the Gitblit Wicket app
- GitBlitWebApp.class
- }
-)
-public class DaggerModule {
-
- @Provides @Singleton IStoredSettings provideSettings() {
- return new FileSettings();
- }
-
- @Provides @Singleton XssFilter provideXssFilter() {
- return new JSoupXssFilter();
- }
-
- @Provides @Singleton IRuntimeManager provideRuntimeManager(IStoredSettings settings, XssFilter xssFilter) {
- return new RuntimeManager(settings, xssFilter);
- }
-
- @Provides @Singleton IPluginManager providePluginManager(IRuntimeManager runtimeManager) {
- return new PluginManager(runtimeManager);
- }
-
- @Provides @Singleton INotificationManager provideNotificationManager(IStoredSettings settings) {
- return new NotificationManager(settings);
- }
-
- @Provides @Singleton IUserManager provideUserManager(
- IRuntimeManager runtimeManager,
- IPluginManager pluginManager) {
-
- return new UserManager(runtimeManager, pluginManager);
- }
-
- @Provides @Singleton IAuthenticationManager provideAuthenticationManager(
- IRuntimeManager runtimeManager,
- IUserManager userManager) {
-
- return new AuthenticationManager(
- runtimeManager,
- userManager);
- }
-
- @Provides @Singleton IPublicKeyManager providePublicKeyManager(
- IStoredSettings settings,
- IRuntimeManager runtimeManager) {
-
- String clazz = settings.getString(Keys.git.sshKeysManager, FileKeyManager.class.getName());
- if (StringUtils.isEmpty(clazz)) {
- clazz = FileKeyManager.class.getName();
- }
- if (FileKeyManager.class.getName().equals(clazz)) {
- return new FileKeyManager(runtimeManager);
- } else if (NullKeyManager.class.getName().equals(clazz)) {
- return new NullKeyManager();
- } else if (MemoryKeyManager.class.getName().equals(clazz)) {
- return new MemoryKeyManager();
- } else {
- try {
- Class<?> mgrClass = Class.forName(clazz);
- return (IPublicKeyManager) mgrClass.newInstance();
- } catch (Exception e) {
-
- }
- return null;
- }
- }
-
- @Provides @Singleton IRepositoryManager provideRepositoryManager(
- IRuntimeManager runtimeManager,
- IPluginManager pluginManager,
- IUserManager userManager) {
-
- return new RepositoryManager(
- runtimeManager,
- pluginManager,
- userManager);
- }
-
- @Provides @Singleton IProjectManager provideProjectManager(
- IRuntimeManager runtimeManager,
- IUserManager userManager,
- IRepositoryManager repositoryManager) {
-
- return new ProjectManager(
- runtimeManager,
- userManager,
- repositoryManager);
- }
-
- @Provides @Singleton IFederationManager provideFederationManager(
- IRuntimeManager runtimeManager,
- INotificationManager notificationManager,
- IRepositoryManager repositoryManager) {
-
- return new FederationManager(
- runtimeManager,
- notificationManager,
- repositoryManager);
- }
-
- @Provides @Singleton IGitblit provideGitblit(
- IRuntimeManager runtimeManager,
- IPluginManager pluginManager,
- INotificationManager notificationManager,
- IUserManager userManager,
- IAuthenticationManager authenticationManager,
- IPublicKeyManager publicKeyManager,
- IRepositoryManager repositoryManager,
- IProjectManager projectManager,
- IFederationManager federationManager) {
-
- return new GitBlit(
- runtimeManager,
- pluginManager,
- notificationManager,
- userManager,
- authenticationManager,
- publicKeyManager,
- repositoryManager,
- projectManager,
- federationManager);
- }
-
- @Provides @Singleton GitBlitWebApp provideWebApplication(
- IRuntimeManager runtimeManager,
- IPluginManager pluginManager,
- INotificationManager notificationManager,
- IUserManager userManager,
- IAuthenticationManager authenticationManager,
- IPublicKeyManager publicKeyManager,
- IRepositoryManager repositoryManager,
- IProjectManager projectManager,
- IFederationManager federationManager,
- IGitblit gitblit) {
-
- return new GitBlitWebApp(
- runtimeManager,
- pluginManager,
- notificationManager,
- userManager,
- authenticationManager,
- publicKeyManager,
- repositoryManager,
- projectManager,
- federationManager,
- gitblit);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/FederationClient.java b/src/main/java/com/gitblit/FederationClient.java
index 079355e..64ff017 100644
--- a/src/main/java/com/gitblit/FederationClient.java
+++ b/src/main/java/com/gitblit/FederationClient.java
@@ -1,192 +1,192 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * 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.
- */
-package com.gitblit;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import org.kohsuke.args4j.CmdLineException;
-import org.kohsuke.args4j.CmdLineParser;
-import org.kohsuke.args4j.Option;
-
-import com.gitblit.manager.FederationManager;
-import com.gitblit.manager.GitblitManager;
-import com.gitblit.manager.IGitblit;
-import com.gitblit.manager.INotificationManager;
-import com.gitblit.manager.RepositoryManager;
-import com.gitblit.manager.RuntimeManager;
-import com.gitblit.manager.UserManager;
-import com.gitblit.models.FederationModel;
-import com.gitblit.models.Mailing;
-import com.gitblit.service.FederationPullService;
-import com.gitblit.utils.FederationUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.XssFilter;
-import com.gitblit.utils.XssFilter.AllowXssFilter;
-
-/**
- * Command-line client to pull federated Gitblit repositories.
- *
- * @author James Moger
- *
- */
-public class FederationClient {
-
- public static void main(String[] args) {
- Params params = new Params();
- CmdLineParser parser = new CmdLineParser(params);
- try {
- parser.parseArgument(args);
- } catch (CmdLineException t) {
- usage(parser, t);
- }
-
- System.out.println("Gitblit Federation Client v" + Constants.getVersion() + " (" + Constants.getBuildDate() + ")");
-
- // command-line specified base folder
- File baseFolder = new File(System.getProperty("user.dir"));
- if (!StringUtils.isEmpty(params.baseFolder)) {
- baseFolder = new File(params.baseFolder);
- }
-
- File regFile = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, baseFolder, params.registrationsFile);
- FileSettings settings = new FileSettings(regFile.getAbsolutePath());
- List<FederationModel> registrations = new ArrayList<FederationModel>();
- if (StringUtils.isEmpty(params.url)) {
- registrations.addAll(FederationUtils.getFederationRegistrations(settings));
- } else {
- if (StringUtils.isEmpty(params.token)) {
- System.out.println("Must specify --token parameter!");
- System.exit(0);
- }
- FederationModel model = new FederationModel("Gitblit");
- model.url = params.url;
- model.token = params.token;
- model.mirror = params.mirror;
- model.bare = params.bare;
- model.folder = "";
- registrations.add(model);
- }
- if (registrations.size() == 0) {
- System.out.println("No Federation Registrations! Nothing to do.");
- System.exit(0);
- }
-
- // command-line specified repositories folder
- if (!StringUtils.isEmpty(params.repositoriesFolder)) {
- settings.overrideSetting(Keys.git.repositoriesFolder, new File(
- params.repositoriesFolder).getAbsolutePath());
- }
-
- // configure the Gitblit singleton for minimal, non-server operation
- XssFilter xssFilter = new AllowXssFilter();
- RuntimeManager runtime = new RuntimeManager(settings, xssFilter, baseFolder).start();
- NoopNotificationManager notifications = new NoopNotificationManager().start();
- UserManager users = new UserManager(runtime, null).start();
- RepositoryManager repositories = new RepositoryManager(runtime, null, users).start();
- FederationManager federation = new FederationManager(runtime, notifications, repositories).start();
- IGitblit gitblit = new GitblitManager(runtime, null, notifications, users, null, null, repositories, null, federation);
-
- FederationPullService puller = new FederationPullService(gitblit, federation.getFederationRegistrations()) {
- @Override
- public void reschedule(FederationModel registration) {
- // NOOP
- }
- };
- puller.run();
-
- System.out.println("Finished.");
- System.exit(0);
- }
-
- private static void usage(CmdLineParser parser, CmdLineException t) {
- System.out.println(Constants.getGitBlitVersion());
- System.out.println();
- if (t != null) {
- System.out.println(t.getMessage());
- System.out.println();
- }
-
- if (parser != null) {
- parser.printUsage(System.out);
- }
- System.exit(0);
- }
-
- /**
- * Parameters class for FederationClient.
- */
- private static class Params {
-
- @Option(name = "--registrations", usage = "Gitblit Federation Registrations File", metaVar = "FILE")
- public String registrationsFile = "${baseFolder}/federation.properties";
-
- @Option(name = "--url", usage = "URL of Gitblit instance to mirror from", metaVar = "URL")
- public String url;
-
- @Option(name = "--mirror", usage = "Mirror repositories")
- public boolean mirror;
-
- @Option(name = "--bare", usage = "Create bare repositories")
- public boolean bare;
-
- @Option(name = "--token", usage = "Federation Token", metaVar = "TOKEN")
- public String token;
-
- @Option(name = "--baseFolder", usage = "Base folder for received data", metaVar = "PATH")
- public String baseFolder;
-
- @Option(name = "--repositoriesFolder", usage = "Destination folder for cloned repositories", metaVar = "PATH")
- public String repositoriesFolder;
-
- }
-
- private static class NoopNotificationManager implements INotificationManager {
-
- @Override
- public NoopNotificationManager start() {
- return this;
- }
-
- @Override
- public NoopNotificationManager stop() {
- return this;
- }
-
- @Override
- public boolean isSendingMail() {
- return false;
- }
-
- @Override
- public void sendMailToAdministrators(String subject, String message) {
- }
-
- @Override
- public void sendMail(String subject, String message, Collection<String> toAddresses) {
- }
-
- @Override
- public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) {
- }
-
- @Override
- public void send(Mailing mailing) {
- }
- }
-}
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.Option;
+
+import com.gitblit.manager.FederationManager;
+import com.gitblit.manager.GitblitManager;
+import com.gitblit.manager.IGitblit;
+import com.gitblit.manager.INotificationManager;
+import com.gitblit.manager.RepositoryManager;
+import com.gitblit.manager.RuntimeManager;
+import com.gitblit.manager.UserManager;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.Mailing;
+import com.gitblit.service.FederationPullService;
+import com.gitblit.utils.FederationUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.XssFilter;
+import com.gitblit.utils.XssFilter.AllowXssFilter;
+
+/**
+ * Command-line client to pull federated Gitblit repositories.
+ *
+ * @author James Moger
+ *
+ */
+public class FederationClient {
+
+ public static void main(String[] args) {
+ Params params = new Params();
+ CmdLineParser parser = new CmdLineParser(params);
+ try {
+ parser.parseArgument(args);
+ } catch (CmdLineException t) {
+ usage(parser, t);
+ }
+
+ System.out.println("Gitblit Federation Client v" + Constants.getVersion() + " (" + Constants.getBuildDate() + ")");
+
+ // command-line specified base folder
+ File baseFolder = new File(System.getProperty("user.dir"));
+ if (!StringUtils.isEmpty(params.baseFolder)) {
+ baseFolder = new File(params.baseFolder);
+ }
+
+ File regFile = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, baseFolder, params.registrationsFile);
+ FileSettings settings = new FileSettings(regFile.getAbsolutePath());
+ List<FederationModel> registrations = new ArrayList<FederationModel>();
+ if (StringUtils.isEmpty(params.url)) {
+ registrations.addAll(FederationUtils.getFederationRegistrations(settings));
+ } else {
+ if (StringUtils.isEmpty(params.token)) {
+ System.out.println("Must specify --token parameter!");
+ System.exit(0);
+ }
+ FederationModel model = new FederationModel("Gitblit");
+ model.url = params.url;
+ model.token = params.token;
+ model.mirror = params.mirror;
+ model.bare = params.bare;
+ model.folder = "";
+ registrations.add(model);
+ }
+ if (registrations.size() == 0) {
+ System.out.println("No Federation Registrations! Nothing to do.");
+ System.exit(0);
+ }
+
+ // command-line specified repositories folder
+ if (!StringUtils.isEmpty(params.repositoriesFolder)) {
+ settings.overrideSetting(Keys.git.repositoriesFolder, new File(
+ params.repositoriesFolder).getAbsolutePath());
+ }
+
+ // configure the Gitblit singleton for minimal, non-server operation
+ XssFilter xssFilter = new AllowXssFilter();
+ RuntimeManager runtime = new RuntimeManager(settings, xssFilter, baseFolder).start();
+ NoopNotificationManager notifications = new NoopNotificationManager().start();
+ UserManager users = new UserManager(runtime, null).start();
+ RepositoryManager repositories = new RepositoryManager(runtime, null, users).start();
+ FederationManager federation = new FederationManager(runtime, notifications, repositories).start();
+ IGitblit gitblit = new GitblitManager(null, null, runtime, null, notifications, users, null, repositories, null, federation, null);
+
+ FederationPullService puller = new FederationPullService(gitblit, federation.getFederationRegistrations()) {
+ @Override
+ public void reschedule(FederationModel registration) {
+ // NOOP
+ }
+ };
+ puller.run();
+
+ System.out.println("Finished.");
+ System.exit(0);
+ }
+
+ private static void usage(CmdLineParser parser, CmdLineException t) {
+ System.out.println(Constants.getGitBlitVersion());
+ System.out.println();
+ if (t != null) {
+ System.out.println(t.getMessage());
+ System.out.println();
+ }
+
+ if (parser != null) {
+ parser.printUsage(System.out);
+ }
+ System.exit(0);
+ }
+
+ /**
+ * Parameters class for FederationClient.
+ */
+ private static class Params {
+
+ @Option(name = "--registrations", usage = "Gitblit Federation Registrations File", metaVar = "FILE")
+ public String registrationsFile = "${baseFolder}/federation.properties";
+
+ @Option(name = "--url", usage = "URL of Gitblit instance to mirror from", metaVar = "URL")
+ public String url;
+
+ @Option(name = "--mirror", usage = "Mirror repositories")
+ public boolean mirror;
+
+ @Option(name = "--bare", usage = "Create bare repositories")
+ public boolean bare;
+
+ @Option(name = "--token", usage = "Federation Token", metaVar = "TOKEN")
+ public String token;
+
+ @Option(name = "--baseFolder", usage = "Base folder for received data", metaVar = "PATH")
+ public String baseFolder;
+
+ @Option(name = "--repositoriesFolder", usage = "Destination folder for cloned repositories", metaVar = "PATH")
+ public String repositoriesFolder;
+
+ }
+
+ private static class NoopNotificationManager implements INotificationManager {
+
+ @Override
+ public NoopNotificationManager start() {
+ return this;
+ }
+
+ @Override
+ public NoopNotificationManager stop() {
+ return this;
+ }
+
+ @Override
+ public boolean isSendingMail() {
+ return false;
+ }
+
+ @Override
+ public void sendMailToAdministrators(String subject, String message) {
+ }
+
+ @Override
+ public void sendMail(String subject, String message, Collection<String> toAddresses) {
+ }
+
+ @Override
+ public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) {
+ }
+
+ @Override
+ public void send(Mailing mailing) {
+ }
+ }
+}
diff --git a/src/main/java/com/gitblit/FileSettings.java b/src/main/java/com/gitblit/FileSettings.java
index 21a2043..3caf966 100644
--- a/src/main/java/com/gitblit/FileSettings.java
+++ b/src/main/java/com/gitblit/FileSettings.java
@@ -18,10 +18,13 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
import java.util.Map;
import java.util.Properties;
import com.gitblit.utils.FileUtils;
+import com.gitblit.utils.StringUtils;
/**
* Dynamically loads and reloads a properties file by keeping track of the last
@@ -77,10 +80,14 @@
if (propertiesFile != null && propertiesFile.exists() && (forceReload || (propertiesFile.lastModified() > lastModified))) {
FileInputStream is = null;
try {
+ logger.debug("loading {}", propertiesFile);
Properties props = new Properties();
is = new FileInputStream(propertiesFile);
props.load(is);
+ // ticket-110
+ props = readIncludes(props);
+
// load properties after we have successfully read file
properties.clear();
properties.putAll(props);
@@ -103,12 +110,68 @@
return properties;
}
+ /**
+ * Recursively read "include" properties files.
+ *
+ * @param properties
+ * @return
+ * @throws IOException
+ */
+ private Properties readIncludes(Properties properties) throws IOException {
+
+ Properties baseProperties = new Properties();
+
+ String include = (String) properties.remove("include");
+ if (!StringUtils.isEmpty(include)) {
+
+ // allow for multiples
+ List<String> names = StringUtils.getStringsFromValue(include, ",");
+ for (String name : names) {
+
+ if (StringUtils.isEmpty(name)) {
+ continue;
+ }
+
+ // try co-located
+ File file = new File(propertiesFile.getParentFile(), name.trim());
+ if (!file.exists()) {
+ // try absolute path
+ file = new File(name.trim());
+ }
+
+ if (!file.exists()) {
+ logger.warn("failed to locate {}", file);
+ continue;
+ }
+
+ // load properties
+ logger.debug("loading {}", file);
+ try (FileInputStream iis = new FileInputStream(file)) {
+ baseProperties.load(iis);
+ }
+
+ // read nested includes
+ baseProperties = readIncludes(baseProperties);
+
+ }
+
+ }
+
+ // includes are "default" properties, they must be set first and the
+ // props which specified the "includes" must override
+ Properties merged = new Properties();
+ merged.putAll(baseProperties);
+ merged.putAll(properties);
+
+ return merged;
+ }
+
@Override
public boolean saveSettings() {
String content = FileUtils.readContent(propertiesFile, "\n");
for (String key : removals) {
String regex = "(?m)^(" + regExEscape(key) + "\\s*+=\\s*+)"
- + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
+ + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
content = content.replaceAll(regex, "");
}
removals.clear();
@@ -128,7 +191,7 @@
String content = FileUtils.readContent(propertiesFile, "\n");
for (Map.Entry<String, String> setting:settings.entrySet()) {
String regex = "(?m)^(" + regExEscape(setting.getKey()) + "\\s*+=\\s*+)"
- + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
+ + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
String oldContent = content;
content = content.replaceAll(regex, setting.getKey() + " = " + setting.getValue());
if (content.equals(oldContent)) {
diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java
index f9d9be9..4e25d5c 100644
--- a/src/main/java/com/gitblit/GitBlit.java
+++ b/src/main/java/com/gitblit/GitBlit.java
@@ -15,471 +15,58 @@
*/
package com.gitblit;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-import javax.inject.Singleton;
-import javax.servlet.http.HttpServletRequest;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.Transport;
import com.gitblit.manager.GitblitManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IFederationManager;
-import com.gitblit.manager.IGitblit;
+import com.gitblit.manager.IFilestoreManager;
import com.gitblit.manager.INotificationManager;
import com.gitblit.manager.IPluginManager;
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IUserManager;
-import com.gitblit.manager.ServicesManager;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.RepositoryUrl;
-import com.gitblit.models.UserModel;
-import com.gitblit.tickets.BranchTicketService;
-import com.gitblit.tickets.FileTicketService;
import com.gitblit.tickets.ITicketService;
-import com.gitblit.tickets.NullTicketService;
-import com.gitblit.tickets.RedisTicketService;
import com.gitblit.transport.ssh.IPublicKeyManager;
-import com.gitblit.utils.StringUtils;
-
-import dagger.Module;
-import dagger.ObjectGraph;
-import dagger.Provides;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
/**
- * GitBlit is the aggregate manager for the Gitblit webapp. It provides all
- * management functions and also manages some long-running services.
+ * GitBlit is the aggregate manager for the Gitblit webapp. The parent class provides all
+ * functionality. This class exists to not break existing Groovy push hooks.
*
* @author James Moger
*
*/
+@Singleton
+@Deprecated
public class GitBlit extends GitblitManager {
- private final ObjectGraph injector;
-
- private final ServicesManager servicesManager;
-
- private ITicketService ticketService;
-
+ @Inject
public GitBlit(
+ Provider<IPublicKeyManager> publicKeyManagerProvider,
+ Provider<ITicketService> ticketServiceProvider,
IRuntimeManager runtimeManager,
IPluginManager pluginManager,
INotificationManager notificationManager,
IUserManager userManager,
IAuthenticationManager authenticationManager,
- IPublicKeyManager publicKeyManager,
IRepositoryManager repositoryManager,
IProjectManager projectManager,
- IFederationManager federationManager) {
+ IFederationManager federationManager,
+ IFilestoreManager filestoreManager) {
- super(runtimeManager,
+ super(
+ publicKeyManagerProvider,
+ ticketServiceProvider,
+ runtimeManager,
pluginManager,
notificationManager,
userManager,
authenticationManager,
- publicKeyManager,
repositoryManager,
projectManager,
- federationManager);
-
- this.injector = ObjectGraph.create(getModules());
-
- this.servicesManager = new ServicesManager(this);
- }
-
- @Override
- public GitBlit start() {
- super.start();
- logger.info("Starting services manager...");
- servicesManager.start();
- configureTicketService();
- return this;
- }
-
- @Override
- public GitBlit stop() {
- super.stop();
- servicesManager.stop();
- ticketService.stop();
- return this;
- }
-
- @Override
- public boolean isServingRepositories() {
- return servicesManager.isServingRepositories();
- }
-
- @Override
- public boolean isServingHTTP() {
- return servicesManager.isServingHTTP();
- }
-
- @Override
- public boolean isServingGIT() {
- return servicesManager.isServingGIT();
- }
-
- @Override
- public boolean isServingSSH() {
- return servicesManager.isServingSSH();
- }
-
- protected Object [] getModules() {
- return new Object [] { new GitBlitModule()};
- }
-
- protected boolean acceptPush(Transport byTransport) {
- if (byTransport == null) {
- logger.info("Unknown transport, push rejected!");
- return false;
- }
-
- Set<Transport> transports = new HashSet<Transport>();
- for (String value : getSettings().getStrings(Keys.git.acceptedPushTransports)) {
- Transport transport = Transport.fromString(value);
- if (transport == null) {
- logger.info(String.format("Ignoring unknown registered transport %s", value));
- continue;
- }
-
- transports.add(transport);
- }
-
- if (transports.isEmpty()) {
- // no transports are explicitly specified, all are acceptable
- return true;
- }
-
- // verify that the transport is permitted
- return transports.contains(byTransport);
- }
-
- /**
- * Returns a list of repository URLs and the user access permission.
- *
- * @param request
- * @param user
- * @param repository
- * @return a list of repository urls
- */
- @Override
- public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
- if (user == null) {
- user = UserModel.ANONYMOUS;
- }
- String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
-
- List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
-
- // http/https url
- if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
- AccessPermission permission = user.getRepositoryPermission(repository).permission;
- if (permission.exceeds(AccessPermission.NONE)) {
- Transport transport = Transport.fromString(request.getScheme());
- if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(transport)) {
- // downgrade the repo permission for this transport
- // because it is not an acceptable PUSH transport
- permission = AccessPermission.CLONE;
- }
- list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
- }
- }
-
- // ssh daemon url
- String sshDaemonUrl = servicesManager.getSshDaemonUrl(request, user, repository);
- if (!StringUtils.isEmpty(sshDaemonUrl)) {
- AccessPermission permission = user.getRepositoryPermission(repository).permission;
- if (permission.exceeds(AccessPermission.NONE)) {
- if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.SSH)) {
- // downgrade the repo permission for this transport
- // because it is not an acceptable PUSH transport
- permission = AccessPermission.CLONE;
- }
-
- list.add(new RepositoryUrl(sshDaemonUrl, permission));
- }
- }
-
- // git daemon url
- String gitDaemonUrl = servicesManager.getGitDaemonUrl(request, user, repository);
- if (!StringUtils.isEmpty(gitDaemonUrl)) {
- AccessPermission permission = servicesManager.getGitDaemonAccessPermission(user, repository);
- if (permission.exceeds(AccessPermission.NONE)) {
- if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.GIT)) {
- // downgrade the repo permission for this transport
- // because it is not an acceptable PUSH transport
- permission = AccessPermission.CLONE;
- }
- list.add(new RepositoryUrl(gitDaemonUrl, permission));
- }
- }
-
- // add all other urls
- // {0} = repository
- // {1} = username
- for (String url : settings.getStrings(Keys.web.otherUrls)) {
- if (url.contains("{1}")) {
- // external url requires username, only add url IF we have one
- if (!StringUtils.isEmpty(username)) {
- list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
- }
- } else {
- // external url does not require username
- list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
- }
- }
-
- // sort transports by highest permission and then by transport security
- Collections.sort(list, new Comparator<RepositoryUrl>() {
-
- @Override
- public int compare(RepositoryUrl o1, RepositoryUrl o2) {
- if (!o1.isExternal() && o2.isExternal()) {
- // prefer Gitblit over external
- return -1;
- } else if (o1.isExternal() && !o2.isExternal()) {
- // prefer Gitblit over external
- return 1;
- } else if (o1.isExternal() && o2.isExternal()) {
- // sort by Transport ordinal
- return o1.transport.compareTo(o2.transport);
- } else if (o1.permission.exceeds(o2.permission)) {
- // prefer highest permission
- return -1;
- } else if (o2.permission.exceeds(o1.permission)) {
- // prefer highest permission
- return 1;
- }
-
- // prefer more secure transports
- return o1.transport.compareTo(o2.transport);
- }
- });
-
- // consider the user's transport preference
- RepositoryUrl preferredUrl = null;
- Transport preferredTransport = user.getPreferences().getTransport();
- if (preferredTransport != null) {
- Iterator<RepositoryUrl> itr = list.iterator();
- while (itr.hasNext()) {
- RepositoryUrl url = itr.next();
- if (url.transport.equals(preferredTransport)) {
- itr.remove();
- preferredUrl = url;
- break;
- }
- }
- }
- if (preferredUrl != null) {
- list.add(0, preferredUrl);
- }
-
- return list;
- }
-
- /**
- * Detect renames and reindex as appropriate.
- */
- @Override
- public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
- boolean isCreate) throws GitBlitException {
- RepositoryModel oldModel = null;
- boolean isRename = !isCreate && !repositoryName.equalsIgnoreCase(repository.name);
- if (isRename) {
- oldModel = repositoryManager.getRepositoryModel(repositoryName);
- }
-
- super.updateRepositoryModel(repositoryName, repository, isCreate);
-
- if (isRename && ticketService != null) {
- ticketService.rename(oldModel, repository);
- }
- }
-
- /**
- * Delete the user and all associated public ssh keys.
- */
- @Override
- public boolean deleteUser(String username) {
- UserModel user = userManager.getUserModel(username);
- return deleteUserModel(user);
- }
-
- @Override
- public boolean deleteUserModel(UserModel model) {
- boolean success = userManager.deleteUserModel(model);
- if (success) {
- getPublicKeyManager().removeAllKeys(model.username);
- }
- return success;
- }
-
- /**
- * Delete the repository and all associated tickets.
- */
- @Override
- public boolean deleteRepository(String repositoryName) {
- RepositoryModel repository = repositoryManager.getRepositoryModel(repositoryName);
- return deleteRepositoryModel(repository);
- }
-
- @Override
- public boolean deleteRepositoryModel(RepositoryModel model) {
- boolean success = repositoryManager.deleteRepositoryModel(model);
- if (success && ticketService != null) {
- ticketService.deleteAll(model);
- }
- return success;
- }
-
- /**
- * Returns the configured ticket service.
- *
- * @return a ticket service
- */
- @Override
- public ITicketService getTicketService() {
- return ticketService;
- }
-
- protected void configureTicketService() {
- String clazz = settings.getString(Keys.tickets.service, NullTicketService.class.getName());
- if (StringUtils.isEmpty(clazz)) {
- clazz = NullTicketService.class.getName();
- }
- try {
- Class<? extends ITicketService> serviceClass = (Class<? extends ITicketService>) Class.forName(clazz);
- ticketService = injector.get(serviceClass).start();
- if (ticketService instanceof NullTicketService) {
- logger.warn("No ticket service configured.");
- } else if (ticketService.isReady()) {
- logger.info("{} is ready.", ticketService);
- } else {
- logger.warn("{} is disabled.", ticketService);
- }
- } catch (Exception e) {
- logger.error("failed to create ticket service " + clazz, e);
- ticketService = injector.get(NullTicketService.class).start();
- }
- }
-
- /**
- * A nested Dagger graph is used for constructor dependency injection of
- * complex classes.
- *
- * @author James Moger
- *
- */
- @Module(
- library = true,
- injects = {
- IStoredSettings.class,
-
- // core managers
- IRuntimeManager.class,
- IPluginManager.class,
- INotificationManager.class,
- IUserManager.class,
- IAuthenticationManager.class,
- IRepositoryManager.class,
- IProjectManager.class,
- IFederationManager.class,
-
- // the monolithic manager
- IGitblit.class,
-
- // ticket services
- NullTicketService.class,
- FileTicketService.class,
- BranchTicketService.class,
- RedisTicketService.class
- }
- )
- class GitBlitModule {
-
- @Provides @Singleton IStoredSettings provideSettings() {
- return settings;
- }
-
- @Provides @Singleton IRuntimeManager provideRuntimeManager() {
- return runtimeManager;
- }
-
- @Provides @Singleton IPluginManager providePluginManager() {
- return pluginManager;
- }
-
- @Provides @Singleton INotificationManager provideNotificationManager() {
- return notificationManager;
- }
-
- @Provides @Singleton IUserManager provideUserManager() {
- return userManager;
- }
-
- @Provides @Singleton IAuthenticationManager provideAuthenticationManager() {
- return authenticationManager;
- }
-
- @Provides @Singleton IRepositoryManager provideRepositoryManager() {
- return repositoryManager;
- }
-
- @Provides @Singleton IProjectManager provideProjectManager() {
- return projectManager;
- }
-
- @Provides @Singleton IFederationManager provideFederationManager() {
- return federationManager;
- }
-
- @Provides @Singleton IGitblit provideGitblit() {
- return GitBlit.this;
- }
-
- @Provides @Singleton NullTicketService provideNullTicketService() {
- return new NullTicketService(
- runtimeManager,
- pluginManager,
- notificationManager,
- userManager,
- repositoryManager);
- }
-
- @Provides @Singleton FileTicketService provideFileTicketService() {
- return new FileTicketService(
- runtimeManager,
- pluginManager,
- notificationManager,
- userManager,
- repositoryManager);
- }
-
- @Provides @Singleton BranchTicketService provideBranchTicketService() {
- return new BranchTicketService(
- runtimeManager,
- pluginManager,
- notificationManager,
- userManager,
- repositoryManager);
- }
-
- @Provides @Singleton RedisTicketService provideRedisTicketService() {
- return new RedisTicketService(
- runtimeManager,
- pluginManager,
- notificationManager,
- userManager,
- repositoryManager);
- }
+ federationManager,
+ filestoreManager);
}
}
diff --git a/src/main/java/com/gitblit/GitBlitServer.java b/src/main/java/com/gitblit/GitBlitServer.java
index c79b172..d56d9c0 100644
--- a/src/main/java/com/gitblit/GitBlitServer.java
+++ b/src/main/java/com/gitblit/GitBlitServer.java
@@ -217,22 +217,7 @@
}
logger = LoggerFactory.getLogger(GitBlitServer.class);
- logger.info(Constants.BORDER);
- logger.info(" _____ _ _ _ _ _ _");
- logger.info(" | __ \\(_)| | | | | |(_)| |");
- logger.info(" | | \\/ _ | |_ | |__ | | _ | |_");
- logger.info(" | | __ | || __|| '_ \\ | || || __|");
- logger.info(" | |_\\ \\| || |_ | |_) || || || |_");
- logger.info(" \\____/|_| \\__||_.__/ |_||_| \\__|");
- int spacing = (Constants.BORDER.length() - Constants.getGitBlitVersion().length()) / 2;
- StringBuilder sb = new StringBuilder();
- while (spacing > 0) {
- spacing--;
- sb.append(' ');
- }
- logger.info(sb.toString() + Constants.getGitBlitVersion());
- logger.info("");
- logger.info(Constants.BORDER);
+ logger.info("\n" + Constants.getASCIIArt());
System.setProperty("java.awt.headless", "true");
@@ -524,22 +509,25 @@
@Override
public void run() {
- logger.info("Shutdown Monitor listening on port " + socket.getLocalPort());
- Socket accept;
- try {
- accept = socket.accept();
- BufferedReader reader = new BufferedReader(new InputStreamReader(
- accept.getInputStream()));
- reader.readLine();
- logger.info(Constants.BORDER);
- logger.info("Stopping " + Constants.NAME);
- logger.info(Constants.BORDER);
- server.stop();
- server.setStopAtShutdown(false);
- accept.close();
- socket.close();
- } catch (Exception e) {
- logger.warn("Failed to shutdown Jetty", e);
+ // Only run if the socket was able to be created (not already in use, failed to bind, etc.)
+ if (null != socket) {
+ logger.info("Shutdown Monitor listening on port " + socket.getLocalPort());
+ Socket accept;
+ try {
+ accept = socket.accept();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(
+ accept.getInputStream()));
+ reader.readLine();
+ logger.info(Constants.BORDER);
+ logger.info("Stopping " + Constants.NAME);
+ logger.info(Constants.BORDER);
+ server.stop();
+ server.setStopAtShutdown(false);
+ accept.close();
+ socket.close();
+ } catch (Exception e) {
+ logger.warn("Failed to shutdown Jetty", e);
+ }
}
}
}
diff --git a/src/main/java/com/gitblit/GravatarGenerator.java b/src/main/java/com/gitblit/GravatarGenerator.java
new file mode 100644
index 0000000..1ba02e5
--- /dev/null
+++ b/src/main/java/com/gitblit/GravatarGenerator.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit;
+
+import com.gitblit.utils.ActivityUtils;
+import com.google.inject.Singleton;
+
+@Singleton
+public class GravatarGenerator implements AvatarGenerator {
+
+ @Override
+ public String getURL(String username, String emailaddress, boolean identicon, int width) {
+ String email = emailaddress == null ? username : emailaddress;
+ if (identicon) {
+ return ActivityUtils.getGravatarIdenticonUrl(email, width);
+ } else {
+ return ActivityUtils.getGravatarThumbnailUrl(email, width);
+ }
+ }
+
+}
diff --git a/src/main/java/com/gitblit/auth/AuthenticationProvider.java b/src/main/java/com/gitblit/auth/AuthenticationProvider.java
index f6511ba..0bfe235 100644
--- a/src/main/java/com/gitblit/auth/AuthenticationProvider.java
+++ b/src/main/java/com/gitblit/auth/AuthenticationProvider.java
@@ -18,10 +18,14 @@
import java.io.File;
import java.math.BigInteger;
+import javax.servlet.http.HttpServletRequest;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
+import com.gitblit.Constants.AuthenticationType;
import com.gitblit.IStoredSettings;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IUserManager;
@@ -72,6 +76,8 @@
return serviceName;
}
+ public abstract AuthenticationType getAuthenticationType();
+
protected void setCookie(UserModel user, char [] password) {
// create a user cookie
if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
@@ -115,14 +121,32 @@
public abstract void stop();
+ /**
+ * Used to handle requests for requests for pages requiring authentication.
+ * This allows authentication to occur based on the contents of the request
+ * itself.
+ *
+ * @param httpRequest
+ * @return
+ */
+ public abstract UserModel authenticate(HttpServletRequest httpRequest);
+
+ /**
+ * Used to authentication user/password credentials, both for login form
+ * and HTTP Basic authentication processing.
+ *
+ * @param username
+ * @param password
+ * @return
+ */
public abstract UserModel authenticate(String username, char[] password);
public abstract AccountType getAccountType();
/**
- * Does the user service support changes to credentials?
+ * Returns true if the users's credentials can be changed.
*
- * @return true or false
+ * @return true if the authentication provider supports credential changes
* @since 1.0.0
*/
public abstract boolean supportsCredentialChanges();
@@ -131,7 +155,7 @@
* Returns true if the user's display name can be changed.
*
* @param user
- * @return true if the user service supports display name changes
+ * @return true if the authentication provider supports display name changes
*/
public abstract boolean supportsDisplayNameChanges();
@@ -139,7 +163,7 @@
* Returns true if the user's email address can be changed.
*
* @param user
- * @return true if the user service supports email address changes
+ * @return true if the authentication provider supports email address changes
*/
public abstract boolean supportsEmailAddressChanges();
@@ -147,10 +171,28 @@
* Returns true if the user's team memberships can be changed.
*
* @param user
- * @return true if the user service supports team membership changes
+ * @return true if the authentication provider supports team membership changes
*/
public abstract boolean supportsTeamMembershipChanges();
+ /**
+ * Returns true if the user's role can be changed.
+ *
+ * @param user
+ * @param role
+ * @return true if the user's role can be changed
+ */
+ public abstract boolean supportsRoleChanges(UserModel user, Role role);
+
+ /**
+ * Returns true if the team's role can be changed.
+ *
+ * @param user
+ * @param role
+ * @return true if the team's role can be changed
+ */
+ public abstract boolean supportsRoleChanges(TeamModel team, Role role);
+
@Override
public String toString() {
return getServiceName() + " (" + getClass().getName() + ")";
@@ -161,6 +203,16 @@
super(serviceName);
}
+ @Override
+ public UserModel authenticate(HttpServletRequest httpRequest) {
+ return null;
+ }
+
+ @Override
+ public AuthenticationType getAuthenticationType() {
+ return AuthenticationType.CREDENTIALS;
+ }
+
@Override
public void stop() {
@@ -184,6 +236,11 @@
}
@Override
+ public UserModel authenticate(HttpServletRequest httpRequest) {
+ return null;
+ }
+
+ @Override
public UserModel authenticate(String username, char[] password) {
return null;
}
@@ -194,6 +251,11 @@
}
@Override
+ public AuthenticationType getAuthenticationType() {
+ return null;
+ }
+
+ @Override
public boolean supportsCredentialChanges() {
return true;
}
@@ -212,5 +274,16 @@
public boolean supportsTeamMembershipChanges() {
return true;
}
+
+ @Override
+ public boolean supportsRoleChanges(UserModel user, Role role) {
+ return true;
+ }
+
+ @Override
+ public boolean supportsRoleChanges(TeamModel team, Role role) {
+ return true;
+ }
+
}
}
diff --git a/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java b/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java
index 5ffb693..2cdabf6 100644
--- a/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java
@@ -32,8 +32,10 @@
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
+import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
@@ -124,6 +126,16 @@
return true;
}
+ @Override
+ public boolean supportsRoleChanges(UserModel user, Role role) {
+ return true;
+ }
+
+ @Override
+ public boolean supportsRoleChanges(TeamModel team, Role role) {
+ return true;
+ }
+
/**
* Authenticate a user based on a username and password.
*
diff --git a/src/main/java/com/gitblit/auth/HttpHeaderAuthProvider.java b/src/main/java/com/gitblit/auth/HttpHeaderAuthProvider.java
new file mode 100644
index 0000000..3a9c539
--- /dev/null
+++ b/src/main/java/com/gitblit/auth/HttpHeaderAuthProvider.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2015 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.auth;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.AuthenticationType;
+import com.gitblit.Constants.Role;
+import com.gitblit.Keys;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+
+public class HttpHeaderAuthProvider extends AuthenticationProvider {
+
+ protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+ protected String userHeaderName;
+ protected String teamHeaderName;
+ protected String teamHeaderSeparator;
+
+ public HttpHeaderAuthProvider() {
+ super("httpheader");
+ }
+
+ @Override
+ public void setup() {
+ // Load HTTP header configuration
+ userHeaderName = settings.getString(Keys.realm.httpheader.userheader, null);
+ teamHeaderName = settings.getString(Keys.realm.httpheader.teamheader, null);
+ teamHeaderSeparator = settings.getString(Keys.realm.httpheader.teamseparator, ",");
+
+ if (StringUtils.isEmpty(userHeaderName)) {
+ logger.warn("HTTP Header authentication is enabled, but no header is not defined in " + Keys.realm.httpheader.userheader);
+ }
+ }
+
+ @Override
+ public void stop() {}
+
+
+ @Override
+ public UserModel authenticate(HttpServletRequest httpRequest) {
+ // Try to authenticate using custom HTTP header if user header is defined
+ if (!StringUtils.isEmpty(userHeaderName)) {
+ String headerUserName = httpRequest.getHeader(userHeaderName);
+ if (!StringUtils.isEmpty(headerUserName) && !userManager.isInternalAccount(headerUserName)) {
+ // We have a user, try to load team names as well
+ Set<TeamModel> userTeams = new HashSet<>();
+ if (!StringUtils.isEmpty(teamHeaderName)) {
+ String headerTeamValue = httpRequest.getHeader(teamHeaderName);
+ if (!StringUtils.isEmpty(headerTeamValue)) {
+ String[] headerTeamNames = headerTeamValue.split(teamHeaderSeparator);
+ for (String teamName : headerTeamNames) {
+ teamName = teamName.trim();
+ if (!StringUtils.isEmpty(teamName)) {
+ TeamModel team = userManager.getTeamModel(teamName);
+ if (null == team) {
+ // Create teams here so they can marked with the correct AccountType
+ team = new TeamModel(teamName);
+ team.accountType = AccountType.HTTPHEADER;
+ updateTeam(team);
+ }
+ userTeams.add(team);
+ }
+ }
+ }
+ }
+
+ UserModel user = userManager.getUserModel(headerUserName);
+ if (user != null) {
+ // If team header is provided in request, reset all team memberships, even if resetting to empty set
+ if (!StringUtils.isEmpty(teamHeaderName)) {
+ user.teams.clear();
+ user.teams.addAll(userTeams);
+ }
+ updateUser(user);
+ return user;
+ } else if (settings.getBoolean(Keys.realm.httpheader.autoCreateAccounts, false)) {
+ // auto-create user from HTTP header
+ user = new UserModel(headerUserName.toLowerCase());
+ user.displayName = headerUserName;
+ user.password = Constants.EXTERNAL_ACCOUNT;
+ user.accountType = AccountType.HTTPHEADER;
+ user.teams.addAll(userTeams);
+ updateUser(user);
+ return user;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public UserModel authenticate(String username, char[] password){
+ // Username/password is not supported for HTTP header authentication
+ return null;
+ }
+
+ @Override
+ public AccountType getAccountType() {
+ return AccountType.HTTPHEADER;
+ }
+
+ @Override
+ public AuthenticationType getAuthenticationType() {
+ return AuthenticationType.HTTPHEADER;
+ }
+
+ @Override
+ public boolean supportsCredentialChanges() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsDisplayNameChanges() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsEmailAddressChanges() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsTeamMembershipChanges() {
+ return StringUtils.isEmpty(teamHeaderName);
+ }
+
+ @Override
+ public boolean supportsRoleChanges(UserModel user, Role role) {
+ return true;
+ }
+
+ @Override
+ public boolean supportsRoleChanges(TeamModel team, Role role) {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/auth/LdapAuthProvider.java b/src/main/java/com/gitblit/auth/LdapAuthProvider.java
index 5690073..cc772e7 100644
--- a/src/main/java/com/gitblit/auth/LdapAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/LdapAuthProvider.java
@@ -30,6 +30,7 @@
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
import com.gitblit.models.TeamModel;
@@ -272,7 +273,6 @@
return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.email, ""));
}
-
/**
* If the LDAP server will maintain team memberships then LdapUserService
* will not allow team membership changes. In this scenario all team
@@ -286,6 +286,32 @@
return !settings.getBoolean(Keys.realm.ldap.maintainTeams, false);
}
+ @Override
+ public boolean supportsRoleChanges(UserModel user, Role role) {
+ if (Role.ADMIN == role) {
+ if (!supportsTeamMembershipChanges()) {
+ List<String> admins = settings.getStrings(Keys.realm.ldap.admins);
+ if (admins.contains(user.username)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean supportsRoleChanges(TeamModel team, Role role) {
+ if (Role.ADMIN == role) {
+ if (!supportsTeamMembershipChanges()) {
+ List<String> admins = settings.getStrings(Keys.realm.ldap.admins);
+ if (admins.contains("@" + team.name)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
@Override
public AccountType getAccountType() {
return AccountType.LDAP;
@@ -591,7 +617,8 @@
if (ldapSyncService.isReady()) {
long ldapSyncPeriod = getSynchronizationPeriodInMilliseconds();
int delay = 1;
- logger.info("Ldap sync service will update users and groups every {} minutes.", ldapSyncPeriod);
+ logger.info("Ldap sync service will update users and groups every {} minutes.",
+ TimeUnit.MILLISECONDS.toMinutes(ldapSyncPeriod));
scheduledExecutorService.scheduleAtFixedRate(ldapSyncService, delay, ldapSyncPeriod, TimeUnit.MILLISECONDS);
} else {
logger.info("Ldap sync service is disabled.");
diff --git a/src/main/java/com/gitblit/auth/PAMAuthProvider.java b/src/main/java/com/gitblit/auth/PAMAuthProvider.java
index 5d441b8..46f4dd6 100644
--- a/src/main/java/com/gitblit/auth/PAMAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/PAMAuthProvider.java
@@ -23,8 +23,10 @@
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
+import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
/**
@@ -77,6 +79,16 @@
return true;
}
+ @Override
+ public boolean supportsRoleChanges(UserModel user, Role role) {
+ return true;
+ }
+
+ @Override
+ public boolean supportsRoleChanges(TeamModel team, Role role) {
+ return true;
+ }
+
@Override
public AccountType getAccountType() {
return AccountType.PAM;
diff --git a/src/main/java/com/gitblit/auth/RedmineAuthProvider.java b/src/main/java/com/gitblit/auth/RedmineAuthProvider.java
index ae4f28e..27cece2 100644
--- a/src/main/java/com/gitblit/auth/RedmineAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/RedmineAuthProvider.java
@@ -23,8 +23,10 @@
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
+import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ConnectionUtils;
import com.gitblit.utils.StringUtils;
@@ -77,6 +79,16 @@
return false;
}
+ @Override
+ public boolean supportsRoleChanges(UserModel user, Role role) {
+ return true;
+ }
+
+ @Override
+ public boolean supportsRoleChanges(TeamModel team, Role role) {
+ return true;
+ }
+
@Override
public AccountType getAccountType() {
return AccountType.REDMINE;
@@ -154,7 +166,7 @@
url = url.concat("/");
}
String apiUrl = url + "users/current.json";
-
+
HttpURLConnection http;
if (username == null) {
// apikey authentication
diff --git a/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java b/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java
index e4273ff..df033c2 100644
--- a/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java
@@ -2,8 +2,10 @@
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
+import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.sforce.soap.partner.Connector;
import com.sforce.soap.partner.GetUserInfoResult;
@@ -119,4 +121,15 @@
public boolean supportsTeamMembershipChanges() {
return true;
}
+
+ @Override
+ public boolean supportsRoleChanges(UserModel user, Role role) {
+ return true;
+ }
+
+ @Override
+ public boolean supportsRoleChanges(TeamModel team, Role role) {
+ return true;
+ }
+
}
diff --git a/src/main/java/com/gitblit/auth/WindowsAuthProvider.java b/src/main/java/com/gitblit/auth/WindowsAuthProvider.java
index ac15b28..aee5100 100644
--- a/src/main/java/com/gitblit/auth/WindowsAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/WindowsAuthProvider.java
@@ -26,8 +26,10 @@
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
+import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
import com.sun.jna.platform.win32.Win32Exception;
@@ -90,6 +92,16 @@
return true;
}
+ @Override
+ public boolean supportsRoleChanges(UserModel user, Role role) {
+ return true;
+ }
+
+ @Override
+ public boolean supportsRoleChanges(TeamModel team, Role role) {
+ return true;
+ }
+
@Override
public AccountType getAccountType() {
return AccountType.WINDOWS;
diff --git a/src/main/java/com/gitblit/client/GitblitClient.java b/src/main/java/com/gitblit/client/GitblitClient.java
index f5bba1a..aecf81f 100644
--- a/src/main/java/com/gitblit/client/GitblitClient.java
+++ b/src/main/java/com/gitblit/client/GitblitClient.java
@@ -33,10 +33,6 @@
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
-import com.gitblit.GitBlitException.ForbiddenException;
-import com.gitblit.GitBlitException.NotAllowedException;
-import com.gitblit.GitBlitException.UnauthorizedException;
-import com.gitblit.GitBlitException.UnknownRequestException;
import com.gitblit.Keys;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FeedEntryModel;
@@ -119,34 +115,19 @@
refreshRepositories();
refreshSubscribedFeeds(0);
- try {
- // credentials may not have administrator access
- // or server may have disabled rpc management
- refreshUsers();
- if (protocolVersion > 1) {
- refreshTeams();
- }
- allowManagement = true;
- } catch (UnauthorizedException e) {
- } catch (ForbiddenException e) {
- } catch (NotAllowedException e) {
- } catch (UnknownRequestException e) {
- } catch (IOException e) {
- e.printStackTrace();
+ // credentials may not have administrator access
+ // or server may have disabled rpc management
+ refreshUsers();
+ if (protocolVersion > 1) {
+ refreshTeams();
}
+ allowManagement = true;
- try {
- // credentials may not have administrator access
- // or server may have disabled rpc administration
- refreshStatus();
- allowAdministration = true;
- } catch (UnauthorizedException e) {
- } catch (ForbiddenException e) {
- } catch (NotAllowedException e) {
- } catch (UnknownRequestException e) {
- } catch (IOException e) {
- e.printStackTrace();
- }
+ // credentials may not have administrator access
+ // or server may have disabled rpc administration
+ refreshStatus();
+ allowAdministration = true;
+
}
public int getProtocolVersion() {
diff --git a/src/main/java/com/gitblit/dagger/DaggerContext.java b/src/main/java/com/gitblit/dagger/DaggerContext.java
deleted file mode 100644
index 0e6a3fc..0000000
--- a/src/main/java/com/gitblit/dagger/DaggerContext.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2013 gitblit.com.
- *
- * 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.
- */
-package com.gitblit.dagger;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import dagger.ObjectGraph;
-
-/**
- * Dagger servlet context listener is a context listener that uses Dagger to
- * instantiate and inject servlets, filters, and anything else you might want.
- *
- * @author James Moger
- *
- */
-public abstract class DaggerContext implements ServletContextListener {
-
- public static final String INJECTOR_NAME = ObjectGraph.class.getName();
-
- protected final Logger logger = LoggerFactory.getLogger(getClass());
-
- protected abstract Object [] getModules();
-
- protected abstract void destroyContext(ServletContext context);
-
- protected ObjectGraph getInjector(ServletContext context) {
- Object o = context.getAttribute(INJECTOR_NAME);
- if (o == null) {
- logger.debug("instantiating Dagger modules");
- Object [] modules = getModules();
- logger.debug("getting Dagger injector");
- try {
- o = ObjectGraph.create(modules);
- logger.debug("setting Dagger injector into {} attribute", INJECTOR_NAME);
- context.setAttribute(INJECTOR_NAME, o);
- } catch (Throwable t) {
- logger.error("an error occurred creating the Dagger injector", t);
- }
- }
- return (ObjectGraph) o;
- }
-
- @Override
- public final void contextDestroyed(ServletContextEvent contextEvent) {
- ServletContext context = contextEvent.getServletContext();
- context.removeAttribute(INJECTOR_NAME);
- destroyContext(context);
- }
-}
diff --git a/src/main/java/com/gitblit/dagger/DaggerFilter.java b/src/main/java/com/gitblit/dagger/DaggerFilter.java
deleted file mode 100644
index 01c07a4..0000000
--- a/src/main/java/com/gitblit/dagger/DaggerFilter.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2013 gitblit.com.
- *
- * 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.
- */
-package com.gitblit.dagger;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-
-import dagger.ObjectGraph;
-
-/**
- * Uses Dagger to manually inject dependencies into a servlet filter.
- * This class is useful for servlet containers that offer CDI and are
- * confused by Dagger.
- *
- * @author James Moger
- *
- */
-public abstract class DaggerFilter implements Filter {
-
- @Override
- public final void init(FilterConfig filterConfig) throws ServletException {
- ServletContext context = filterConfig.getServletContext();
- ObjectGraph objectGraph = (ObjectGraph) context.getAttribute(DaggerContext.INJECTOR_NAME);
- inject(objectGraph, filterConfig);
- }
-
- protected abstract void inject(ObjectGraph dagger, FilterConfig filterConfig) throws ServletException;
-
- @Override
- public void destroy() {
- }
-}
diff --git a/src/main/java/com/gitblit/dagger/DaggerServlet.java b/src/main/java/com/gitblit/dagger/DaggerServlet.java
deleted file mode 100644
index 88331a4..0000000
--- a/src/main/java/com/gitblit/dagger/DaggerServlet.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2013 gitblit.com.
- *
- * 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.
- */
-package com.gitblit.dagger;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-
-import dagger.ObjectGraph;
-
-/**
- * Uses Dagger to manually inject dependencies into a servlet.
- * This class is useful for servlet containers that offer CDI and are
- * confused by Dagger.
- *
- * @author James Moger
- *
- */
-public abstract class DaggerServlet extends HttpServlet {
-
- private static final long serialVersionUID = 1L;
-
- @Override
- public final void init() throws ServletException {
- ServletContext context = getServletContext();
- ObjectGraph objectGraph = (ObjectGraph) context.getAttribute(DaggerContext.INJECTOR_NAME);
- inject(objectGraph);
- }
-
- protected abstract void inject(ObjectGraph dagger);
-}
diff --git a/src/main/java/com/gitblit/dagger/DaggerWicketFilter.java b/src/main/java/com/gitblit/dagger/DaggerWicketFilter.java
deleted file mode 100644
index c2fd4d6..0000000
--- a/src/main/java/com/gitblit/dagger/DaggerWicketFilter.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2013 gitblit.com.
- *
- * 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.
- */
-package com.gitblit.dagger;
-
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-
-import org.apache.wicket.protocol.http.WicketFilter;
-
-import dagger.ObjectGraph;
-
-/**
- * Uses Dagger to manually inject dependencies into a Wicket filter.
- * This class is useful for servlet containers that offer CDI and are
- * confused by Dagger.
- *
- * @author James Moger
- *
- */
-public abstract class DaggerWicketFilter extends WicketFilter {
-
- @Override
- public final void init(FilterConfig filterConfig) throws ServletException {
- ServletContext context = filterConfig.getServletContext();
- ObjectGraph objectGraph = (ObjectGraph) context.getAttribute(DaggerContext.INJECTOR_NAME);
- inject(objectGraph);
- super.init(filterConfig);
- }
-
- protected abstract void inject(ObjectGraph dagger);
-}
diff --git a/src/main/java/com/gitblit/extensions/RepositoryLifeCycleListener.java b/src/main/java/com/gitblit/extensions/RepositoryLifeCycleListener.java
index 5ef03af..e23fca7 100644
--- a/src/main/java/com/gitblit/extensions/RepositoryLifeCycleListener.java
+++ b/src/main/java/com/gitblit/extensions/RepositoryLifeCycleListener.java
@@ -36,6 +36,24 @@
public abstract void onCreation(RepositoryModel repository);
/**
+ * Called after a repository has been forked.
+ *
+ * @param origin
+ * @param fork
+ * @since 1.7.0
+ */
+ public abstract void onFork(RepositoryModel origin, RepositoryModel fork);
+
+ /**
+ * Called after a repository has been renamed.
+ *
+ * @param oldName
+ * @param repository
+ * @since 1.7.0
+ */
+ public abstract void onRename(String oldName, RepositoryModel repository);
+
+ /**
* Called after a repository has been deleted.
*
* @param repository
diff --git a/src/main/java/com/gitblit/git/PatchsetReceivePack.java b/src/main/java/com/gitblit/git/PatchsetReceivePack.java
index 54ffb7b..ef0b409 100644
--- a/src/main/java/com/gitblit/git/PatchsetReceivePack.java
+++ b/src/main/java/com/gitblit/git/PatchsetReceivePack.java
@@ -667,7 +667,7 @@
// identified the missing object earlier before we got control.
LOGGER.error("failed to get commit count", e);
} finally {
- walk.release();
+ walk.close();
}
sendError("");
@@ -1078,7 +1078,7 @@
LOGGER.error("failed to get commit count", e);
return 0;
} finally {
- walk.release();
+ walk.close();
}
return count;
}
diff --git a/src/main/java/com/gitblit/guice/AvatarGeneratorProvider.java b/src/main/java/com/gitblit/guice/AvatarGeneratorProvider.java
new file mode 100644
index 0000000..a18e299
--- /dev/null
+++ b/src/main/java/com/gitblit/guice/AvatarGeneratorProvider.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2015 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.guice;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.AvatarGenerator;
+import com.gitblit.GravatarGenerator;
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+/**
+ * Provides a lazily-instantiated AvatarGenerator configured from IStoredSettings.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class AvatarGeneratorProvider implements Provider<AvatarGenerator> {
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ private final IRuntimeManager runtimeManager;
+
+ private volatile AvatarGenerator avatarGenerator;
+
+ @Inject
+ public AvatarGeneratorProvider(IRuntimeManager runtimeManager) {
+ this.runtimeManager = runtimeManager;
+ }
+
+ @Override
+ public synchronized AvatarGenerator get() {
+ if (avatarGenerator != null) {
+ return avatarGenerator;
+ }
+
+ IStoredSettings settings = runtimeManager.getSettings();
+ String clazz = settings.getString(Keys.web.avatarClass, GravatarGenerator.class.getName());
+ if (StringUtils.isEmpty(clazz)) {
+ clazz = GravatarGenerator.class.getName();
+ }
+ try {
+ Class<? extends AvatarGenerator> generatorClass = (Class<? extends AvatarGenerator>) Class.forName(clazz);
+ avatarGenerator = runtimeManager.getInjector().getInstance(generatorClass);
+ } catch (Exception e) {
+ logger.error("failed to create avatar generator", e);
+ avatarGenerator = new GravatarGenerator();
+ }
+ return avatarGenerator;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/guice/CoreModule.java b/src/main/java/com/gitblit/guice/CoreModule.java
new file mode 100644
index 0000000..e2d1439
--- /dev/null
+++ b/src/main/java/com/gitblit/guice/CoreModule.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.guice;
+
+import com.gitblit.FileSettings;
+import com.gitblit.GitBlit;
+import com.gitblit.IStoredSettings;
+import com.gitblit.manager.AuthenticationManager;
+import com.gitblit.manager.FederationManager;
+import com.gitblit.manager.FilestoreManager;
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IFilestoreManager;
+import com.gitblit.manager.IGitblit;
+import com.gitblit.manager.INotificationManager;
+import com.gitblit.manager.IPluginManager;
+import com.gitblit.manager.IProjectManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IServicesManager;
+import com.gitblit.manager.IUserManager;
+import com.gitblit.manager.NotificationManager;
+import com.gitblit.manager.PluginManager;
+import com.gitblit.manager.ProjectManager;
+import com.gitblit.manager.RepositoryManager;
+import com.gitblit.manager.RuntimeManager;
+import com.gitblit.manager.ServicesManager;
+import com.gitblit.manager.UserManager;
+import com.gitblit.tickets.ITicketService;
+import com.gitblit.transport.ssh.IPublicKeyManager;
+import com.gitblit.utils.JSoupXssFilter;
+import com.gitblit.utils.WorkQueue;
+import com.gitblit.utils.XssFilter;
+import com.google.inject.AbstractModule;
+
+/**
+ * CoreModule references all the core business objects.
+ *
+ * @author James Moger
+ *
+ */
+public class CoreModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+
+ bind(IStoredSettings.class).toInstance(new FileSettings());
+ bind(XssFilter.class).to(JSoupXssFilter.class);
+
+ // bind complex providers
+ bind(IPublicKeyManager.class).toProvider(IPublicKeyManagerProvider.class);
+ bind(ITicketService.class).toProvider(ITicketServiceProvider.class);
+ bind(WorkQueue.class).toProvider(WorkQueueProvider.class);
+
+ // core managers
+ bind(IRuntimeManager.class).to(RuntimeManager.class);
+ bind(IPluginManager.class).to(PluginManager.class);
+ bind(INotificationManager.class).to(NotificationManager.class);
+ bind(IUserManager.class).to(UserManager.class);
+ bind(IAuthenticationManager.class).to(AuthenticationManager.class);
+ bind(IRepositoryManager.class).to(RepositoryManager.class);
+ bind(IProjectManager.class).to(ProjectManager.class);
+ bind(IFederationManager.class).to(FederationManager.class);
+ bind(IFilestoreManager.class).to(FilestoreManager.class);
+
+ // the monolithic manager
+ bind(IGitblit.class).to(GitBlit.class);
+
+ // manager for long-running daemons and services
+ bind(IServicesManager.class).to(ServicesManager.class);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/guice/IPublicKeyManagerProvider.java b/src/main/java/com/gitblit/guice/IPublicKeyManagerProvider.java
new file mode 100644
index 0000000..8075aa9
--- /dev/null
+++ b/src/main/java/com/gitblit/guice/IPublicKeyManagerProvider.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.guice;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.transport.ssh.FileKeyManager;
+import com.gitblit.transport.ssh.IPublicKeyManager;
+import com.gitblit.transport.ssh.NullKeyManager;
+import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+/**
+ * Provides a lazily-instantiated IPublicKeyManager configured from IStoredSettings.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class IPublicKeyManagerProvider implements Provider<IPublicKeyManager> {
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ private final IRuntimeManager runtimeManager;
+
+ private volatile IPublicKeyManager manager;
+
+ @Inject
+ public IPublicKeyManagerProvider(IRuntimeManager runtimeManager) {
+ this.runtimeManager = runtimeManager;
+ }
+
+ @Override
+ public synchronized IPublicKeyManager get() {
+ if (manager != null) {
+ return manager;
+ }
+
+ IStoredSettings settings = runtimeManager.getSettings();
+ String clazz = settings.getString(Keys.git.sshKeysManager, FileKeyManager.class.getName());
+ if (StringUtils.isEmpty(clazz)) {
+ clazz = FileKeyManager.class.getName();
+ }
+ try {
+ Class<? extends IPublicKeyManager> mgrClass = (Class<? extends IPublicKeyManager>) Class.forName(clazz);
+ manager = runtimeManager.getInjector().getInstance(mgrClass);
+ } catch (Exception e) {
+ logger.error("failed to create public key manager", e);
+ manager = new NullKeyManager();
+ }
+ return manager;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/guice/ITicketServiceProvider.java b/src/main/java/com/gitblit/guice/ITicketServiceProvider.java
new file mode 100644
index 0000000..fd39955
--- /dev/null
+++ b/src/main/java/com/gitblit/guice/ITicketServiceProvider.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.guice;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.tickets.ITicketService;
+import com.gitblit.tickets.NullTicketService;
+import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+/**
+ * Provides a lazily-instantiated ITicketService configured from IStoredSettings.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class ITicketServiceProvider implements Provider<ITicketService> {
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ private final IRuntimeManager runtimeManager;
+
+ private volatile ITicketService service;
+
+ @Inject
+ public ITicketServiceProvider(IRuntimeManager runtimeManager) {
+ this.runtimeManager = runtimeManager;
+ }
+
+ @Override
+ public synchronized ITicketService get() {
+ if (service != null) {
+ return service;
+ }
+
+ IStoredSettings settings = runtimeManager.getSettings();
+ String clazz = settings.getString(Keys.tickets.service, NullTicketService.class.getName());
+ if (StringUtils.isEmpty(clazz)) {
+ clazz = NullTicketService.class.getName();
+ }
+
+ try {
+ Class<? extends ITicketService> serviceClass = (Class<? extends ITicketService>) Class.forName(clazz);
+ service = runtimeManager.getInjector().getInstance(serviceClass);
+ } catch (Exception e) {
+ logger.error("failed to create ticket service", e);
+ service = runtimeManager.getInjector().getInstance(NullTicketService.class);
+ }
+
+ return service;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/guice/WebModule.java b/src/main/java/com/gitblit/guice/WebModule.java
new file mode 100644
index 0000000..7c83e45
--- /dev/null
+++ b/src/main/java/com/gitblit/guice/WebModule.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.guice;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.gitblit.AvatarGenerator;
+import com.gitblit.Constants;
+import com.gitblit.servlet.AccessDeniedServlet;
+import com.gitblit.servlet.BranchGraphServlet;
+import com.gitblit.servlet.DownloadZipFilter;
+import com.gitblit.servlet.DownloadZipServlet;
+import com.gitblit.servlet.EnforceAuthenticationFilter;
+import com.gitblit.servlet.FederationServlet;
+import com.gitblit.servlet.FilestoreServlet;
+import com.gitblit.servlet.GitFilter;
+import com.gitblit.servlet.GitServlet;
+import com.gitblit.servlet.LogoServlet;
+import com.gitblit.servlet.PagesFilter;
+import com.gitblit.servlet.PagesServlet;
+import com.gitblit.servlet.ProxyFilter;
+import com.gitblit.servlet.PtServlet;
+import com.gitblit.servlet.RawFilter;
+import com.gitblit.servlet.RawServlet;
+import com.gitblit.servlet.RobotsTxtServlet;
+import com.gitblit.servlet.RpcFilter;
+import com.gitblit.servlet.RpcServlet;
+import com.gitblit.servlet.SparkleShareInviteServlet;
+import com.gitblit.servlet.SyndicationFilter;
+import com.gitblit.servlet.SyndicationServlet;
+import com.gitblit.wicket.GitblitWicketFilter;
+import com.google.common.base.Joiner;
+import com.google.inject.servlet.ServletModule;
+
+/**
+ * Defines all the web servlets & filters.
+ *
+ * @author James Moger
+ *
+ */
+public class WebModule extends ServletModule {
+
+ final static String ALL = "/*";
+
+ @Override
+ protected void configureServlets() {
+
+ // bind web component providers
+ bind(AvatarGenerator.class).toProvider(AvatarGeneratorProvider.class);
+
+ // servlets
+ serveRegex(FilestoreServlet.REGEX_PATH).with(FilestoreServlet.class);
+ serve(fuzzy(Constants.R_PATH), fuzzy(Constants.GIT_PATH)).with(GitServlet.class);
+ serve(fuzzy(Constants.RAW_PATH)).with(RawServlet.class);
+ serve(fuzzy(Constants.PAGES)).with(PagesServlet.class);
+ serve(fuzzy(Constants.RPC_PATH)).with(RpcServlet.class);
+ serve(fuzzy(Constants.ZIP_PATH)).with(DownloadZipServlet.class);
+ serve(fuzzy(Constants.SYNDICATION_PATH)).with(SyndicationServlet.class);
+
+
+ serve(fuzzy(Constants.FEDERATION_PATH)).with(FederationServlet.class);
+ serve(fuzzy(Constants.SPARKLESHARE_INVITE_PATH)).with(SparkleShareInviteServlet.class);
+ serve(fuzzy(Constants.BRANCH_GRAPH_PATH)).with(BranchGraphServlet.class);
+ serve(Constants.PT_PATH).with(PtServlet.class);
+ serve("/robots.txt").with(RobotsTxtServlet.class);
+ serve("/logo.png").with(LogoServlet.class);
+
+ /* Prevent accidental access to 'resources' such as GitBlit java classes
+ *
+ * In the GO setup the JAR containing the application and the WAR injected
+ * into Jetty are the same file. However Jetty expects to serve the entire WAR
+ * contents, except the WEB-INF folder. Thus, all java binary classes in the
+ * JAR are served by default as is they were legitimate resources.
+ *
+ * The below servlet mappings prevent that behavior
+ */
+ serve(fuzzy("/com/")).with(AccessDeniedServlet.class);
+
+ // global filters
+ filter(ALL).through(ProxyFilter.class);
+ filter(ALL).through(EnforceAuthenticationFilter.class);
+
+ // security filters
+ filter(fuzzy(Constants.R_PATH), fuzzy(Constants.GIT_PATH)).through(GitFilter.class);
+ filter(fuzzy(Constants.RAW_PATH)).through(RawFilter.class);
+ filter(fuzzy(Constants.PAGES)).through(PagesFilter.class);
+ filter(fuzzy(Constants.RPC_PATH)).through(RpcFilter.class);
+ filter(fuzzy(Constants.ZIP_PATH)).through(DownloadZipFilter.class);
+ filter(fuzzy(Constants.SYNDICATION_PATH)).through(SyndicationFilter.class);
+
+
+ // Wicket
+ String toIgnore = Joiner.on(",").join(Constants.R_PATH, Constants.GIT_PATH, Constants.RAW_PATH,
+ Constants.PAGES, Constants.RPC_PATH, Constants.ZIP_PATH, Constants.SYNDICATION_PATH,
+ Constants.FEDERATION_PATH, Constants.SPARKLESHARE_INVITE_PATH, Constants.BRANCH_GRAPH_PATH,
+ Constants.PT_PATH, "/robots.txt", "/logo.png");
+
+ Map<String, String> params = new HashMap<String, String>();
+ params.put(GitblitWicketFilter.FILTER_MAPPING_PARAM, ALL);
+ params.put(GitblitWicketFilter.IGNORE_PATHS_PARAM, toIgnore);
+ filter(ALL).through(GitblitWicketFilter.class, params);
+ }
+
+ private String fuzzy(String path) {
+ if (path.endsWith(ALL)) {
+ return path;
+ } else if (path.endsWith("/")) {
+ return path + "*";
+ }
+ return path + ALL;
+ }
+}
diff --git a/src/main/java/com/gitblit/guice/WorkQueueProvider.java b/src/main/java/com/gitblit/guice/WorkQueueProvider.java
new file mode 100644
index 0000000..cde27ea
--- /dev/null
+++ b/src/main/java/com/gitblit/guice/WorkQueueProvider.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.guice;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.utils.IdGenerator;
+import com.gitblit.utils.WorkQueue;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+/**
+ * Provides a lazily-instantiated WorkQueue configured from IStoredSettings.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class WorkQueueProvider implements Provider<WorkQueue> {
+
+ private final IRuntimeManager runtimeManager;
+
+ private volatile WorkQueue workQueue;
+
+ @Inject
+ public WorkQueueProvider(IRuntimeManager runtimeManager) {
+ this.runtimeManager = runtimeManager;
+ }
+
+ @Override
+ public synchronized WorkQueue get() {
+ if (workQueue != null) {
+ return workQueue;
+ }
+
+ IStoredSettings settings = runtimeManager.getSettings();
+ int defaultThreadPoolSize = settings.getInteger(Keys.execution.defaultThreadPoolSize, 1);
+ IdGenerator idGenerator = new IdGenerator();
+ workQueue = new WorkQueue(idGenerator, defaultThreadPoolSize);
+ return workQueue;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/manager/AuthenticationManager.java b/src/main/java/com/gitblit/manager/AuthenticationManager.java
index f98f7b6..4978763 100644
--- a/src/main/java/com/gitblit/manager/AuthenticationManager.java
+++ b/src/main/java/com/gitblit/manager/AuthenticationManager.java
@@ -35,11 +35,13 @@
import com.gitblit.Constants;
import com.gitblit.Constants.AccountType;
import com.gitblit.Constants.AuthenticationType;
+import com.gitblit.Constants.Role;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.auth.AuthenticationProvider;
import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
import com.gitblit.auth.HtpasswdAuthProvider;
+import com.gitblit.auth.HttpHeaderAuthProvider;
import com.gitblit.auth.LdapAuthProvider;
import com.gitblit.auth.PAMAuthProvider;
import com.gitblit.auth.RedmineAuthProvider;
@@ -52,6 +54,8 @@
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.X509Utils.X509Metadata;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* The authentication manager handles user login & logout.
@@ -59,6 +63,7 @@
* @author James Moger
*
*/
+@Singleton
public class AuthenticationManager implements IAuthenticationManager {
private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -75,6 +80,7 @@
private final Map<String, String> legacyRedirects;
+ @Inject
public AuthenticationManager(
IRuntimeManager runtimeManager,
IUserManager userManager) {
@@ -87,6 +93,7 @@
// map of shortcut provider names
providerNames = new HashMap<String, Class<? extends AuthenticationProvider>>();
providerNames.put("htpasswd", HtpasswdAuthProvider.class);
+ providerNames.put("httpheader", HttpHeaderAuthProvider.class);
providerNames.put("ldap", LdapAuthProvider.class);
providerNames.put("pam", PAMAuthProvider.class);
providerNames.put("redmine", RedmineAuthProvider.class);
@@ -165,7 +172,11 @@
}
/**
- * Authenticate a user based on HTTP request parameters.
+ * Used to handle authentication for page requests.
+ *
+ * This allows authentication to occur based on the contents of the request
+ * itself. If no configured @{AuthenticationProvider}s authenticate succesffully,
+ * a request for login will be shown.
*
* Authentication by X509Certificate is tried first and then by cookie.
*
@@ -180,7 +191,7 @@
/**
* Authenticate a user based on HTTP request parameters.
*
- * Authentication by servlet container principal, X509Certificate, cookie,
+ * Authentication by custom HTTP header, servlet container principal, X509Certificate, cookie,
* and finally BASIC header.
*
* @param httpRequest
@@ -189,6 +200,14 @@
*/
@Override
public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
+
+ // Check if this request has already been authenticated, and trust that instead of re-processing
+ String reqAuthUser = (String) httpRequest.getAttribute(Constants.ATTRIB_AUTHUSER);
+ if (!StringUtils.isEmpty(reqAuthUser)) {
+ logger.debug("Called servlet authenticate when request is already authenticated.");
+ return userManager.getUserModel(reqAuthUser);
+ }
+
// try to authenticate by servlet container principal
if (!requiresCertificate) {
Principal principal = httpRequest.getUserPrincipal();
@@ -199,7 +218,7 @@
UserModel user = userManager.getUserModel(username);
if (user != null) {
// existing user
- flagSession(httpRequest, AuthenticationType.CONTAINER);
+ flagRequest(httpRequest, AuthenticationType.CONTAINER, user.username);
logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}",
user.username, httpRequest.getRemoteAddr()));
return validateAuthentication(user, AuthenticationType.CONTAINER);
@@ -210,8 +229,31 @@
user.displayName = username;
user.password = Constants.EXTERNAL_ACCOUNT;
user.accountType = AccountType.CONTAINER;
+
+ // Try to extract user's informations for the session
+ // it uses "realm.container.autoAccounts.*" as the attribute name to look for
+ HttpSession session = httpRequest.getSession();
+ String emailAddress = resolveAttribute(session, Keys.realm.container.autoAccounts.emailAddress);
+ if(emailAddress != null) {
+ user.emailAddress = emailAddress;
+ }
+ String displayName = resolveAttribute(session, Keys.realm.container.autoAccounts.displayName);
+ if(displayName != null) {
+ user.displayName = displayName;
+ }
+ String userLocale = resolveAttribute(session, Keys.realm.container.autoAccounts.locale);
+ if(userLocale != null) {
+ user.getPreferences().setLocale(userLocale);
+ }
+ String adminRole = settings.getString(Keys.realm.container.autoAccounts.adminRole, null);
+ if(adminRole != null && ! adminRole.isEmpty()) {
+ if(httpRequest.isUserInRole(adminRole)) {
+ user.canAdmin = true;
+ }
+ }
+
userManager.updateUserModel(user);
- flagSession(httpRequest, AuthenticationType.CONTAINER);
+ flagRequest(httpRequest, AuthenticationType.CONTAINER, user.username);
logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}",
user.username, httpRequest.getRemoteAddr()));
return validateAuthentication(user, AuthenticationType.CONTAINER);
@@ -232,7 +274,7 @@
UserModel user = userManager.getUserModel(model.username);
X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
if (user != null) {
- flagSession(httpRequest, AuthenticationType.CERTIFICATE);
+ flagRequest(httpRequest, AuthenticationType.CERTIFICATE, user.username);
logger.debug(MessageFormat.format("{0} authenticated by client certificate {1} from {2}",
user.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
return validateAuthentication(user, AuthenticationType.CERTIFICATE);
@@ -254,7 +296,7 @@
if (!StringUtils.isEmpty(cookie)) {
user = userManager.getUserModel(cookie.toCharArray());
if (user != null) {
- flagSession(httpRequest, AuthenticationType.COOKIE);
+ flagRequest(httpRequest, AuthenticationType.COOKIE, user.username);
logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}",
user.username, httpRequest.getRemoteAddr()));
return validateAuthentication(user, AuthenticationType.COOKIE);
@@ -274,20 +316,54 @@
if (values.length == 2) {
String username = values[0];
char[] password = values[1].toCharArray();
- user = authenticate(username, password);
+ user = authenticate(username, password, httpRequest.getRemoteAddr());
if (user != null) {
- flagSession(httpRequest, AuthenticationType.CREDENTIALS);
+ flagRequest(httpRequest, AuthenticationType.CREDENTIALS, user.username);
logger.debug(MessageFormat.format("{0} authenticated by BASIC request header from {1}",
user.username, httpRequest.getRemoteAddr()));
return validateAuthentication(user, AuthenticationType.CREDENTIALS);
- } else {
- logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}",
- username, httpRequest.getRemoteAddr()));
}
}
}
+
+ // Check each configured AuthenticationProvider
+ for (AuthenticationProvider ap : authenticationProviders) {
+ UserModel authedUser = ap.authenticate(httpRequest);
+ if (null != authedUser) {
+ flagRequest(httpRequest, ap.getAuthenticationType(), authedUser.username);
+ logger.debug(MessageFormat.format("{0} authenticated by {1} from {2} for {3}",
+ authedUser.username, ap.getServiceName(), httpRequest.getRemoteAddr(),
+ httpRequest.getPathInfo()));
+ return validateAuthentication(authedUser, ap.getAuthenticationType());
+ }
+ }
return null;
}
+
+ /**
+ * Extract given attribute from the session and return it's content
+ * it return null if attributeMapping is empty, or if the value is
+ * empty
+ *
+ * @param session The user session
+ * @param attributeMapping
+ * @return
+ */
+ private String resolveAttribute(HttpSession session, String attributeMapping) {
+ String attributeName = settings.getString(attributeMapping, null);
+ if(StringUtils.isEmpty(attributeName)) {
+ return null;
+ }
+ Object attributeValue = session.getAttribute(attributeName);
+ if(attributeValue == null) {
+ return null;
+ }
+ String value = attributeValue.toString();
+ if(value.isEmpty()) {
+ return null;
+ }
+ return value;
+ }
/**
* Authenticate a user based on a public key.
@@ -321,6 +397,35 @@
/**
+ * Return the UserModel for already authenticated user.
+ *
+ * This implementation assumes that the authentication has already take place
+ * (e.g. SSHDaemon) and that this is a validation/verification of the user.
+ *
+ * @param username
+ * @return a user object or null
+ */
+ @Override
+ public UserModel authenticate(String username) {
+ if (username != null) {
+ if (!StringUtils.isEmpty(username)) {
+ UserModel user = userManager.getUserModel(username);
+ if (user != null) {
+ // existing user
+ logger.debug(MessageFormat.format("{0} authenticated externally", user.username));
+ return validateAuthentication(user, AuthenticationType.CONTAINER);
+ }
+ logger.warn(MessageFormat.format("Failed to find UserModel for {0} during external authentication",
+ username));
+ }
+ } else {
+ logger.warn("Empty user passed to AuthenticationManager.authenticate!");
+ }
+ return null;
+ }
+
+
+ /**
* This method allows the authentication manager to reject authentication
* attempts. It is called after the username/secret have been verified to
* ensure that the authentication technique has been logged.
@@ -341,8 +446,9 @@
return user;
}
- protected void flagSession(HttpServletRequest httpRequest, AuthenticationType authenticationType) {
- httpRequest.getSession().setAttribute(Constants.AUTHENTICATION_TYPE, authenticationType);
+ protected void flagRequest(HttpServletRequest httpRequest, AuthenticationType authenticationType, String authedUsername) {
+ httpRequest.setAttribute(Constants.ATTRIB_AUTHUSER, authedUsername);
+ httpRequest.setAttribute(Constants.ATTRIB_AUTHTYPE, authenticationType);
}
/**
@@ -354,12 +460,18 @@
* @return a user object or null
*/
@Override
- public UserModel authenticate(String username, char[] password) {
+ public UserModel authenticate(String username, char[] password, String remoteIP) {
if (StringUtils.isEmpty(username)) {
// can not authenticate empty username
return null;
}
+ if (username.equalsIgnoreCase(Constants.FEDERATION_USER)) {
+ // can not authenticate internal FEDERATION_USER at this point
+ // it must be routed to FederationManager
+ return null;
+ }
+
String usernameDecoded = StringUtils.decodeUsername(username);
String pw = new String(password);
if (StringUtils.isEmpty(pw)) {
@@ -371,22 +483,29 @@
// try local authentication
if (user != null && user.isLocalAccount()) {
- return authenticateLocal(user, password);
- }
-
- // try registered external authentication providers
- for (AuthenticationProvider provider : authenticationProviders) {
- if (provider instanceof UsernamePasswordAuthenticationProvider) {
- UserModel returnedUser = provider.authenticate(usernameDecoded, password);
- if (returnedUser != null) {
- // user authenticated
- returnedUser.accountType = provider.getAccountType();
- return validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS);
+ UserModel returnedUser = authenticateLocal(user, password);
+ if (returnedUser != null) {
+ // user authenticated
+ return returnedUser;
+ }
+ } else {
+ // try registered external authentication providers
+ for (AuthenticationProvider provider : authenticationProviders) {
+ if (provider instanceof UsernamePasswordAuthenticationProvider) {
+ UserModel returnedUser = provider.authenticate(usernameDecoded, password);
+ if (returnedUser != null) {
+ // user authenticated
+ returnedUser.accountType = provider.getAccountType();
+ return validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS);
+ }
}
}
}
// could not authenticate locally or with a provider
+ logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}", username,
+ remoteIP != null ? remoteIP : "unknown"));
+
return null;
}
@@ -463,9 +582,15 @@
@Override
public void setCookie(HttpServletRequest request, HttpServletResponse response, UserModel user) {
if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) {
- HttpSession session = request.getSession();
- AuthenticationType authenticationType = (AuthenticationType) session.getAttribute(Constants.AUTHENTICATION_TYPE);
- boolean standardLogin = authenticationType.isStandard();
+ boolean standardLogin = true;
+
+ if (null != request) {
+ // Pull the auth type from the request, it is set there if container managed
+ AuthenticationType authenticationType = (AuthenticationType) request.getAttribute(Constants.ATTRIB_AUTHTYPE);
+
+ if (null != authenticationType)
+ standardLogin = authenticationType.isStandard();
+ }
if (standardLogin) {
Cookie userCookie;
@@ -576,6 +701,28 @@
return (team != null && team.isLocalTeam()) || findProvider(team).supportsTeamMembershipChanges();
}
+ /**
+ * Returns true if the user's role can be changed.
+ *
+ * @param user
+ * @return true if the user's role can be changed
+ */
+ @Override
+ public boolean supportsRoleChanges(UserModel user, Role role) {
+ return (user != null && user.isLocalAccount()) || findProvider(user).supportsRoleChanges(user, role);
+ }
+
+ /**
+ * Returns true if the team's role can be changed.
+ *
+ * @param user
+ * @return true if the team's role can be changed
+ */
+ @Override
+ public boolean supportsRoleChanges(TeamModel team, Role role) {
+ return (team != null && team.isLocalTeam()) || findProvider(team).supportsRoleChanges(team, role);
+ }
+
protected AuthenticationProvider findProvider(UserModel user) {
for (AuthenticationProvider provider : authenticationProviders) {
if (provider.getAccountType().equals(user.accountType)) {
diff --git a/src/main/java/com/gitblit/manager/FederationManager.java b/src/main/java/com/gitblit/manager/FederationManager.java
index 95d38af..8f68733 100644
--- a/src/main/java/com/gitblit/manager/FederationManager.java
+++ b/src/main/java/com/gitblit/manager/FederationManager.java
@@ -45,6 +45,8 @@
import com.gitblit.utils.FederationUtils;
import com.gitblit.utils.JsonUtils;
import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* Federation manager controls all aspects of handling federation sets, tokens,
@@ -53,6 +55,7 @@
* @author James Moger
*
*/
+@Singleton
public class FederationManager implements IFederationManager {
private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -70,6 +73,7 @@
private final IRepositoryManager repositoryManager;
+ @Inject
public FederationManager(
IRuntimeManager runtimeManager,
INotificationManager notificationManager,
@@ -363,6 +367,10 @@
&& file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
}
});
+ if (files == null) {
+ return list;
+ }
+
for (File file : files) {
String json = com.gitblit.utils.FileUtils.readContent(file, null);
FederationProposal proposal = JsonUtils.fromJsonString(json,
diff --git a/src/main/java/com/gitblit/manager/FilestoreManager.java b/src/main/java/com/gitblit/manager/FilestoreManager.java
new file mode 100644
index 0000000..1110855
--- /dev/null
+++ b/src/main/java/com/gitblit/manager/FilestoreManager.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright 2015 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.manager;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.lang.reflect.Type;
+import java.nio.file.Files;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.models.FilestoreModel;
+import com.gitblit.models.FilestoreModel.Status;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.JsonUtils.GmtDateTypeAdapter;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * FilestoreManager handles files uploaded via:
+ * + git-lfs
+ * + ticket attachment (TBD)
+ *
+ * Files are stored using their SHA256 hash (as per git-lfs)
+ * If the same file is uploaded through different repositories no additional space is used
+ * Access is controlled through the current repository permissions.
+ *
+ * TODO: Identify what and how the actual BLOBs should work with federation
+ *
+ * @author Paul Martin
+ *
+ */
+@Singleton
+public class FilestoreManager implements IFilestoreManager {
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ private final IRuntimeManager runtimeManager;
+
+ private final IRepositoryManager repositoryManager;
+
+ private final IStoredSettings settings;
+
+ public static final int UNDEFINED_SIZE = -1;
+
+ private static final String METAFILE = "filestore.json";
+
+ private static final String METAFILE_TMP = "filestore.json.tmp";
+
+ protected static final Type METAFILE_TYPE = new TypeToken<Collection<FilestoreModel>>() {}.getType();
+
+ private Map<String, FilestoreModel > fileCache = new ConcurrentHashMap<String, FilestoreModel>();
+
+
+ @Inject
+ FilestoreManager(
+ IRuntimeManager runtimeManager,
+ IRepositoryManager repositoryManager) {
+ this.runtimeManager = runtimeManager;
+ this.repositoryManager = repositoryManager;
+ this.settings = runtimeManager.getSettings();
+ }
+
+ @Override
+ public IManager start() {
+
+ // Try to load any existing metadata
+ File dir = getStorageFolder();
+ dir.mkdirs();
+ File metadata = new File(dir, METAFILE);
+
+ if (metadata.exists()) {
+ Collection<FilestoreModel> items = null;
+
+ Gson gson = gson();
+ try (FileReader file = new FileReader(metadata)) {
+ items = gson.fromJson(file, METAFILE_TYPE);
+ file.close();
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ for(Iterator<FilestoreModel> itr = items.iterator(); itr.hasNext(); ) {
+ FilestoreModel model = itr.next();
+ fileCache.put(model.oid, model);
+ }
+
+ logger.info("Loaded {} items from filestore metadata file", fileCache.size());
+ }
+ else
+ {
+ logger.info("No filestore metadata file found");
+ }
+
+ return this;
+ }
+
+ @Override
+ public IManager stop() {
+ return this;
+ }
+
+
+ @Override
+ public boolean isValidOid(String oid) {
+ //NOTE: Assuming SHA256 support only as per git-lfs
+ return Pattern.matches("[a-fA-F0-9]{64}", oid);
+ }
+
+ @Override
+ public FilestoreModel.Status addObject(String oid, long size, UserModel user, RepositoryModel repo) {
+
+ //Handle access control
+ if (!user.canPush(repo)) {
+ if (user == UserModel.ANONYMOUS) {
+ return Status.AuthenticationRequired;
+ } else {
+ return Status.Error_Unauthorized;
+ }
+ }
+
+ //Handle object details
+ if (!isValidOid(oid)) { return Status.Error_Invalid_Oid; }
+
+ if (fileCache.containsKey(oid)) {
+ FilestoreModel item = fileCache.get(oid);
+
+ if (!item.isInErrorState() && (size != UNDEFINED_SIZE) && (item.getSize() != size)) {
+ return Status.Error_Size_Mismatch;
+ }
+
+ item.addRepository(repo.name);
+
+ if (item.isInErrorState()) {
+ item.reset(user, size);
+ }
+ } else {
+
+ if (size < 0) {return Status.Error_Invalid_Size; }
+ if ((getMaxUploadSize() != UNDEFINED_SIZE) && (size > getMaxUploadSize())) { return Status.Error_Exceeds_Size_Limit; }
+
+ FilestoreModel model = new FilestoreModel(oid, size, user, repo.name);
+ fileCache.put(oid, model);
+ saveFilestoreModel(model);
+ }
+
+ return fileCache.get(oid).getStatus();
+ }
+
+ @Override
+ public FilestoreModel.Status uploadBlob(String oid, long size, UserModel user, RepositoryModel repo, InputStream streamIn) {
+
+ //Access control and object logic
+ Status state = addObject(oid, size, user, repo);
+
+ if (state != Status.Upload_Pending) {
+ return state;
+ }
+
+ FilestoreModel model = fileCache.get(oid);
+
+ if (!model.actionUpload(user)) {
+ return Status.Upload_In_Progress;
+ } else {
+ long actualSize = 0;
+ File file = getStoragePath(oid);
+
+ try {
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+
+ try (FileOutputStream streamOut = new FileOutputStream(file)) {
+
+ actualSize = IOUtils.copyLarge(streamIn, streamOut);
+
+ streamOut.flush();
+ streamOut.close();
+
+ if (model.getSize() != actualSize) {
+ model.setStatus(Status.Error_Size_Mismatch, user);
+
+ logger.warn(MessageFormat.format("Failed to upload blob {0} due to size mismatch, expected {1} got {2}",
+ oid, model.getSize(), actualSize));
+ } else {
+ String actualOid = "";
+
+ try (FileInputStream fileForHash = new FileInputStream(file)) {
+ actualOid = DigestUtils.sha256Hex(fileForHash);
+ fileForHash.close();
+ }
+
+ if (oid.equalsIgnoreCase(actualOid)) {
+ model.setStatus(Status.Available, user);
+ } else {
+ model.setStatus(Status.Error_Hash_Mismatch, user);
+
+ logger.warn(MessageFormat.format("Failed to upload blob {0} due to hash mismatch, got {1}", oid, actualOid));
+ }
+ }
+ }
+ } catch (Exception e) {
+
+ model.setStatus(Status.Error_Unknown, user);
+ logger.warn(MessageFormat.format("Failed to upload blob {0}", oid), e);
+ } finally {
+ saveFilestoreModel(model);
+ }
+
+ if (model.isInErrorState()) {
+ file.delete();
+ model.removeRepository(repo.name);
+ }
+ }
+
+ return model.getStatus();
+ }
+
+ private FilestoreModel.Status canGetObject(String oid, UserModel user, RepositoryModel repo) {
+
+ //Access Control
+ if (!user.canView(repo)) {
+ if (user == UserModel.ANONYMOUS) {
+ return Status.AuthenticationRequired;
+ } else {
+ return Status.Error_Unauthorized;
+ }
+ }
+
+ //Object Logic
+ if (!isValidOid(oid)) {
+ return Status.Error_Invalid_Oid;
+ }
+
+ if (!fileCache.containsKey(oid)) {
+ return Status.Unavailable;
+ }
+
+ FilestoreModel item = fileCache.get(oid);
+
+ if (item.getStatus() == Status.Available) {
+ return Status.Available;
+ }
+
+ return Status.Unavailable;
+ }
+
+ @Override
+ public FilestoreModel getObject(String oid, UserModel user, RepositoryModel repo) {
+
+ if (canGetObject(oid, user, repo) == Status.Available) {
+ return fileCache.get(oid);
+ }
+
+ return null;
+ }
+
+ @Override
+ public FilestoreModel.Status downloadBlob(String oid, UserModel user, RepositoryModel repo, OutputStream streamOut) {
+
+ //Access control and object logic
+ Status status = canGetObject(oid, user, repo);
+
+ if (status != Status.Available) {
+ return status;
+ }
+
+ FilestoreModel item = fileCache.get(oid);
+
+ if (streamOut != null) {
+ try (FileInputStream streamIn = new FileInputStream(getStoragePath(oid))) {
+
+ IOUtils.copyLarge(streamIn, streamOut);
+
+ streamOut.flush();
+ streamIn.close();
+ } catch (EOFException e) {
+ logger.error(MessageFormat.format("Client aborted connection for {0}", oid), e);
+ return Status.Error_Unexpected_Stream_End;
+ } catch (Exception e) {
+ logger.error(MessageFormat.format("Failed to download blob {0}", oid), e);
+ return Status.Error_Unknown;
+ }
+ }
+
+ return item.getStatus();
+ }
+
+ @Override
+ public List<FilestoreModel> getAllObjects(UserModel user) {
+
+ final List<RepositoryModel> viewableRepositories = repositoryManager.getRepositoryModels(user);
+ List<String> viewableRepositoryNames = new ArrayList<String>(viewableRepositories.size());
+
+ for (RepositoryModel repository : viewableRepositories) {
+ viewableRepositoryNames.add(repository.name);
+ }
+
+ if (viewableRepositoryNames.size() == 0) {
+ return null;
+ }
+
+ final Collection<FilestoreModel> allFiles = fileCache.values();
+ List<FilestoreModel> userViewableFiles = new ArrayList<FilestoreModel>(allFiles.size());
+
+ for (FilestoreModel file : allFiles) {
+ if (file.isInRepositoryList(viewableRepositoryNames)) {
+ userViewableFiles.add(file);
+ }
+ }
+
+ return userViewableFiles;
+ }
+
+ @Override
+ public File getStorageFolder() {
+ return runtimeManager.getFileOrFolder(Keys.filestore.storageFolder, "${baseFolder}/lfs");
+ }
+
+ @Override
+ public File getStoragePath(String oid) {
+ return new File(getStorageFolder(), oid.substring(0, 2).concat("/").concat(oid.substring(2)));
+ }
+
+ @Override
+ public long getMaxUploadSize() {
+ return settings.getLong(Keys.filestore.maxUploadSize, -1);
+ }
+
+ @Override
+ public long getFilestoreUsedByteCount() {
+ Iterator<FilestoreModel> iterator = fileCache.values().iterator();
+ long total = 0;
+
+ while (iterator.hasNext()) {
+
+ FilestoreModel item = iterator.next();
+ if (item.getStatus() == Status.Available) {
+ total += item.getSize();
+ }
+ }
+
+ return total;
+ }
+
+ @Override
+ public long getFilestoreAvailableByteCount() {
+
+ try {
+ return Files.getFileStore(getStorageFolder().toPath()).getUsableSpace();
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("Failed to retrive available space in Filestore {0}", e));
+ }
+
+ return UNDEFINED_SIZE;
+ };
+
+ private synchronized void saveFilestoreModel(FilestoreModel model) {
+
+ File metaFile = new File(getStorageFolder(), METAFILE);
+ File metaFileTmp = new File(getStorageFolder(), METAFILE_TMP);
+ boolean isNewFile = false;
+
+ try {
+ if (!metaFile.exists()) {
+ metaFile.getParentFile().mkdirs();
+ metaFile.createNewFile();
+ isNewFile = true;
+ }
+ FileUtils.copyFile(metaFile, metaFileTmp);
+
+ } catch (IOException e) {
+ logger.error("Writing filestore model to file {0}, {1}", METAFILE, e);
+ }
+
+ try (RandomAccessFile fs = new RandomAccessFile(metaFileTmp, "rw")) {
+
+ if (isNewFile) {
+ fs.writeBytes("[");
+ } else {
+ fs.seek(fs.length() - 1);
+ fs.writeBytes(",");
+ }
+
+ fs.writeBytes(gson().toJson(model));
+ fs.writeBytes("]");
+
+ fs.close();
+
+ } catch (IOException e) {
+ logger.error("Writing filestore model to file {0}, {1}", METAFILE_TMP, e);
+ }
+
+ try {
+ if (metaFileTmp.exists()) {
+ FileUtils.copyFile(metaFileTmp, metaFile);
+
+ metaFileTmp.delete();
+ } else {
+ logger.error("Writing filestore model to file {0}", METAFILE);
+ }
+ }
+ catch (IOException e) {
+ logger.error("Writing filestore model to file {0}, {1}", METAFILE, e);
+ }
+ }
+
+ /*
+ * Intended for testing purposes only
+ */
+ @Override
+ public void clearFilestoreCache() {
+ fileCache.clear();
+ }
+
+ private static Gson gson(ExclusionStrategy... strategies) {
+ GsonBuilder builder = new GsonBuilder();
+ builder.registerTypeAdapter(Date.class, new GmtDateTypeAdapter());
+ if (!ArrayUtils.isEmpty(strategies)) {
+ builder.setExclusionStrategies(strategies);
+ }
+ return builder.create();
+ }
+
+}
diff --git a/src/main/java/com/gitblit/manager/GitblitManager.java b/src/main/java/com/gitblit/manager/GitblitManager.java
index 88fa804..85d5c19 100644
--- a/src/main/java/com/gitblit/manager/GitblitManager.java
+++ b/src/main/java/com/gitblit/manager/GitblitManager.java
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.OutputStream;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.util.ArrayList;
@@ -49,15 +50,16 @@
import com.gitblit.Constants;
import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.FederationRequest;
import com.gitblit.Constants.FederationToken;
+import com.gitblit.Constants.Role;
import com.gitblit.GitBlitException;
import com.gitblit.IStoredSettings;
-import com.gitblit.Keys;
+import com.gitblit.extensions.RepositoryLifeCycleListener;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
+import com.gitblit.models.FilestoreModel;
import com.gitblit.models.ForkModel;
import com.gitblit.models.GitClientApplication;
import com.gitblit.models.Mailing;
@@ -68,7 +70,6 @@
import com.gitblit.models.ProjectModel;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.RepositoryUrl;
import com.gitblit.models.SearchResult;
import com.gitblit.models.ServerSettings;
import com.gitblit.models.ServerStatus;
@@ -79,7 +80,6 @@
import com.gitblit.transport.ssh.IPublicKeyManager;
import com.gitblit.transport.ssh.SshKey;
import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.JsonUtils;
import com.gitblit.utils.ObjectCache;
import com.gitblit.utils.StringUtils;
@@ -88,6 +88,10 @@
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
/**
* GitblitManager is an aggregate interface delegate. It implements all the manager
@@ -101,12 +105,17 @@
* @author James Moger
*
*/
+@Singleton
public class GitblitManager implements IGitblit {
protected final Logger logger = LoggerFactory.getLogger(getClass());
protected final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>();
+ protected final Provider<IPublicKeyManager> publicKeyManagerProvider;
+
+ protected final Provider<ITicketService> ticketServiceProvider;
+
protected final IStoredSettings settings;
protected final IRuntimeManager runtimeManager;
@@ -119,24 +128,30 @@
protected final IAuthenticationManager authenticationManager;
- protected final IPublicKeyManager publicKeyManager;
-
protected final IRepositoryManager repositoryManager;
protected final IProjectManager projectManager;
protected final IFederationManager federationManager;
+ protected final IFilestoreManager filestoreManager;
+
+ @Inject
public GitblitManager(
+ Provider<IPublicKeyManager> publicKeyManagerProvider,
+ Provider<ITicketService> ticketServiceProvider,
IRuntimeManager runtimeManager,
IPluginManager pluginManager,
INotificationManager notificationManager,
IUserManager userManager,
IAuthenticationManager authenticationManager,
- IPublicKeyManager publicKeyManager,
IRepositoryManager repositoryManager,
IProjectManager projectManager,
- IFederationManager federationManager) {
+ IFederationManager federationManager,
+ IFilestoreManager filestoreManager) {
+
+ this.publicKeyManagerProvider = publicKeyManagerProvider;
+ this.ticketServiceProvider = ticketServiceProvider;
this.settings = runtimeManager.getSettings();
this.runtimeManager = runtimeManager;
@@ -144,10 +159,10 @@
this.notificationManager = notificationManager;
this.userManager = userManager;
this.authenticationManager = authenticationManager;
- this.publicKeyManager = publicKeyManager;
this.repositoryManager = repositoryManager;
this.projectManager = projectManager;
this.federationManager = federationManager;
+ this.filestoreManager = filestoreManager;
}
@Override
@@ -268,6 +283,16 @@
// add this clone to the cached model
repositoryManager.addToCachedRepositoryList(cloneModel);
+
+ if (pluginManager != null) {
+ for (RepositoryLifeCycleListener listener : pluginManager.getExtensions(RepositoryLifeCycleListener.class)) {
+ try {
+ listener.onFork(repository, cloneModel);
+ } catch (Throwable t) {
+ logger.error(String.format("failed to call plugin onFork %s", repository.name), t);
+ }
+ }
+ }
return cloneModel;
}
@@ -358,66 +383,6 @@
}
/**
- * Returns a list of repository URLs and the user access permission.
- *
- * @param request
- * @param user
- * @param repository
- * @return a list of repository urls
- */
- @Override
- public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
- if (user == null) {
- user = UserModel.ANONYMOUS;
- }
- String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
-
- List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
- // http/https url
- if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
- AccessPermission permission = user.getRepositoryPermission(repository).permission;
- if (permission.exceeds(AccessPermission.NONE)) {
- list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
- }
- }
-
- // add all other urls
- // {0} = repository
- // {1} = username
- for (String url : settings.getStrings(Keys.web.otherUrls)) {
- if (url.contains("{1}")) {
- // external url requires username, only add url IF we have one
- if (!StringUtils.isEmpty(username)) {
- list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
- }
- } else {
- // external url does not require username
- list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
- }
- }
- return list;
- }
-
- protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
- String gitblitUrl = settings.getString(Keys.web.canonicalUrl, null);
- if (StringUtils.isEmpty(gitblitUrl)) {
- gitblitUrl = HttpUtils.getGitblitURL(request);
- }
- StringBuilder sb = new StringBuilder();
- sb.append(gitblitUrl);
- sb.append(Constants.R_PATH);
- sb.append(repository.name);
-
- // inject username into repository url if authentication is required
- if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
- && !StringUtils.isEmpty(username)) {
- sb.insert(sb.indexOf("://") + 3, username + "@");
- }
- return sb.toString();
- }
-
-
- /**
* Returns the list of custom client applications to be used for the
* repository url panel;
*
@@ -492,7 +457,7 @@
// Read bundled Gitblit properties to extract setting descriptions.
// This copy is pristine and only used for populating the setting
// models map.
- InputStream is = GitblitManager.class.getResourceAsStream("/reference.properties");
+ InputStream is = GitblitManager.class.getResourceAsStream("/defaults.properties");
BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is));
StringBuilder description = new StringBuilder();
SettingModel setting = new SettingModel();
@@ -537,24 +502,20 @@
}
propertiesReader.close();
} catch (NullPointerException e) {
- logger.error("Failed to find resource copy of gitblit.properties");
+ logger.error("Failed to find classpath resource 'defaults.properties'");
} catch (IOException e) {
- logger.error("Failed to load resource copy of gitblit.properties");
+ logger.error("Failed to load classpath resource 'defaults.properties'");
}
}
- /**
- * Throws an exception if trying to get a ticket service.
- *
- */
@Override
public ITicketService getTicketService() {
- throw new RuntimeException("This class does not have a ticket service!");
+ return ticketServiceProvider.get();
}
@Override
public IPublicKeyManager getPublicKeyManager() {
- return publicKeyManager;
+ return publicKeyManagerProvider.get();
}
/*
@@ -605,26 +566,6 @@
}
@Override
- public boolean isServingRepositories() {
- return runtimeManager.isServingRepositories();
- }
-
- @Override
- public boolean isServingHTTP() {
- return runtimeManager.isServingHTTP();
- }
-
- @Override
- public boolean isServingGIT() {
- return runtimeManager.isServingGIT();
- }
-
- @Override
- public boolean isServingSSH() {
- return runtimeManager.isServingSSH();
- }
-
- @Override
public TimeZone getTimezone() {
return runtimeManager.getTimezone();
}
@@ -665,6 +606,11 @@
}
@Override
+ public Injector getInjector() {
+ return runtimeManager.getInjector();
+ }
+
+ @Override
public XssFilter getXssFilter() {
return runtimeManager.getXssFilter();
}
@@ -703,8 +649,8 @@
*/
@Override
- public UserModel authenticate(String username, char[] password) {
- return authenticationManager.authenticate(username, password);
+ public UserModel authenticate(String username, char[] password, String remoteIP) {
+ return authenticationManager.authenticate(username, password, remoteIP);
}
@Override
@@ -722,6 +668,11 @@
}
@Override
+ public UserModel authenticate(String username) {
+ return authenticationManager.authenticate(username);
+ }
+
+ @Override
public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
UserModel user = authenticationManager.authenticate(httpRequest, requiresCertificate);
if (user == null) {
@@ -782,6 +733,16 @@
return authenticationManager.supportsTeamMembershipChanges(team);
}
+ @Override
+ public boolean supportsRoleChanges(UserModel user, Role role) {
+ return authenticationManager.supportsRoleChanges(user, role);
+ }
+
+ @Override
+ public boolean supportsRoleChanges(TeamModel team, Role role) {
+ return authenticationManager.supportsRoleChanges(team, role);
+ }
+
/*
* USER MANAGER
*/
@@ -806,11 +767,6 @@
}
@Override
- public boolean deleteUser(String username) {
- return userManager.deleteUser(username);
- }
-
- @Override
public UserModel getUserModel(String username) {
return userManager.getUserModel(username);
}
@@ -851,8 +807,22 @@
}
@Override
+ public boolean deleteUser(String username) {
+ // delegate to deleteUserModel() to delete public ssh keys
+ UserModel user = userManager.getUserModel(username);
+ return deleteUserModel(user);
+ }
+
+ /**
+ * Delete the user and all associated public ssh keys.
+ */
+ @Override
public boolean deleteUserModel(UserModel model) {
- return userManager.deleteUserModel(model);
+ boolean success = userManager.deleteUserModel(model);
+ if (success) {
+ getPublicKeyManager().removeAllKeys(model.username);
+ }
+ return success;
}
@Override
@@ -1053,10 +1023,23 @@
return repositoryManager.getRepositoryDefaultMetrics(model, repository);
}
+ /**
+ * Detect renames and reindex as appropriate.
+ */
@Override
public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
boolean isCreate) throws GitBlitException {
+ RepositoryModel oldModel = null;
+ boolean isRename = !isCreate && !repositoryName.equalsIgnoreCase(repository.name);
+ if (isRename) {
+ oldModel = repositoryManager.getRepositoryModel(repositoryName);
+ }
+
repositoryManager.updateRepositoryModel(repositoryName, repository, isCreate);
+
+ if (isRename && ticketServiceProvider.get() != null) {
+ ticketServiceProvider.get().rename(oldModel, repository);
+ }
}
@Override
@@ -1069,14 +1052,23 @@
return repositoryManager.canDelete(model);
}
+ /**
+ * Delete the repository and all associated tickets.
+ */
@Override
public boolean deleteRepositoryModel(RepositoryModel model) {
- return repositoryManager.deleteRepositoryModel(model);
+ boolean success = repositoryManager.deleteRepositoryModel(model);
+ if (success && ticketServiceProvider.get() != null) {
+ ticketServiceProvider.get().deleteAll(model);
+ }
+ return success;
}
@Override
public boolean deleteRepository(String repositoryName) {
- return repositoryManager.deleteRepository(repositoryName);
+ // delegate to deleteRepositoryModel() to destroy indexed tickets
+ RepositoryModel repository = repositoryManager.getRepositoryModel(repositoryName);
+ return deleteRepositoryModel(repository);
}
@Override
@@ -1253,6 +1245,70 @@
}
/*
+ * FILE STORAGE MANAGER
+ */
+
+ @Override
+ public boolean isValidOid(String oid) {
+ return filestoreManager.isValidOid(oid);
+ }
+
+ @Override
+ public FilestoreModel.Status addObject(String oid, long size, UserModel user, RepositoryModel repo) {
+ return filestoreManager.addObject(oid, size, user, repo);
+ }
+
+ @Override
+ public FilestoreModel getObject(String oid, UserModel user, RepositoryModel repo) {
+ return filestoreManager.getObject(oid, user, repo);
+ };
+
+ @Override
+ public FilestoreModel.Status uploadBlob(String oid, long size, UserModel user, RepositoryModel repo, InputStream streamIn ) {
+ return filestoreManager.uploadBlob(oid, size, user, repo, streamIn);
+ }
+
+ @Override
+ public FilestoreModel.Status downloadBlob(String oid, UserModel user, RepositoryModel repo, OutputStream streamOut ) {
+ return filestoreManager.downloadBlob(oid, user, repo, streamOut);
+ }
+
+ @Override
+ public List<FilestoreModel> getAllObjects(UserModel user) {
+ return filestoreManager.getAllObjects(user);
+ }
+
+ @Override
+ public File getStorageFolder() {
+ return filestoreManager.getStorageFolder();
+ }
+
+ @Override
+ public File getStoragePath(String oid) {
+ return filestoreManager.getStoragePath(oid);
+ }
+
+ @Override
+ public long getMaxUploadSize() {
+ return filestoreManager.getMaxUploadSize();
+ };
+
+ @Override
+ public void clearFilestoreCache() {
+ filestoreManager.clearFilestoreCache();
+ };
+
+ @Override
+ public long getFilestoreUsedByteCount() {
+ return filestoreManager.getFilestoreUsedByteCount();
+ };
+
+ @Override
+ public long getFilestoreAvailableByteCount() {
+ return filestoreManager.getFilestoreAvailableByteCount();
+ };
+
+ /*
* PLUGIN MANAGER
*/
@@ -1355,4 +1411,5 @@
public PluginRelease lookupRelease(String pluginId, String version) {
return pluginManager.lookupRelease(pluginId, version);
}
+
}
diff --git a/src/main/java/com/gitblit/manager/IAuthenticationManager.java b/src/main/java/com/gitblit/manager/IAuthenticationManager.java
index 3600b32..5406a79 100644
--- a/src/main/java/com/gitblit/manager/IAuthenticationManager.java
+++ b/src/main/java/com/gitblit/manager/IAuthenticationManager.java
@@ -18,6 +18,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import com.gitblit.Constants.Role;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.transport.ssh.SshKey;
@@ -64,10 +65,21 @@
* @see IUserService.authenticate(String, char[])
* @param username
* @param password
+ * @param remoteIP
* @return a user object or null
* @since 1.4.0
*/
- UserModel authenticate(String username, char[] password);
+ UserModel authenticate(String username, char[] password, String remoteIP);
+
+ /**
+ * Return the UserModel for already authenticated user.
+ *
+ * @see IUserService.authenticate(String, char[])
+ * @param username
+ * @return a user object or null
+ * @since 1.7.0
+ */
+ UserModel authenticate(String username);
/**
* Returns the Gitlbit cookie in the request.
@@ -161,4 +173,22 @@
*/
boolean supportsTeamMembershipChanges(TeamModel team);
+ /**
+ * Returns true if the specified role can be changed.
+ *
+ * @param user
+ * @return true if the specified role can be changed
+ * @since 1.6.1
+ */
+ boolean supportsRoleChanges(UserModel user, Role role);
+
+ /**
+ * Returns true if the specified role can be changed.
+ *
+ * @param team
+ * @return true if the specified role can be changed
+ * @since 1.6.1
+ */
+ boolean supportsRoleChanges(TeamModel team, Role role);
+
}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/manager/IFilestoreManager.java b/src/main/java/com/gitblit/manager/IFilestoreManager.java
new file mode 100644
index 0000000..454331a
--- /dev/null
+++ b/src/main/java/com/gitblit/manager/IFilestoreManager.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.manager;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+import com.gitblit.models.FilestoreModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+
+
+public interface IFilestoreManager extends IManager {
+
+ boolean isValidOid(String oid);
+
+ FilestoreModel.Status addObject(String oid, long size, UserModel user, RepositoryModel repo);
+
+ FilestoreModel getObject(String oid, UserModel user, RepositoryModel repo);
+
+ FilestoreModel.Status uploadBlob(String oid, long size, UserModel user, RepositoryModel repo, InputStream streamIn );
+
+ FilestoreModel.Status downloadBlob(String oid, UserModel user, RepositoryModel repo, OutputStream streamOut );
+
+ List<FilestoreModel> getAllObjects(UserModel user);
+
+ File getStorageFolder();
+
+ File getStoragePath(String oid);
+
+ long getMaxUploadSize();
+
+ void clearFilestoreCache();
+
+ long getFilestoreUsedByteCount();
+
+ long getFilestoreAvailableByteCount();
+
+}
diff --git a/src/main/java/com/gitblit/manager/IGitblit.java b/src/main/java/com/gitblit/manager/IGitblit.java
index 50ec8b1..489de62 100644
--- a/src/main/java/com/gitblit/manager/IGitblit.java
+++ b/src/main/java/com/gitblit/manager/IGitblit.java
@@ -16,14 +16,10 @@
package com.gitblit.manager;
import java.util.Collection;
-import java.util.List;
-
-import javax.servlet.http.HttpServletRequest;
import com.gitblit.GitBlitException;
import com.gitblit.models.GitClientApplication;
import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.RepositoryUrl;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.tickets.ITicketService;
@@ -37,18 +33,8 @@
IAuthenticationManager,
IRepositoryManager,
IProjectManager,
- IFederationManager {
-
- /**
- * Returns a list of repository URLs and the user access permission.
- *
- * @param request
- * @param user
- * @param repository
- * @return a list of repository urls
- * @since 1.4.0
- */
- List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository);
+ IFederationManager,
+ IFilestoreManager {
/**
* Creates a complete user object.
diff --git a/src/main/java/com/gitblit/manager/IRuntimeManager.java b/src/main/java/com/gitblit/manager/IRuntimeManager.java
index 132534c..2203b7f 100644
--- a/src/main/java/com/gitblit/manager/IRuntimeManager.java
+++ b/src/main/java/com/gitblit/manager/IRuntimeManager.java
@@ -25,9 +25,12 @@
import com.gitblit.models.ServerSettings;
import com.gitblit.models.ServerStatus;
import com.gitblit.utils.XssFilter;
+import com.google.inject.Injector;
public interface IRuntimeManager extends IManager {
+ Injector getInjector();
+
void setBaseFolder(File folder);
File getBaseFolder();
@@ -49,42 +52,6 @@
Locale getLocale();
/**
- * Determine if this Gitblit instance is actively serving git repositories
- * or if it is merely a repository viewer.
- *
- * @return true if Gitblit is serving repositories
- * @since 1.4.0
- */
- boolean isServingRepositories();
-
- /**
- * Determine if this Gitblit instance is actively serving git repositories
- * over HTTP.
- *
- * @return true if Gitblit is serving repositories over HTTP
- * @since 1.6.0
- */
- boolean isServingHTTP();
-
- /**
- * Determine if this Gitblit instance is actively serving git repositories
- * over the GIT Daemon protocol.
- *
- * @return true if Gitblit is serving repositories over the GIT Daemon protocol
- * @since 1.6.0
- */
- boolean isServingGIT();
-
- /**
- * Determine if this Gitblit instance is actively serving git repositories
- * over the SSH protocol.
- *
- * @return true if Gitblit is serving repositories over the SSH protocol
- * @since 1.6.0
- */
- boolean isServingSSH();
-
- /**
* Determine if this Gitblit instance is running in debug mode
*
* @return true if Gitblit is running in debug mode
diff --git a/src/main/java/com/gitblit/manager/IServicesManager.java b/src/main/java/com/gitblit/manager/IServicesManager.java
new file mode 100644
index 0000000..b3a973b
--- /dev/null
+++ b/src/main/java/com/gitblit/manager/IServicesManager.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.manager;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.gitblit.Constants.Transport;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.RepositoryUrl;
+import com.gitblit.models.UserModel;
+
+public interface IServicesManager extends IManager {
+
+ /**
+ * Determine if this Gitblit instance is actively serving git repositories
+ * or if it is merely a repository viewer.
+ *
+ * @return true if Gitblit is serving repositories
+ * @since 1.7.0
+ */
+ boolean isServingRepositories();
+
+ /**
+ * Determine if this Gitblit instance is actively serving git repositories
+ * over HTTP.
+ *
+ * @return true if Gitblit is serving repositories over HTTP
+ * @since 1.7.0
+ */
+ boolean isServingHTTP();
+
+ /**
+ * Determine if this Gitblit instance is actively serving git repositories
+ * over HTTP.
+ *
+ * @return true if Gitblit is serving repositories over HTTPS
+ * @since 1.7.0
+ */
+ boolean isServingHTTPS();
+
+ /**
+ * Determine if this Gitblit instance is actively serving git repositories
+ * over the GIT Daemon protocol.
+ *
+ * @return true if Gitblit is serving repositories over the GIT Daemon protocol
+ * @since 1.7.0
+ */
+ boolean isServingGIT();
+
+ /**
+ * Determine if this Gitblit instance is actively serving git repositories
+ * over the SSH protocol.
+ *
+ * @return true if Gitblit is serving repositories over the SSH protocol
+ * @since 1.7.0
+ */
+ boolean isServingSSH();
+
+ /**
+ * Returns a list of repository URLs and the user access permission.
+ *
+ * @param request
+ * @param user
+ * @param repository
+ * @return a list of repository urls
+ * @since 1.7.0
+ */
+ List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository);
+
+ /**
+ * Returns true if the transport may be used for pushing.
+ *
+ * @param byTransport
+ * @return true if the transport can be used for pushes.
+ * @since 1.7.0
+ */
+ boolean acceptsPush(Transport byTransport);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/manager/NotificationManager.java b/src/main/java/com/gitblit/manager/NotificationManager.java
index 69a611b..4bbc2ab 100644
--- a/src/main/java/com/gitblit/manager/NotificationManager.java
+++ b/src/main/java/com/gitblit/manager/NotificationManager.java
@@ -29,6 +29,8 @@
import com.gitblit.Keys;
import com.gitblit.models.Mailing;
import com.gitblit.service.MailService;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* The notification manager dispatches notifications. Currently, email is the
@@ -38,6 +40,7 @@
* @author James Moger
*
*/
+@Singleton
public class NotificationManager implements INotificationManager {
private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -48,6 +51,7 @@
private final MailService mailService;
+ @Inject
public NotificationManager(IStoredSettings settings) {
this.settings = settings;
this.mailService = new MailService(settings);
diff --git a/src/main/java/com/gitblit/manager/PluginManager.java b/src/main/java/com/gitblit/manager/PluginManager.java
index 5e25caa..b3936e5 100644
--- a/src/main/java/com/gitblit/manager/PluginManager.java
+++ b/src/main/java/com/gitblit/manager/PluginManager.java
@@ -15,13 +15,15 @@
*/
package com.gitblit.manager;
-import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
@@ -37,8 +39,12 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import ro.fortsoft.pf4j.DefaultPluginFactory;
import ro.fortsoft.pf4j.DefaultPluginManager;
+import ro.fortsoft.pf4j.ExtensionFactory;
+import ro.fortsoft.pf4j.Plugin;
import ro.fortsoft.pf4j.PluginClassLoader;
+import ro.fortsoft.pf4j.PluginFactory;
import ro.fortsoft.pf4j.PluginState;
import ro.fortsoft.pf4j.PluginStateEvent;
import ro.fortsoft.pf4j.PluginStateListener;
@@ -56,8 +62,9 @@
import com.gitblit.utils.FileUtils;
import com.gitblit.utils.JsonUtils;
import com.gitblit.utils.StringUtils;
-import com.google.common.io.Files;
-import com.google.common.io.InputSupplier;
+import com.google.common.io.ByteStreams;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* The plugin manager maintains the lifecycle of plugins. It is exposed as
@@ -68,32 +75,23 @@
* @author James Moger
*
*/
+@Singleton
public class PluginManager implements IPluginManager, PluginStateListener {
private final Logger logger = LoggerFactory.getLogger(getClass());
- private final DefaultPluginManager pf4j;
-
private final IRuntimeManager runtimeManager;
+ private DefaultPluginManager pf4j;
+
// timeout defaults of Maven 3.0.4 in seconds
private int connectTimeout = 20;
private int readTimeout = 12800;
+ @Inject
public PluginManager(IRuntimeManager runtimeManager) {
- File dir = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins");
- dir.mkdirs();
this.runtimeManager = runtimeManager;
-
- this.pf4j = new DefaultPluginManager(dir);
-
- try {
- Version systemVersion = Version.createVersion(Constants.getVersion());
- pf4j.setSystemVersion(systemVersion);
- } catch (Exception e) {
- logger.error(null, e);
- }
}
@Override
@@ -108,6 +106,28 @@
@Override
public PluginManager start() {
+ File dir = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins");
+ dir.mkdirs();
+
+ pf4j = new DefaultPluginManager(dir) {
+
+ @Override
+ protected PluginFactory createPluginFactory() {
+ return new GuicePluginFactory();
+ }
+
+ @Override
+ protected ExtensionFactory createExtensionFactory() {
+ return new GuiceExtensionFactory();
+ }
+ };
+
+ try {
+ Version systemVersion = Version.createVersion(Constants.getVersion());
+ pf4j.setSystemVersion(systemVersion);
+ } catch (Exception e) {
+ logger.error(null, e);
+ }
pf4j.loadPlugins();
logger.debug("Starting plugins");
pf4j.startPlugins();
@@ -438,7 +458,7 @@
}
- if (sha1File == null && md5File == null && verifyChecksum) {
+ if (sha1File == null && md5File == null) {
throw new IOException("Missing SHA1 and MD5 checksums for " + url);
}
@@ -513,12 +533,9 @@
// try to get the server-specified last-modified date of this artifact
long lastModified = conn.getHeaderFieldDate("Last-Modified", System.currentTimeMillis());
- Files.copy(new InputSupplier<InputStream>() {
- @Override
- public InputStream getInput() throws IOException {
- return new BufferedInputStream(conn.getInputStream());
- }
- }, tmpFile);
+ try (InputStream is = conn.getInputStream(); OutputStream os = new FileOutputStream(tmpFile);) {
+ ByteStreams.copy(is, os);
+ }
File destFile = new File(pFolder, StringUtils.getLastPathElement(u.getPath()));
if (destFile.exists()) {
@@ -567,10 +584,55 @@
}
protected Proxy getProxy(URL url) {
- return java.net.Proxy.NO_PROXY;
+ String proxyHost = runtimeManager.getSettings().getString(Keys.plugins.httpProxyHost, "");
+ String proxyPort = runtimeManager.getSettings().getString(Keys.plugins.httpProxyPort, "");
+
+ if (!StringUtils.isEmpty(proxyHost) && !StringUtils.isEmpty(proxyPort)) {
+ return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, Integer.parseInt(proxyPort)));
+ } else {
+ return java.net.Proxy.NO_PROXY;
+ }
}
protected String getProxyAuthorization(URL url) {
- return "";
+ String proxyAuth = runtimeManager.getSettings().getString(Keys.plugins.httpProxyAuthorization, "");
+ return proxyAuth;
+ }
+
+ /**
+ * Instantiates a plugin using pf4j but injects member fields
+ * with Guice.
+ */
+ private class GuicePluginFactory extends DefaultPluginFactory {
+
+ @Override
+ public Plugin create(PluginWrapper pluginWrapper) {
+ // use pf4j to create the plugin
+ Plugin plugin = super.create(pluginWrapper);
+
+ if (plugin != null) {
+ // allow Guice to inject member fields
+ runtimeManager.getInjector().injectMembers(plugin);
+ }
+
+ return plugin;
+ }
+ }
+
+ /**
+ * Instantiates an extension using Guice.
+ */
+ private class GuiceExtensionFactory implements ExtensionFactory {
+ @Override
+ public Object create(Class<?> extensionClass) {
+ // instantiate && inject the extension
+ logger.debug("Create instance for extension '{}'", extensionClass.getName());
+ try {
+ return runtimeManager.getInjector().getInstance(extensionClass);
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ }
+ return null;
+ }
}
}
diff --git a/src/main/java/com/gitblit/manager/ProjectManager.java b/src/main/java/com/gitblit/manager/ProjectManager.java
index 666f521..ae46bdf 100644
--- a/src/main/java/com/gitblit/manager/ProjectManager.java
+++ b/src/main/java/com/gitblit/manager/ProjectManager.java
@@ -41,6 +41,8 @@
import com.gitblit.utils.ModelUtils;
import com.gitblit.utils.ObjectCache;
import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* Project manager handles project-related functions.
@@ -48,6 +50,7 @@
* @author James Moger
*
*/
+@Singleton
public class ProjectManager implements IProjectManager {
private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -68,6 +71,7 @@
private FileBasedConfig projectConfigs;
+ @Inject
public ProjectManager(
IRuntimeManager runtimeManager,
IUserManager userManager,
diff --git a/src/main/java/com/gitblit/manager/RepositoryManager.java b/src/main/java/com/gitblit/manager/RepositoryManager.java
index 2db4132..e9bf5b8 100644
--- a/src/main/java/com/gitblit/manager/RepositoryManager.java
+++ b/src/main/java/com/gitblit/manager/RepositoryManager.java
@@ -91,6 +91,8 @@
import com.gitblit.utils.ObjectCache;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* Repository manager creates, updates, deletes and caches git repositories. It
@@ -99,6 +101,7 @@
* @author James Moger
*
*/
+@Singleton
public class RepositoryManager implements IRepositoryManager {
private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -121,7 +124,7 @@
private final IUserManager userManager;
- private final File repositoriesFolder;
+ private File repositoriesFolder;
private LuceneService luceneExecutor;
@@ -129,6 +132,7 @@
private MirrorService mirrorExecutor;
+ @Inject
public RepositoryManager(
IRuntimeManager runtimeManager,
IPluginManager pluginManager,
@@ -138,11 +142,11 @@
this.runtimeManager = runtimeManager;
this.pluginManager = pluginManager;
this.userManager = userManager;
- this.repositoriesFolder = runtimeManager.getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
}
@Override
public RepositoryManager start() {
+ repositoriesFolder = runtimeManager.getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
logger.info("Repositories folder : {}", repositoriesFolder.getAbsolutePath());
// initialize utilities
@@ -1109,9 +1113,16 @@
// find the root, cached
String key = getRepositoryKey(repository);
RepositoryModel model = repositoryListCache.get(key);
+ if (model == null) {
+ return null;
+ }
+
while (model.originRepository != null) {
String originKey = getRepositoryKey(model.originRepository);
model = repositoryListCache.get(originKey);
+ if (model == null) {
+ return null;
+ }
}
ForkModel root = getForkModelFromCache(model.name);
return root;
@@ -1343,7 +1354,7 @@
}
/**
- * Creates/updates the repository model keyed by reopsitoryName. Saves all
+ * Creates/updates the repository model keyed by repositoryName. Saves all
* repository settings in .git/config. This method allows for renaming
* repositories and will update user access permissions accordingly.
*
@@ -1371,6 +1382,7 @@
repository.name = repository.name.substring(projectPath.length() + 1);
}
}
+ boolean isRename = false;
if (isCreate) {
// ensure created repository name ends with .git
if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
@@ -1387,7 +1399,8 @@
r = JGitUtils.createRepository(repositoriesFolder, repository.name, shared);
} else {
// rename repository
- if (!repositoryName.equalsIgnoreCase(repository.name)) {
+ isRename = !repositoryName.equalsIgnoreCase(repository.name);
+ if (isRename) {
if (!repository.name.toLowerCase().endsWith(
org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
@@ -1507,6 +1520,14 @@
logger.error(String.format("failed to call plugin onCreation %s", repositoryName), t);
}
}
+ } else if (isRename && pluginManager != null) {
+ for (RepositoryLifeCycleListener listener : pluginManager.getExtensions(RepositoryLifeCycleListener.class)) {
+ try {
+ listener.onRename(repositoryName, repository);
+ } catch (Throwable t) {
+ logger.error(String.format("failed to call plugin onRename %s", repositoryName), t);
+ }
+ }
}
}
@@ -1901,7 +1922,7 @@
cfg.setPackedGitWindowSize(settings.getFilesize(Keys.git.packedGitWindowSize, cfg.getPackedGitWindowSize()));
cfg.setPackedGitLimit(settings.getFilesize(Keys.git.packedGitLimit, cfg.getPackedGitLimit()));
cfg.setDeltaBaseCacheLimit(settings.getFilesize(Keys.git.deltaBaseCacheLimit, cfg.getDeltaBaseCacheLimit()));
- cfg.setPackedGitOpenFiles(settings.getFilesize(Keys.git.packedGitOpenFiles, cfg.getPackedGitOpenFiles()));
+ cfg.setPackedGitOpenFiles(settings.getInteger(Keys.git.packedGitOpenFiles, cfg.getPackedGitOpenFiles()));
cfg.setPackedGitMMAP(settings.getBoolean(Keys.git.packedGitMmap, cfg.isPackedGitMMAP()));
try {
@@ -1967,21 +1988,19 @@
}
protected void confirmWriteAccess() {
- if (runtimeManager.isServingRepositories()) {
- try {
- if (!getRepositoriesFolder().exists()) {
- getRepositoriesFolder().mkdirs();
- }
- File file = File.createTempFile(".test-", ".txt", getRepositoriesFolder());
- file.delete();
- } catch (Exception e) {
- logger.error("");
- logger.error(Constants.BORDER2);
- logger.error("Please check filesystem permissions!");
- logger.error("FAILED TO WRITE TO REPOSITORIES FOLDER!!", e);
- logger.error(Constants.BORDER2);
- logger.error("");
+ try {
+ if (!getRepositoriesFolder().exists()) {
+ getRepositoriesFolder().mkdirs();
}
+ File file = File.createTempFile(".test-", ".txt", getRepositoriesFolder());
+ file.delete();
+ } catch (Exception e) {
+ logger.error("");
+ logger.error(Constants.BORDER2);
+ logger.error("Please check filesystem permissions!");
+ logger.error("FAILED TO WRITE TO REPOSITORIES FOLDER!!", e);
+ logger.error(Constants.BORDER2);
+ logger.error("");
}
}
}
diff --git a/src/main/java/com/gitblit/manager/RuntimeManager.java b/src/main/java/com/gitblit/manager/RuntimeManager.java
index 219bf80..18d6b9c 100644
--- a/src/main/java/com/gitblit/manager/RuntimeManager.java
+++ b/src/main/java/com/gitblit/manager/RuntimeManager.java
@@ -33,7 +33,11 @@
import com.gitblit.models.SettingModel;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.XssFilter;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Singleton;
+@Singleton
public class RuntimeManager implements IRuntimeManager {
private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -50,6 +54,10 @@
private TimeZone timezone;
+ @Inject
+ private Injector injector;
+
+ @Inject
public RuntimeManager(IStoredSettings settings, XssFilter xssFilter) {
this(settings, xssFilter, null);
}
@@ -79,6 +87,11 @@
}
@Override
+ public Injector getInjector() {
+ return injector;
+ }
+
+ @Override
public File getBaseFolder() {
return baseFolder;
}
@@ -116,52 +129,6 @@
}
/**
- * Determine if this Gitblit instance is actively serving git repositories
- * or if it is merely a repository viewer.
- *
- * @return true if Gitblit is serving repositories
- */
- @Override
- public boolean isServingRepositories() {
- return isServingHTTP()
- || isServingGIT()
- || isServingSSH();
- }
-
- /**
- * Determine if this Gitblit instance is actively serving git repositories
- * over the HTTP protocol.
- *
- * @return true if Gitblit is serving repositories over the HTTP protocol
- */
- @Override
- public boolean isServingHTTP() {
- return settings.getBoolean(Keys.git.enableGitServlet, true);
- }
-
- /**
- * Determine if this Gitblit instance is actively serving git repositories
- * over the Git Daemon protocol.
- *
- * @return true if Gitblit is serving repositories over the Git Daemon protocol
- */
- @Override
- public boolean isServingGIT() {
- return settings.getInteger(Keys.git.daemonPort, 0) > 0;
- }
-
- /**
- * Determine if this Gitblit instance is actively serving git repositories
- * over the SSH protocol.
- *
- * @return true if Gitblit is serving repositories over the SSH protocol
- */
- @Override
- public boolean isServingSSH() {
- return settings.getInteger(Keys.git.sshPort, 0) > 0;
- }
-
- /**
* Returns the preferred timezone for the Gitblit instance.
*
* @return a timezone
diff --git a/src/main/java/com/gitblit/manager/ServicesManager.java b/src/main/java/com/gitblit/manager/ServicesManager.java
index 437fd10..b993eb6 100644
--- a/src/main/java/com/gitblit/manager/ServicesManager.java
+++ b/src/main/java/com/gitblit/manager/ServicesManager.java
@@ -18,9 +18,15 @@
import java.io.IOException;
import java.net.URI;
import java.text.MessageFormat;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -30,9 +36,11 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.gitblit.Constants;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.FederationToken;
+import com.gitblit.Constants.Transport;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.fanout.FanoutNioService;
@@ -40,14 +48,18 @@
import com.gitblit.fanout.FanoutSocketService;
import com.gitblit.models.FederationModel;
import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.RepositoryUrl;
import com.gitblit.models.UserModel;
import com.gitblit.service.FederationPullService;
import com.gitblit.transport.git.GitDaemon;
import com.gitblit.transport.ssh.SshDaemon;
-import com.gitblit.utils.IdGenerator;
+import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.utils.WorkQueue;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
/**
* Services manager manages long-running services/processes that either have no
@@ -57,32 +69,35 @@
* @author James Moger
*
*/
-public class ServicesManager implements IManager {
+@Singleton
+public class ServicesManager implements IServicesManager {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);
+ private final Provider<WorkQueue> workQueueProvider;
+
private final IStoredSettings settings;
private final IGitblit gitblit;
- private final IdGenerator idGenerator;
-
- private final WorkQueue workQueue;
-
private FanoutService fanoutService;
private GitDaemon gitDaemon;
private SshDaemon sshDaemon;
- public ServicesManager(IGitblit gitblit) {
- this.settings = gitblit.getSettings();
+ @Inject
+ public ServicesManager(
+ Provider<WorkQueue> workQueueProvider,
+ IStoredSettings settings,
+ IGitblit gitblit) {
+
+ this.workQueueProvider = workQueueProvider;
+
+ this.settings = settings;
this.gitblit = gitblit;
- int defaultThreadPoolSize = settings.getInteger(Keys.execution.defaultThreadPoolSize, 1);
- this.idGenerator = new IdGenerator();
- this.workQueue = new WorkQueue(idGenerator, defaultThreadPoolSize);
}
@Override
@@ -107,24 +122,217 @@
if (sshDaemon != null) {
sshDaemon.stop();
}
- workQueue.stop();
+ workQueueProvider.get().stop();
return this;
}
+ protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
+ String gitblitUrl = settings.getString(Keys.web.canonicalUrl, null);
+ if (StringUtils.isEmpty(gitblitUrl)) {
+ gitblitUrl = HttpUtils.getGitblitURL(request);
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append(gitblitUrl);
+ sb.append(Constants.R_PATH);
+ sb.append(repository.name);
+
+ // inject username into repository url if authentication is required
+ if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
+ && !StringUtils.isEmpty(username)) {
+ sb.insert(sb.indexOf("://") + 3, username + "@");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns a list of repository URLs and the user access permission.
+ *
+ * @param request
+ * @param user
+ * @param repository
+ * @return a list of repository urls
+ */
+ @Override
+ public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
+ if (user == null) {
+ user = UserModel.ANONYMOUS;
+ }
+ String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
+
+ List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
+
+ // http/https url
+ if (settings.getBoolean(Keys.git.enableGitServlet, true) &&
+ settings.getBoolean(Keys.web.showHttpServletUrls, true)) {
+ AccessPermission permission = user.getRepositoryPermission(repository).permission;
+ if (permission.exceeds(AccessPermission.NONE)) {
+ String repoUrl = getRepositoryUrl(request, username, repository);
+ Transport transport = Transport.fromUrl(repoUrl);
+ if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(transport)) {
+ // downgrade the repo permission for this transport
+ // because it is not an acceptable PUSH transport
+ permission = AccessPermission.CLONE;
+ }
+ list.add(new RepositoryUrl(repoUrl, permission));
+ }
+ }
+
+ // ssh daemon url
+ String sshDaemonUrl = getSshDaemonUrl(request, user, repository);
+ if (!StringUtils.isEmpty(sshDaemonUrl) &&
+ settings.getBoolean(Keys.web.showSshDaemonUrls, true)) {
+ AccessPermission permission = user.getRepositoryPermission(repository).permission;
+ if (permission.exceeds(AccessPermission.NONE)) {
+ if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(Transport.SSH)) {
+ // downgrade the repo permission for this transport
+ // because it is not an acceptable PUSH transport
+ permission = AccessPermission.CLONE;
+ }
+
+ list.add(new RepositoryUrl(sshDaemonUrl, permission));
+ }
+ }
+
+ // git daemon url
+ String gitDaemonUrl = getGitDaemonUrl(request, user, repository);
+ if (!StringUtils.isEmpty(gitDaemonUrl) &&
+ settings.getBoolean(Keys.web.showGitDaemonUrls, true)) {
+ AccessPermission permission = getGitDaemonAccessPermission(user, repository);
+ if (permission.exceeds(AccessPermission.NONE)) {
+ if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(Transport.GIT)) {
+ // downgrade the repo permission for this transport
+ // because it is not an acceptable PUSH transport
+ permission = AccessPermission.CLONE;
+ }
+ list.add(new RepositoryUrl(gitDaemonUrl, permission));
+ }
+ }
+
+ // add all other urls
+ // {0} = repository
+ // {1} = username
+ boolean advertisePermsForOther = settings.getBoolean(Keys.web.advertiseAccessPermissionForOtherUrls, false);
+ for (String url : settings.getStrings(Keys.web.otherUrls)) {
+ String externalUrl = null;
+
+ if (url.contains("{1}")) {
+ // external url requires username, only add url IF we have one
+ if (StringUtils.isEmpty(username)) {
+ continue;
+ } else {
+ externalUrl = MessageFormat.format(url, repository.name, username);
+ }
+ } else {
+ // external url does not require username, just do repo name formatting
+ externalUrl = MessageFormat.format(url, repository.name);
+ }
+
+ AccessPermission permission = null;
+ if (advertisePermsForOther) {
+ permission = user.getRepositoryPermission(repository).permission;
+ if (permission.exceeds(AccessPermission.NONE)) {
+ Transport transport = Transport.fromUrl(externalUrl);
+ if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(transport)) {
+ // downgrade the repo permission for this transport
+ // because it is not an acceptable PUSH transport
+ permission = AccessPermission.CLONE;
+ }
+ }
+ }
+ list.add(new RepositoryUrl(externalUrl, permission));
+ }
+
+ // sort transports by highest permission and then by transport security
+ Collections.sort(list, new Comparator<RepositoryUrl>() {
+
+ @Override
+ public int compare(RepositoryUrl o1, RepositoryUrl o2) {
+ if (o1.hasPermission() && !o2.hasPermission()) {
+ // prefer known permission items over unknown
+ return -1;
+ } else if (!o1.hasPermission() && o2.hasPermission()) {
+ // prefer known permission items over unknown
+ return 1;
+ } else if (!o1.hasPermission() && !o2.hasPermission()) {
+ // sort by Transport ordinal
+ return o1.transport.compareTo(o2.transport);
+ } else if (o1.permission.exceeds(o2.permission)) {
+ // prefer highest permission
+ return -1;
+ } else if (o2.permission.exceeds(o1.permission)) {
+ // prefer highest permission
+ return 1;
+ }
+
+ // prefer more secure transports
+ return o1.transport.compareTo(o2.transport);
+ }
+ });
+
+ // consider the user's transport preference
+ RepositoryUrl preferredUrl = null;
+ Transport preferredTransport = user.getPreferences().getTransport();
+ if (preferredTransport != null) {
+ Iterator<RepositoryUrl> itr = list.iterator();
+ while (itr.hasNext()) {
+ RepositoryUrl url = itr.next();
+ if (url.transport.equals(preferredTransport)) {
+ itr.remove();
+ preferredUrl = url;
+ break;
+ }
+ }
+ }
+ if (preferredUrl != null) {
+ list.add(0, preferredUrl);
+ }
+
+ return list;
+ }
+
+ /* (non-Javadoc)
+ * @see com.gitblit.manager.IServicesManager#isServingRepositories()
+ */
+ @Override
public boolean isServingRepositories() {
- return isServingHTTP()
+ return isServingHTTPS()
+ || isServingHTTP()
|| isServingGIT()
|| isServingSSH();
}
+ /* (non-Javadoc)
+ * @see com.gitblit.manager.IServicesManager#isServingHTTP()
+ */
+ @Override
public boolean isServingHTTP() {
- return settings.getBoolean(Keys.git.enableGitServlet, true);
+ return settings.getBoolean(Keys.git.enableGitServlet, true)
+ && ((gitblit.getStatus().isGO && settings.getInteger(Keys.server.httpPort, 0) > 0)
+ || !gitblit.getStatus().isGO);
}
+ /* (non-Javadoc)
+ * @see com.gitblit.manager.IServicesManager#isServingHTTPS()
+ */
+ @Override
+ public boolean isServingHTTPS() {
+ return settings.getBoolean(Keys.git.enableGitServlet, true)
+ && ((gitblit.getStatus().isGO && settings.getInteger(Keys.server.httpsPort, 0) > 0)
+ || !gitblit.getStatus().isGO);
+ }
+
+ /* (non-Javadoc)
+ * @see com.gitblit.manager.IServicesManager#isServingGIT()
+ */
+ @Override
public boolean isServingGIT() {
return gitDaemon != null && gitDaemon.isRunning();
}
+ /* (non-Javadoc)
+ * @see com.gitblit.manager.IServicesManager#isServingSSH()
+ */
+ @Override
public boolean isServingSSH() {
return sshDaemon != null && sshDaemon.isRunning();
}
@@ -158,6 +366,33 @@
}
}
+ @Override
+ public boolean acceptsPush(Transport byTransport) {
+ if (byTransport == null) {
+ logger.info("Unknown transport, push rejected!");
+ return false;
+ }
+
+ Set<Transport> transports = new HashSet<Transport>();
+ for (String value : settings.getStrings(Keys.git.acceptedPushTransports)) {
+ Transport transport = Transport.fromString(value);
+ if (transport == null) {
+ logger.info(String.format("Ignoring unknown registered transport %s", value));
+ continue;
+ }
+
+ transports.add(transport);
+ }
+
+ if (transports.isEmpty()) {
+ // no transports are explicitly specified, all are acceptable
+ return true;
+ }
+
+ // verify that the transport is permitted
+ return transports.contains(byTransport);
+ }
+
protected void configureGitDaemon() {
int port = settings.getInteger(Keys.git.daemonPort, 0);
String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
@@ -179,7 +414,7 @@
String bindInterface = settings.getString(Keys.git.sshBindInterface, "localhost");
if (port > 0) {
try {
- sshDaemon = new SshDaemon(gitblit, workQueue);
+ sshDaemon = new SshDaemon(gitblit, workQueueProvider.get());
sshDaemon.start();
} catch (IOException e) {
sshDaemon = null;
@@ -285,7 +520,7 @@
*/
protected String getHostname(HttpServletRequest request) {
String hostname = request.getServerName();
- String canonicalUrl = gitblit.getSettings().getString(Keys.web.canonicalUrl, null);
+ String canonicalUrl = settings.getString(Keys.web.canonicalUrl, null);
if (!StringUtils.isEmpty(canonicalUrl)) {
try {
URI uri = new URI(canonicalUrl);
diff --git a/src/main/java/com/gitblit/manager/UserManager.java b/src/main/java/com/gitblit/manager/UserManager.java
index 2b82ffb..e88ac93 100644
--- a/src/main/java/com/gitblit/manager/UserManager.java
+++ b/src/main/java/com/gitblit/manager/UserManager.java
@@ -36,6 +36,8 @@
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* The user manager manages persistence and retrieval of users and teams.
@@ -43,6 +45,7 @@
* @author James Moger
*
*/
+@Singleton
public class UserManager implements IUserManager {
private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -57,6 +60,7 @@
private IUserService userService;
+ @Inject
public UserManager(IRuntimeManager runtimeManager, IPluginManager pluginManager) {
this.settings = runtimeManager.getSettings();
this.runtimeManager = runtimeManager;
@@ -79,9 +83,9 @@
* @param userService
*/
public void setUserService(IUserService userService) {
- logger.info(userService.toString());
this.userService = userService;
this.userService.setup(runtimeManager);
+ logger.info(userService.toString());
}
@Override
@@ -111,10 +115,12 @@
// check to see if this "file" is a custom user service class
Class<?> realmClass = Class.forName(realm);
service = (IUserService) realmClass.newInstance();
- } catch (Throwable t) {
+ } catch (ClassNotFoundException t) {
// typical file path configuration
File realmFile = runtimeManager.getFileOrFolder(Keys.realm.userService, "${baseFolder}/users.conf");
service = createUserService(realmFile);
+ } catch (InstantiationException | IllegalAccessException e) {
+ logger.error("failed to instantiate user service {}: {}", realm, e.getMessage());
}
}
setUserService(service);
diff --git a/src/main/java/com/gitblit/models/FilestoreModel.java b/src/main/java/com/gitblit/models/FilestoreModel.java
new file mode 100644
index 0000000..40b51e0
--- /dev/null
+++ b/src/main/java/com/gitblit/models/FilestoreModel.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2015 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.models;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.gitblit.Constants;
+
+/**
+ * A FilestoreModel represents a file stored outside a repository but referenced by the repository using a unique objectID
+ *
+ * @author Paul Martin
+ *
+ */
+public class FilestoreModel implements Serializable, Comparable<FilestoreModel> {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String metaRegexText = new StringBuilder()
+ .append("version\\shttps://git-lfs.github.com/spec/v1\\s+")
+ .append("oid\\ssha256:(" + Constants.REGEX_SHA256 + ")\\s+")
+ .append("size\\s([0-9]+)")
+ .toString();
+
+ private static final Pattern metaRegex = Pattern.compile(metaRegexText);
+
+ private static final int metaRegexIndexSHA = 1;
+
+ private static final int metaRegexIndexSize = 2;
+
+ public final String oid;
+
+ private Long size;
+ private Status status;
+
+ //Audit
+ private String stateChangedBy;
+ private Date stateChangedOn;
+
+ //Access Control
+ private List<String> repositories;
+
+ public FilestoreModel(String id, long definedSize) {
+ oid = id;
+ size = definedSize;
+ status = Status.ReferenceOnly;
+ }
+
+ public FilestoreModel(String id, long expectedSize, UserModel user, String repo) {
+ oid = id;
+ size = expectedSize;
+ status = Status.Upload_Pending;
+ stateChangedBy = user.getName();
+ stateChangedOn = new Date();
+ repositories = new ArrayList<String>();
+ repositories.add(repo);
+ }
+
+ /*
+ * Attempts to create a FilestoreModel from the given meta string
+ *
+ * @return A valid FilestoreModel if successful, otherwise null
+ */
+ public static FilestoreModel fromMetaString(String meta) {
+
+ Matcher m = metaRegex.matcher(meta);
+
+ if (m.find()) {
+ try
+ {
+ final Long size = Long.parseLong(m.group(metaRegexIndexSize));
+ final String sha = m.group(metaRegexIndexSHA);
+ return new FilestoreModel(sha, size);
+ } catch (Exception e) {
+ //Fail silent - it is not a valid filestore item
+ }
+ }
+
+ return null;
+ }
+
+ public synchronized long getSize() {
+ return size;
+ }
+
+ public synchronized Status getStatus() {
+ return status;
+ }
+
+ public synchronized String getChangedBy() {
+ return stateChangedBy;
+ }
+
+ public synchronized Date getChangedOn() {
+ return stateChangedOn;
+ }
+
+ public synchronized void setStatus(Status status, UserModel user) {
+ this.status = status;
+ stateChangedBy = user.getName();
+ stateChangedOn = new Date();
+ }
+
+ public synchronized void reset(UserModel user, long size) {
+ status = Status.Upload_Pending;
+ stateChangedBy = user.getName();
+ stateChangedOn = new Date();
+ this.size = size;
+ }
+
+ /*
+ * Handles possible race condition with concurrent connections
+ * @return true if action can proceed, false otherwise
+ */
+ public synchronized boolean actionUpload(UserModel user) {
+ if (status == Status.Upload_Pending) {
+ status = Status.Upload_In_Progress;
+ stateChangedBy = user.getName();
+ stateChangedOn = new Date();
+ return true;
+ }
+
+ return false;
+ }
+
+ public synchronized boolean isInErrorState() {
+ return (this.status.value < 0);
+ }
+
+ public synchronized void addRepository(String repo) {
+ if (status != Status.ReferenceOnly) {
+ if (!repositories.contains(repo)) {
+ repositories.add(repo);
+ }
+ }
+ }
+
+ public synchronized void removeRepository(String repo) {
+ if (status != Status.ReferenceOnly) {
+ repositories.remove(repo);
+ }
+ }
+
+ public synchronized boolean isInRepositoryList(List<String> repoList) {
+ if (status != Status.ReferenceOnly) {
+ for (String name : repositories) {
+ if (repoList.contains(name)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static enum Status {
+
+ ReferenceOnly(-42),
+
+ Deleted(-30),
+ AuthenticationRequired(-20),
+
+ Error_Unknown(-8),
+ Error_Unexpected_Stream_End(-7),
+ Error_Invalid_Oid(-6),
+ Error_Invalid_Size(-5),
+ Error_Hash_Mismatch(-4),
+ Error_Size_Mismatch(-3),
+ Error_Exceeds_Size_Limit(-2),
+ Error_Unauthorized(-1),
+ //Negative values provide additional information and may be treated as 0 when not required
+ Unavailable(0),
+ Upload_Pending(1),
+ Upload_In_Progress(2),
+ Available(3);
+
+ final int value;
+
+ Status(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return name().toLowerCase().replace('_', ' ');
+ }
+
+ public static Status fromState(int state) {
+ for (Status s : values()) {
+ if (s.getValue() == state) {
+ return s;
+ }
+ }
+ throw new NoSuchElementException(String.valueOf(state));
+ }
+ }
+
+ @Override
+ public int compareTo(FilestoreModel o) {
+ return this.oid.compareTo(o.oid);
+ }
+
+}
+
diff --git a/src/main/java/com/gitblit/models/PathModel.java b/src/main/java/com/gitblit/models/PathModel.java
index bf58542..3c280eb 100644
--- a/src/main/java/com/gitblit/models/PathModel.java
+++ b/src/main/java/com/gitblit/models/PathModel.java
@@ -15,11 +15,20 @@
*/
package com.gitblit.models;
+import java.io.IOException;
import java.io.Serializable;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import com.gitblit.manager.FilestoreManager;
+import com.gitblit.utils.JGitUtils;
/**
* PathModel is a serializable model class that represents a file or a folder,
@@ -34,16 +43,18 @@
public final String name;
public final String path;
+ private final FilestoreModel filestoreItem;
public final long size;
public final int mode;
public final String objectId;
public final String commitId;
public boolean isParentPath;
-
- public PathModel(String name, String path, long size, int mode, String objectId, String commitId) {
+
+ public PathModel(String name, String path, FilestoreModel filestoreItem, long size, int mode, String objectId, String commitId) {
this.name = name;
this.path = path;
- this.size = size;
+ this.filestoreItem = filestoreItem;
+ this.size = (filestoreItem == null) ? size : filestoreItem.getSize();
this.mode = mode;
this.objectId = objectId;
this.commitId = commitId;
@@ -66,6 +77,18 @@
|| FileMode.EXECUTABLE_FILE.equals(mode)
|| (FileMode.MISSING.equals(mode) && !isSymlink() && !isSubmodule() && !isTree());
}
+
+ public boolean isFilestoreItem() {
+ return filestoreItem != null;
+ }
+
+ public String getFilestoreOid() {
+ if (filestoreItem != null) {
+ return filestoreItem.oid;
+ }
+
+ return null;
+ }
@Override
public int hashCode() {
@@ -119,9 +142,9 @@
public int deletions;
- public PathChangeModel(String name, String path, long size, int mode, String objectId,
+ public PathChangeModel(String name, String path, FilestoreModel filestoreItem, long size, int mode, String objectId,
String commitId, ChangeType type) {
- super(name, path, size, mode, objectId, commitId);
+ super(name, path, filestoreItem, size, mode, objectId, commitId);
this.changeType = type;
}
@@ -148,18 +171,33 @@
return super.equals(o);
}
- public static PathChangeModel from(DiffEntry diff, String commitId) {
+ public static PathChangeModel from(DiffEntry diff, String commitId, Repository repository) {
PathChangeModel pcm;
+ FilestoreModel filestoreItem = null;
+ long size = 0;
+
+ if (repository != null) {
+ try (RevWalk revWalk = new RevWalk(repository)) {
+ size = revWalk.getObjectReader().getObjectSize(diff.getNewId().toObjectId(), Constants.OBJ_BLOB);
+
+ if (JGitUtils.isPossibleFilestoreItem(size)) {
+ filestoreItem = JGitUtils.getFilestoreItem(revWalk.getObjectReader().open(diff.getNewId().toObjectId()));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
if (diff.getChangeType().equals(ChangeType.DELETE)) {
- pcm = new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff
+ pcm = new PathChangeModel(diff.getOldPath(), diff.getOldPath(), filestoreItem, size, diff
.getNewMode().getBits(), diff.getOldId().name(), commitId, diff
.getChangeType());
} else if (diff.getChangeType().equals(ChangeType.RENAME)) {
- pcm = new PathChangeModel(diff.getOldPath(), diff.getNewPath(), 0, diff
+ pcm = new PathChangeModel(diff.getOldPath(), diff.getNewPath(), filestoreItem, size, diff
.getNewMode().getBits(), diff.getNewId().name(), commitId, diff
.getChangeType());
} else {
- pcm = new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff
+ pcm = new PathChangeModel(diff.getNewPath(), diff.getNewPath(), filestoreItem, size, diff
.getNewMode().getBits(), diff.getNewId().name(), commitId, diff
.getChangeType());
}
diff --git a/src/main/java/com/gitblit/models/RefModel.java b/src/main/java/com/gitblit/models/RefModel.java
index 02ba130..d20c2dc 100644
--- a/src/main/java/com/gitblit/models/RefModel.java
+++ b/src/main/java/com/gitblit/models/RefModel.java
@@ -58,12 +58,7 @@
}
} else if (referencedObject instanceof RevCommit) {
RevCommit commit = (RevCommit) referencedObject;
- PersonIdent committer = commit.getCommitterIdent();
- if (committer != null) {
- date = committer.getWhen();
- } else {
- date = JGitUtils.getCommitDate(commit);
- }
+ date = JGitUtils.getAuthorDate(commit);
}
}
return date;
diff --git a/src/main/java/com/gitblit/models/RepositoryUrl.java b/src/main/java/com/gitblit/models/RepositoryUrl.java
index d155dbd..13f6917 100644
--- a/src/main/java/com/gitblit/models/RepositoryUrl.java
+++ b/src/main/java/com/gitblit/models/RepositoryUrl.java
@@ -41,8 +41,8 @@
this.permission = permission;
}
- public boolean isExternal() {
- return permission == null;
+ public boolean hasPermission() {
+ return permission != null;
}
@Override
diff --git a/src/main/java/com/gitblit/models/TicketModel.java b/src/main/java/com/gitblit/models/TicketModel.java
index a4880ea..7495448 100644
--- a/src/main/java/com/gitblit/models/TicketModel.java
+++ b/src/main/java/com/gitblit/models/TicketModel.java
@@ -91,6 +91,10 @@
public Integer deletions;
+ public Priority priority;
+
+ public Severity severity;
+
/**
* Builds an effective ticket from the collection of changes. A change may
* Add or Subtract information from a ticket, but the collection of changes
@@ -103,6 +107,30 @@
TicketModel ticket;
List<Change> effectiveChanges = new ArrayList<Change>();
Map<String, Change> comments = new HashMap<String, Change>();
+ Map<Integer, Integer> latestRevisions = new HashMap<Integer, Integer>();
+
+ int latestPatchsetNumber = -1;
+
+ List<Integer> deletedPatchsets = new ArrayList<Integer>();
+
+ for (Change change : changes) {
+ if (change.patchset != null) {
+ if (change.patchset.isDeleted()) {
+ deletedPatchsets.add(change.patchset.number);
+ } else {
+ Integer latestRev = latestRevisions.get(change.patchset.number);
+
+ if (latestRev == null || change.patchset.rev > latestRev) {
+ latestRevisions.put(change.patchset.number, change.patchset.rev);
+ }
+
+ if (change.patchset.number > latestPatchsetNumber) {
+ latestPatchsetNumber = change.patchset.number;
+ }
+ }
+ }
+ }
+
for (Change change : changes) {
if (change.comment != null) {
if (comments.containsKey(change.comment.id)) {
@@ -118,6 +146,19 @@
effectiveChanges.add(change);
comments.put(change.comment.id, change);
}
+ } else if (change.patchset != null) {
+ //All revisions of a deleted patchset are not displayed
+ if (!deletedPatchsets.contains(change.patchset.number)) {
+
+ Integer latestRev = latestRevisions.get(change.patchset.number);
+
+ if ( (change.patchset.number < latestPatchsetNumber)
+ && (change.patchset.rev == latestRev)) {
+ change.patchset.canDelete = true;
+ }
+
+ effectiveChanges.add(change);
+ }
} else {
effectiveChanges.add(change);
}
@@ -141,6 +182,8 @@
changes = new ArrayList<Change>();
status = Status.New;
type = Type.defaultType;
+ priority = Priority.defaultPriority;
+ severity = Severity.defaultSeverity;
}
public boolean isOpen() {
@@ -517,6 +560,12 @@
case mergeSha:
mergeSha = toString(value);
break;
+ case priority:
+ priority = TicketModel.Priority.fromObject(value, priority);
+ break;
+ case severity:
+ severity = TicketModel.Severity.fromObject(value, severity);
+ break;
default:
// unknown
break;
@@ -1021,10 +1070,16 @@
public int added;
public PatchsetType type;
+ public transient boolean canDelete = false;
+
public boolean isFF() {
return PatchsetType.FastForward == type;
}
+ public boolean isDeleted() {
+ return PatchsetType.Delete == type;
+ }
+
@Override
public int hashCode() {
return toString().hashCode();
@@ -1183,16 +1238,16 @@
public static enum Field {
title, body, responsible, type, status, milestone, mergeSha, mergeTo,
- topic, labels, watchers, reviewers, voters, mentions;
+ topic, labels, watchers, reviewers, voters, mentions, priority, severity;
}
public static enum Type {
- Enhancement, Task, Bug, Proposal, Question;
+ Enhancement, Task, Bug, Proposal, Question, Maintenance;
public static Type defaultType = Task;
public static Type [] choices() {
- return new Type [] { Enhancement, Task, Bug, Question };
+ return new Type [] { Enhancement, Task, Bug, Question, Maintenance };
}
@Override
@@ -1275,7 +1330,7 @@
}
public static enum PatchsetType {
- Proposal, FastForward, Rebase, Squash, Rebase_Squash, Amend;
+ Proposal, FastForward, Rebase, Squash, Rebase_Squash, Amend, Delete;
public boolean isRewrite() {
return (this != FastForward) && (this != Proposal);
@@ -1310,4 +1365,110 @@
return null;
}
}
+
+ public static enum Priority {
+ Low(-1), Normal(0), High(1), Urgent(2);
+
+ public static Priority defaultPriority = Normal;
+
+ final int value;
+
+ Priority(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public static Priority [] choices() {
+ return new Priority [] { Urgent, High, Normal, Low };
+ }
+
+ @Override
+ public String toString() {
+ return name().toLowerCase().replace('_', ' ');
+ }
+
+ public static Priority fromObject(Object o, Priority defaultPriority) {
+ if (o instanceof Priority) {
+ // cast and return
+ return (Priority) o;
+ } else if (o instanceof String) {
+ // find by name
+ for (Priority priority : values()) {
+ String str = o.toString();
+ if (priority.name().equalsIgnoreCase(str)
+ || priority.toString().equalsIgnoreCase(str)) {
+ return priority;
+ }
+ }
+ } else if (o instanceof Number) {
+
+ switch (((Number) o).intValue()) {
+ case -1: return Priority.Low;
+ case 0: return Priority.Normal;
+ case 1: return Priority.High;
+ case 2: return Priority.Urgent;
+ default: return Priority.Normal;
+ }
+ }
+
+ return defaultPriority;
+ }
+ }
+
+ public static enum Severity {
+ Unrated(-1), Negligible(1), Minor(2), Serious(3), Critical(4), Catastrophic(5);
+
+ public static Severity defaultSeverity = Unrated;
+
+ final int value;
+
+ Severity(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public static Severity [] choices() {
+ return new Severity [] { Unrated, Negligible, Minor, Serious, Critical, Catastrophic };
+ }
+
+ @Override
+ public String toString() {
+ return name().toLowerCase().replace('_', ' ');
+ }
+
+ public static Severity fromObject(Object o, Severity defaultSeverity) {
+ if (o instanceof Severity) {
+ // cast and return
+ return (Severity) o;
+ } else if (o instanceof String) {
+ // find by name
+ for (Severity severity : values()) {
+ String str = o.toString();
+ if (severity.name().equalsIgnoreCase(str)
+ || severity.toString().equalsIgnoreCase(str)) {
+ return severity;
+ }
+ }
+ } else if (o instanceof Number) {
+
+ switch (((Number) o).intValue()) {
+ case -1: return Severity.Unrated;
+ case 1: return Severity.Negligible;
+ case 2: return Severity.Minor;
+ case 3: return Severity.Serious;
+ case 4: return Severity.Critical;
+ case 5: return Severity.Catastrophic;
+ default: return Severity.Unrated;
+ }
+ }
+
+ return defaultSeverity;
+ }
+ }
}
diff --git a/src/main/java/com/gitblit/service/LuceneService.java b/src/main/java/com/gitblit/service/LuceneService.java
index 482be5c..097a39b 100644
--- a/src/main/java/com/gitblit/service/LuceneService.java
+++ b/src/main/java/com/gitblit/service/LuceneService.java
@@ -105,7 +105,7 @@
public class LuceneService implements Runnable {
- private static final int INDEX_VERSION = 5;
+ private static final int INDEX_VERSION = 6;
private static final String FIELD_OBJECT_TYPE = "type";
private static final String FIELD_PATH = "path";
@@ -125,7 +125,7 @@
private static final String CONF_ALIAS = "aliases";
private static final String CONF_BRANCH = "branches";
- private static final Version LUCENE_VERSION = Version.LUCENE_46;
+ private static final Version LUCENE_VERSION = Version.LUCENE_4_10_0;
private final Logger logger = LoggerFactory.getLogger(LuceneService.class);
@@ -437,7 +437,7 @@
// skip non-annotated tags
continue;
}
- if (!tags.containsKey(tag.getObjectId().getName())) {
+ if (!tags.containsKey(tag.getReferencedObjectId().getName())) {
tags.put(tag.getReferencedObjectId().getName(), new ArrayList<String>());
}
tags.get(tag.getReferencedObjectId().getName()).add(tag.displayName);
@@ -615,7 +615,7 @@
}
// finished
- reader.release();
+ reader.close();
// commit all changes and reset the searcher
config.setInt(CONF_INDEX, null, CONF_VERSION, INDEX_VERSION);
@@ -1104,6 +1104,7 @@
content = "";
}
+ int tabLength = storedSettings.getInteger(Keys.web.tabLength, 4);
int fragmentLength = SearchObjectType.commit == result.type ? 512 : 150;
QueryScorer scorer = new QueryScorer(query, "content");
@@ -1126,7 +1127,7 @@
if (fragment.length() > fragmentLength) {
fragment = fragment.substring(0, fragmentLength) + "...";
}
- return "<pre class=\"text\">" + StringUtils.escapeForHtml(fragment, true) + "</pre>";
+ return "<pre class=\"text\">" + StringUtils.escapeForHtml(fragment, true, tabLength) + "</pre>";
}
// make sure we have unique fragments
diff --git a/src/main/java/com/gitblit/service/MailService.java b/src/main/java/com/gitblit/service/MailService.java
index ae9727f..ec3a84c 100644
--- a/src/main/java/com/gitblit/service/MailService.java
+++ b/src/main/java/com/gitblit/service/MailService.java
@@ -37,6 +37,7 @@
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimeUtility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -196,7 +197,8 @@
}
message.setSentDate(new Date());
- message.setSubject(mailing.subject);
+ // UTF-8 encode
+ message.setSubject(MimeUtility.encodeText(mailing.subject, "utf-8", "B"));
MimeBodyPart messagePart = new MimeBodyPart();
messagePart.setText(mailing.content, "utf-8");
diff --git a/src/main/java/com/gitblit/servlet/AccessDeniedServlet.java b/src/main/java/com/gitblit/servlet/AccessDeniedServlet.java
new file mode 100644
index 0000000..ae0797e
--- /dev/null
+++ b/src/main/java/com/gitblit/servlet/AccessDeniedServlet.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015 Jean-Baptiste Mayer
+ *
+ * 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.
+ */
+package com.gitblit.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.inject.Singleton;
+
+/**
+ * Access-denied Servlet.
+ *
+ * This servlet serves only 404 Not Found error replies.
+ *
+ * This servlet is used to override the container's default behavior to serve
+ * all contents of the application's WAR. We can selectively prevent access to
+ * a specific path simply by mapping this servlet onto specific paths.
+ *
+ *
+ * @author Jean-Baptiste Mayer
+ *
+ */
+@Singleton
+public class AccessDeniedServlet extends HttpServlet {
+ /**
+ *
+ */
+ private static final long serialVersionUID = -3239463647917811122L;
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, java.io.IOException {
+ processRequest(request, response);
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ processRequest(request, response);
+ }
+
+ private void processRequest(HttpServletRequest request,
+ HttpServletResponse response) {
+ response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java b/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java
index 7f69119..e1d76db 100644
--- a/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java
+++ b/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java
@@ -17,23 +17,23 @@
import java.io.IOException;
import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.Iterator;
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 com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
-import dagger.ObjectGraph;
-
/**
* The AccessRestrictionFilter is an AuthenticationFilter that confirms that the
* requested repository can be accessed by the anonymous or named user.
@@ -54,11 +54,15 @@
protected IRepositoryManager repositoryManager;
- @Override
- protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
- super.inject(dagger, filterConfig);
- this.runtimeManager = dagger.get(IRuntimeManager.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
+ protected AccessRestrictionFilter(
+ IRuntimeManager runtimeManager,
+ IAuthenticationManager authenticationManager,
+ IRepositoryManager repositoryManager) {
+
+ super(authenticationManager);
+
+ this.runtimeManager = runtimeManager;
+ this.repositoryManager = repositoryManager;
}
/**
@@ -82,16 +86,17 @@
*
* @return true if the filter allows repository creation
*/
- protected abstract boolean isCreationAllowed();
+ protected abstract boolean isCreationAllowed(String action);
/**
* Determine if the action may be executed on the repository.
*
* @param repository
* @param action
+ * @param method
* @return true if the action may be performed
*/
- protected abstract boolean isActionAllowed(RepositoryModel repository, String action);
+ protected abstract boolean isActionAllowed(RepositoryModel repository, String action, String method);
/**
* Determine if the repository requires authentication.
@@ -100,7 +105,7 @@
* @param action
* @return true if authentication required
*/
- protected abstract boolean requiresAuthentication(RepositoryModel repository, String action);
+ protected abstract boolean requiresAuthentication(RepositoryModel repository, String action, String method);
/**
* Determine if the user can access the repository and perform the specified
@@ -124,7 +129,27 @@
protected RepositoryModel createRepository(UserModel user, String repository, String action) {
return null;
}
-
+
+ /**
+ * Allows authentication header to be altered based on the action requested
+ * Default is WWW-Authenticate
+ * @param httpRequest
+ * @param action
+ * @return authentication type header
+ */
+ protected String getAuthenticationHeader(HttpServletRequest httpRequest, String action) {
+ return "WWW-Authenticate";
+ }
+
+ /**
+ * Allows request headers to be used as part of filtering
+ * @param request
+ * @return true (default) if headers are valid, false otherwise
+ */
+ protected boolean hasValidRequestHeader(String action, HttpServletRequest request) {
+ return true;
+ }
+
/**
* doFilter does the actual work of preprocessing the request to ensure that
* the user may proceed.
@@ -161,13 +186,14 @@
// Load the repository model
RepositoryModel model = repositoryManager.getRepositoryModel(repository);
if (model == null) {
- if (isCreationAllowed()) {
+ if (isCreationAllowed(urlRequestType)) {
if (user == null) {
// challenge client to provide credentials for creation. send 401.
if (runtimeManager.isDebugMode()) {
logger.info(MessageFormat.format("ARF: CREATE CHALLENGE {0}", fullUrl));
}
- httpResponse.setHeader("WWW-Authenticate", CHALLENGE);
+
+ httpResponse.setHeader(getAuthenticationHeader(httpRequest, urlRequestType), CHALLENGE);
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
} else {
@@ -186,7 +212,7 @@
}
// Confirm that the action may be executed on the repository
- if (!isActionAllowed(model, urlRequestType)) {
+ if (!isActionAllowed(model, urlRequestType, httpRequest.getMethod())) {
logger.info(MessageFormat.format("ARF: action {0} on {1} forbidden ({2})",
urlRequestType, model, HttpServletResponse.SC_FORBIDDEN));
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
@@ -208,13 +234,13 @@
}
// BASIC authentication challenge and response processing
- if (!StringUtils.isEmpty(urlRequestType) && requiresAuthentication(model, urlRequestType)) {
+ if (!StringUtils.isEmpty(urlRequestType) && requiresAuthentication(model, urlRequestType, httpRequest.getMethod())) {
if (user == null) {
// challenge client to provide credentials. send 401.
if (runtimeManager.isDebugMode()) {
logger.info(MessageFormat.format("ARF: CHALLENGE {0}", fullUrl));
}
- httpResponse.setHeader("WWW-Authenticate", CHALLENGE);
+ httpResponse.setHeader(getAuthenticationHeader(httpRequest, urlRequestType), CHALLENGE);
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
} else {
@@ -223,8 +249,8 @@
// authenticated request permitted.
// pass processing to the restricted servlet.
newSession(authenticatedRequest, httpResponse);
- logger.info(MessageFormat.format("ARF: {0} ({1}) authenticated", fullUrl,
- HttpServletResponse.SC_CONTINUE));
+ logger.info(MessageFormat.format("ARF: authenticated {0} to {1} ({2})", user.username,
+ fullUrl, HttpServletResponse.SC_CONTINUE));
chain.doFilter(authenticatedRequest, httpResponse);
return;
}
@@ -246,4 +272,17 @@
// pass processing to the restricted servlet.
chain.doFilter(authenticatedRequest, httpResponse);
}
+
+ public static boolean hasContentInRequestHeader(HttpServletRequest request, String headerName, String content)
+ {
+ Iterator<String> headerItr = Collections.list(request.getHeaders(headerName)).iterator();
+
+ while (headerItr.hasNext()) {
+ if (headerItr.next().contains(content)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/servlet/AuthenticationFilter.java b/src/main/java/com/gitblit/servlet/AuthenticationFilter.java
index c21f869..3093e63 100644
--- a/src/main/java/com/gitblit/servlet/AuthenticationFilter.java
+++ b/src/main/java/com/gitblit/servlet/AuthenticationFilter.java
@@ -1,184 +1,190 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * 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.
- */
-package com.gitblit.servlet;
-
-import java.io.IOException;
-import java.security.Principal;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
-
-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.HttpServletRequestWrapper;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants;
-import com.gitblit.dagger.DaggerFilter;
-import com.gitblit.manager.IAuthenticationManager;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.DeepCopier;
-import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
-
-/**
- * The AuthenticationFilter is a servlet filter that preprocesses requests that
- * match its url pattern definition in the web.xml file.
- *
- * http://en.wikipedia.org/wiki/Basic_access_authentication
- *
- * @author James Moger
- *
- */
-public abstract class AuthenticationFilter extends DaggerFilter {
-
- protected static final String CHALLENGE = "Basic realm=\"" + Constants.NAME + "\"";
-
- protected static final String SESSION_SECURED = "com.gitblit.secured";
-
- protected transient Logger logger = LoggerFactory.getLogger(getClass());
-
- protected IAuthenticationManager authenticationManager;
-
- @Override
- protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
- this.authenticationManager = dagger.get(IAuthenticationManager.class);
- }
-
- /**
- * doFilter does the actual work of preprocessing the request to ensure that
- * the user may proceed.
- *
- * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
- * javax.servlet.ServletResponse, javax.servlet.FilterChain)
- */
- @Override
- public abstract void doFilter(final ServletRequest request, final ServletResponse response,
- final FilterChain chain) throws IOException, ServletException;
-
- /**
- * Allow the filter to require a client certificate to continue processing.
- *
- * @return true, if a client certificate is required
- */
- protected boolean requiresClientCertificate() {
- return false;
- }
-
- /**
- * Returns the full relative url of the request.
- *
- * @param httpRequest
- * @return url
- */
- protected String getFullUrl(HttpServletRequest httpRequest) {
- String servletUrl = httpRequest.getContextPath() + httpRequest.getServletPath();
- String url = httpRequest.getRequestURI().substring(servletUrl.length());
- String params = httpRequest.getQueryString();
- if (url.length() > 0 && url.charAt(0) == '/') {
- url = url.substring(1);
- }
- String fullUrl = url + (StringUtils.isEmpty(params) ? "" : ("?" + params));
- return fullUrl;
- }
-
- /**
- * Returns the user making the request, if the user has authenticated.
- *
- * @param httpRequest
- * @return user
- */
- protected UserModel getUser(HttpServletRequest httpRequest) {
- UserModel user = authenticationManager.authenticate(httpRequest, requiresClientCertificate());
- return user;
- }
-
- /**
- * Taken from Jetty's LoginAuthenticator.renewSessionOnAuthentication()
- */
- protected void newSession(HttpServletRequest request, HttpServletResponse response) {
- HttpSession oldSession = request.getSession(false);
- if (oldSession != null && oldSession.getAttribute(SESSION_SECURED) == null) {
- synchronized (this) {
- Map<String, Object> attributes = new HashMap<String, Object>();
- Enumeration<String> e = oldSession.getAttributeNames();
- while (e.hasMoreElements()) {
- String name = e.nextElement();
- attributes.put(name, oldSession.getAttribute(name));
- oldSession.removeAttribute(name);
- }
- oldSession.invalidate();
-
- HttpSession newSession = request.getSession(true);
- newSession.setAttribute(SESSION_SECURED, Boolean.TRUE);
- for (Map.Entry<String, Object> entry : attributes.entrySet()) {
- newSession.setAttribute(entry.getKey(), entry.getValue());
- }
- }
- }
- }
-
- /**
- * Wraps a standard HttpServletRequest and overrides user principal methods.
- */
- public static class AuthenticatedRequest extends HttpServletRequestWrapper {
-
- private UserModel user;
-
- public AuthenticatedRequest(HttpServletRequest req) {
- super(req);
- user = DeepCopier.copy(UserModel.ANONYMOUS);
- }
-
- UserModel getUser() {
- return user;
- }
-
- void setUser(UserModel user) {
- this.user = user;
- }
-
- @Override
- public String getRemoteUser() {
- return user.username;
- }
-
- @Override
- public boolean isUserInRole(String role) {
- if (role.equals(Constants.ADMIN_ROLE)) {
- return user.canAdmin();
- }
- // Gitblit does not currently use actual roles in the traditional
- // servlet container sense. That is the reason this is marked
- // deprecated, but I may want to revisit this.
- return user.hasRepositoryPermission(role);
- }
-
- @Override
- public Principal getUserPrincipal() {
- return user;
- }
- }
-}
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.servlet;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+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.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.Constants.Role;
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.DeepCopier;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * The AuthenticationFilter is a servlet filter that preprocesses requests that
+ * match its url pattern definition in the web.xml file.
+ *
+ * http://en.wikipedia.org/wiki/Basic_access_authentication
+ *
+ * @author James Moger
+ *
+ */
+public abstract class AuthenticationFilter implements Filter {
+
+ protected static final String CHALLENGE = "Basic realm=\"" + Constants.NAME + "\"";
+
+ protected static final String SESSION_SECURED = "com.gitblit.secured";
+
+ protected transient Logger logger = LoggerFactory.getLogger(getClass());
+
+ protected IAuthenticationManager authenticationManager;
+
+ protected AuthenticationFilter(IAuthenticationManager authenticationManager) {
+ this.authenticationManager = authenticationManager;
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ /**
+ * doFilter does the actual work of preprocessing the request to ensure that
+ * the user may proceed.
+ *
+ * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
+ * javax.servlet.ServletResponse, javax.servlet.FilterChain)
+ */
+ @Override
+ public abstract void doFilter(final ServletRequest request, final ServletResponse response,
+ final FilterChain chain) throws IOException, ServletException;
+
+ /**
+ * Allow the filter to require a client certificate to continue processing.
+ *
+ * @return true, if a client certificate is required
+ */
+ protected boolean requiresClientCertificate() {
+ return false;
+ }
+
+ /**
+ * Returns the full relative url of the request.
+ *
+ * @param httpRequest
+ * @return url
+ */
+ protected String getFullUrl(HttpServletRequest httpRequest) {
+ String servletUrl = httpRequest.getContextPath() + httpRequest.getServletPath();
+ String url = httpRequest.getRequestURI().substring(servletUrl.length());
+ String params = httpRequest.getQueryString();
+ if (url.length() > 0 && url.charAt(0) == '/') {
+ url = url.substring(1);
+ }
+ String fullUrl = url + (StringUtils.isEmpty(params) ? "" : ("?" + params));
+ return fullUrl;
+ }
+
+ /**
+ * Returns the user making the request, if the user has authenticated.
+ *
+ * @param httpRequest
+ * @return user
+ */
+ protected UserModel getUser(HttpServletRequest httpRequest) {
+ UserModel user = authenticationManager.authenticate(httpRequest, requiresClientCertificate());
+ return user;
+ }
+
+ /**
+ * Taken from Jetty's LoginAuthenticator.renewSessionOnAuthentication()
+ */
+ protected void newSession(HttpServletRequest request, HttpServletResponse response) {
+ HttpSession oldSession = request.getSession(false);
+ if (oldSession != null && oldSession.getAttribute(SESSION_SECURED) == null) {
+ synchronized (this) {
+ Map<String, Object> attributes = new HashMap<String, Object>();
+ Enumeration<String> e = oldSession.getAttributeNames();
+ while (e.hasMoreElements()) {
+ String name = e.nextElement();
+ attributes.put(name, oldSession.getAttribute(name));
+ oldSession.removeAttribute(name);
+ }
+ oldSession.invalidate();
+
+ HttpSession newSession = request.getSession(true);
+ newSession.setAttribute(SESSION_SECURED, Boolean.TRUE);
+ for (Map.Entry<String, Object> entry : attributes.entrySet()) {
+ newSession.setAttribute(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ }
+
+ /**
+ * Wraps a standard HttpServletRequest and overrides user principal methods.
+ */
+ public static class AuthenticatedRequest extends HttpServletRequestWrapper {
+
+ private UserModel user;
+
+ public AuthenticatedRequest(HttpServletRequest req) {
+ super(req);
+ user = DeepCopier.copy(UserModel.ANONYMOUS);
+ }
+
+ UserModel getUser() {
+ return user;
+ }
+
+ void setUser(UserModel user) {
+ this.user = user;
+ }
+
+ @Override
+ public String getRemoteUser() {
+ return user.username;
+ }
+
+ @Override
+ public boolean isUserInRole(String role) {
+ if (role.equals(Role.ADMIN.getRole())) {
+ return user.canAdmin();
+ }
+ // Gitblit does not currently use actual roles in the traditional
+ // servlet container sense. That is the reason this is marked
+ // deprecated, but I may want to revisit this.
+ return user.hasRepositoryPermission(role);
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return user;
+ }
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/BranchGraphServlet.java b/src/main/java/com/gitblit/servlet/BranchGraphServlet.java
index fa2152c..85fbb74 100644
--- a/src/main/java/com/gitblit/servlet/BranchGraphServlet.java
+++ b/src/main/java/com/gitblit/servlet/BranchGraphServlet.java
@@ -36,7 +36,10 @@
import java.util.TreeSet;
import javax.imageio.ImageIO;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -55,20 +58,18 @@
import com.gitblit.Constants;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
-import dagger.ObjectGraph;
-
/**
* Handles requests for branch graphs
*
* @author James Moger
*
*/
-public class BranchGraphServlet extends DaggerServlet {
+@Singleton
+public class BranchGraphServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@@ -87,20 +88,20 @@
private IRepositoryManager repositoryManager;
- public BranchGraphServlet() {
- super();
+ @Inject
+ public BranchGraphServlet(
+ IStoredSettings settings,
+ IRepositoryManager repositoryManager) {
+
+ this.settings = settings;
+ this.repositoryManager = repositoryManager;
+
strokeCache = new Stroke[4];
for (int i = 1; i < strokeCache.length; i++) {
strokeCache[i] = new BasicStroke(i);
}
}
- @Override
- protected void inject(ObjectGraph dagger) {
- this.settings = dagger.get(IStoredSettings.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
- }
-
/**
* Returns an url to this servlet for the specified parameters.
*
diff --git a/src/main/java/com/gitblit/servlet/DownloadZipFilter.java b/src/main/java/com/gitblit/servlet/DownloadZipFilter.java
index 42257a2..146f6d4 100644
--- a/src/main/java/com/gitblit/servlet/DownloadZipFilter.java
+++ b/src/main/java/com/gitblit/servlet/DownloadZipFilter.java
@@ -15,7 +15,13 @@
*/
package com.gitblit.servlet;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
@@ -27,8 +33,18 @@
* @author James Moger
*
*/
+@Singleton
public class DownloadZipFilter extends AccessRestrictionFilter {
+ @Inject
+ public DownloadZipFilter(
+ IRuntimeManager runtimeManager,
+ IAuthenticationManager authenticationManager,
+ IRepositoryManager repositoryManager) {
+
+ super(runtimeManager, authenticationManager, repositoryManager);
+ }
+
/**
* Extract the repository name from the url.
*
@@ -65,7 +81,7 @@
* @return true if the filter allows repository creation
*/
@Override
- protected boolean isCreationAllowed() {
+ protected boolean isCreationAllowed(String action) {
return false;
}
@@ -74,10 +90,11 @@
*
* @param repository
* @param action
+ * @param method
* @return true if the action may be performed
*/
@Override
- protected boolean isActionAllowed(RepositoryModel repository, String action) {
+ protected boolean isActionAllowed(RepositoryModel repository, String action, String method) {
return true;
}
@@ -86,10 +103,11 @@
*
* @param repository
* @param action
+ * @param method
* @return true if authentication required
*/
@Override
- protected boolean requiresAuthentication(RepositoryModel repository, String action) {
+ protected boolean requiresAuthentication(RepositoryModel repository, String action, String method) {
return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW);
}
diff --git a/src/main/java/com/gitblit/servlet/DownloadZipServlet.java b/src/main/java/com/gitblit/servlet/DownloadZipServlet.java
index 6a64778..319c4f9 100644
--- a/src/main/java/com/gitblit/servlet/DownloadZipServlet.java
+++ b/src/main/java/com/gitblit/servlet/DownloadZipServlet.java
@@ -20,7 +20,11 @@
import java.text.ParseException;
import java.util.Date;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.lib.Repository;
@@ -31,15 +35,13 @@
import com.gitblit.Constants;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
+import com.gitblit.manager.IFilestoreManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.utils.CompressionUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
-import dagger.ObjectGraph;
-
/**
* Streams out a zip file from the specified repository for any tree path at any
* revision.
@@ -47,7 +49,8 @@
* @author James Moger
*
*/
-public class DownloadZipServlet extends DaggerServlet {
+@Singleton
+public class DownloadZipServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@@ -56,6 +59,8 @@
private IStoredSettings settings;
private IRepositoryManager repositoryManager;
+
+ private IFilestoreManager filestoreManager;
public static enum Format {
zip(".zip"), tar(".tar"), gz(".tar.gz"), xz(".tar.xz"), bzip2(".tar.bzip2");
@@ -76,10 +81,11 @@
}
}
- @Override
- protected void inject(ObjectGraph dagger) {
- this.settings = dagger.get(IStoredSettings.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
+ @Inject
+ public DownloadZipServlet(IStoredSettings settings, IRepositoryManager repositoryManager, IFilestoreManager filestoreManager) {
+ this.settings = settings;
+ this.repositoryManager = repositoryManager;
+ this.filestoreManager = filestoreManager;
}
/**
@@ -168,22 +174,23 @@
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
+
try {
switch (format) {
case zip:
- CompressionUtils.zip(r, basePath, objectId, response.getOutputStream());
+ CompressionUtils.zip(r, filestoreManager, basePath, objectId, response.getOutputStream());
break;
case tar:
- CompressionUtils.tar(r, basePath, objectId, response.getOutputStream());
+ CompressionUtils.tar(r, filestoreManager, basePath, objectId, response.getOutputStream());
break;
case gz:
- CompressionUtils.gz(r, basePath, objectId, response.getOutputStream());
+ CompressionUtils.gz(r, filestoreManager, basePath, objectId, response.getOutputStream());
break;
case xz:
- CompressionUtils.xz(r, basePath, objectId, response.getOutputStream());
+ CompressionUtils.xz(r, filestoreManager, basePath, objectId, response.getOutputStream());
break;
case bzip2:
- CompressionUtils.bzip2(r, basePath, objectId, response.getOutputStream());
+ CompressionUtils.bzip2(r, filestoreManager, basePath, objectId, response.getOutputStream());
break;
}
diff --git a/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java b/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java
index c015021..8a3f782 100644
--- a/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java
+++ b/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java
@@ -18,6 +18,9 @@
import java.io.IOException;
import java.text.MessageFormat;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
@@ -31,12 +34,9 @@
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerFilter;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.models.UserModel;
-import dagger.ObjectGraph;
-
/**
* This filter enforces authentication via HTTP Basic Authentication, if the settings indicate so.
* It looks at the settings "web.authenticateViewPages" and "web.enforceHttpBasicAuthentication"; if
@@ -45,7 +45,8 @@
* @author Laurens Vrijnsen
*
*/
-public class EnforceAuthenticationFilter extends DaggerFilter {
+@Singleton
+public class EnforceAuthenticationFilter implements Filter {
protected transient Logger logger = LoggerFactory.getLogger(getClass());
@@ -53,10 +54,21 @@
private IAuthenticationManager authenticationManager;
+ @Inject
+ public EnforceAuthenticationFilter(
+ IStoredSettings settings,
+ IAuthenticationManager authenticationManager) {
+
+ this.settings = settings;
+ this.authenticationManager = authenticationManager;
+ }
+
@Override
- protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
- this.settings = dagger.get(IStoredSettings.class);
- this.authenticationManager = dagger.get(IAuthenticationManager.class);
+ public void init(FilterConfig config) {
+ }
+
+ @Override
+ public void destroy() {
}
/*
@@ -87,12 +99,4 @@
chain.doFilter(request, response);
}
}
-
-
- /*
- * @see javax.servlet.Filter#destroy()
- */
- @Override
- public void destroy() {
- }
}
diff --git a/src/main/java/com/gitblit/servlet/FederationServlet.java b/src/main/java/com/gitblit/servlet/FederationServlet.java
index acbc002..78709c9 100644
--- a/src/main/java/com/gitblit/servlet/FederationServlet.java
+++ b/src/main/java/com/gitblit/servlet/FederationServlet.java
@@ -25,6 +25,8 @@
import java.util.Map;
import java.util.Set;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.http.HttpServletResponse;
import com.gitblit.Constants.FederationRequest;
@@ -43,14 +45,13 @@
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
-import dagger.ObjectGraph;
-
/**
* Handles federation requests.
*
* @author James Moger
*
*/
+@Singleton
public class FederationServlet extends JsonServlet {
private static final long serialVersionUID = 1L;
@@ -63,12 +64,17 @@
private IFederationManager federationManager;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.settings = dagger.get(IStoredSettings.class);
- this.userManager = dagger.get(IUserManager.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
- this.federationManager = dagger.get(IFederationManager.class);
+ @Inject
+ public FederationServlet(
+ IStoredSettings settings,
+ IUserManager userManager,
+ IRepositoryManager repositoryManager,
+ IFederationManager federationManager) {
+
+ this.settings = settings;
+ this.userManager = userManager;
+ this.repositoryManager = repositoryManager;
+ this.federationManager = federationManager;
}
/**
diff --git a/src/main/java/com/gitblit/servlet/FilestoreServlet.java b/src/main/java/com/gitblit/servlet/FilestoreServlet.java
new file mode 100644
index 0000000..4af9084
--- /dev/null
+++ b/src/main/java/com/gitblit/servlet/FilestoreServlet.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright 2015 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+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 com.gitblit.Constants;
+import com.gitblit.IStoredSettings;
+import com.gitblit.models.FilestoreModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.FilestoreModel.Status;
+import com.gitblit.manager.FilestoreManager;
+import com.gitblit.manager.IGitblit;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.JsonUtils;
+
+
+/**
+ * Handles large file storage as per the Git LFS v1 Batch API
+ *
+ * Further details can be found at https://github.com/github/git-lfs
+ *
+ * @author Paul Martin
+ */
+@Singleton
+public class FilestoreServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+ public static final int PROTOCOL_VERSION = 1;
+
+ public static final String GIT_LFS_META_MIME = "application/vnd.git-lfs+json";
+
+ public static final String REGEX_PATH = "^(.*?)/(r)/(.*?)/info/lfs/objects/(batch|" + Constants.REGEX_SHA256 + ")";
+ public static final int REGEX_GROUP_BASE_URI = 1;
+ public static final int REGEX_GROUP_PREFIX = 2;
+ public static final int REGEX_GROUP_REPOSITORY = 3;
+ public static final int REGEX_GROUP_ENDPOINT = 4;
+
+ protected final Logger logger;
+
+ private static IGitblit gitblit;
+
+ @Inject
+ public FilestoreServlet(IStoredSettings settings, IGitblit gitblit) {
+
+ super();
+ logger = LoggerFactory.getLogger(getClass());
+
+ FilestoreServlet.gitblit = gitblit;
+ }
+
+
+ /**
+ * Handles batch upload request (metadata)
+ *
+ * @param request
+ * @param response
+ * @throws javax.servlet.ServletException
+ * @throws java.io.IOException
+ */
+ @Override
+ protected void doPost(HttpServletRequest request,
+ HttpServletResponse response) throws ServletException ,IOException {
+
+ UrlInfo info = getInfoFromRequest(request);
+ if (info == null) {
+ sendError(response, HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ //Post is for batch operations so no oid should be defined
+ if (info.oid != null) {
+ sendError(response, HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ IGitLFS.Batch batch = deserialize(request, response, IGitLFS.Batch.class);
+
+ if (batch == null) {
+ sendError(response, HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ UserModel user = getUserOrAnonymous(request);
+
+ IGitLFS.BatchResponse batchResponse = new IGitLFS.BatchResponse();
+
+ if (batch.operation.equalsIgnoreCase("upload")) {
+ for (IGitLFS.Request item : batch.objects) {
+
+ Status state = gitblit.addObject(item.oid, item.size, user, info.repository);
+
+ batchResponse.objects.add(getResponseForUpload(info.baseUrl, item.oid, item.size, user.getName(), info.repository.name, state));
+ }
+ } else if (batch.operation.equalsIgnoreCase("download")) {
+ for (IGitLFS.Request item : batch.objects) {
+
+ Status state = gitblit.downloadBlob(item.oid, user, info.repository, null);
+ batchResponse.objects.add(getResponseForDownload(info.baseUrl, item.oid, item.size, user.getName(), info.repository.name, state));
+ }
+ } else {
+ sendError(response, HttpServletResponse.SC_NOT_IMPLEMENTED);
+ return;
+ }
+
+ response.setStatus(HttpServletResponse.SC_OK);
+ serialize(response, batchResponse);
+ }
+
+ /**
+ * Handles the actual upload (BLOB)
+ *
+ * @param request
+ * @param response
+ * @throws javax.servlet.ServletException
+ * @throws java.io.IOException
+ */
+ @Override
+ protected void doPut(HttpServletRequest request,
+ HttpServletResponse response) throws ServletException ,IOException {
+
+ UrlInfo info = getInfoFromRequest(request);
+
+ if (info == null) {
+ sendError(response, HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ //Put is a singular operation so must have oid
+ if (info.oid == null) {
+ sendError(response, HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ UserModel user = getUserOrAnonymous(request);
+ long size = FilestoreManager.UNDEFINED_SIZE;
+
+
+
+ FilestoreModel.Status status = gitblit.uploadBlob(info.oid, size, user, info.repository, request.getInputStream());
+ IGitLFS.Response responseObject = getResponseForUpload(info.baseUrl, info.oid, size, user.getName(), info.repository.name, status);
+
+ logger.info(MessageFormat.format("FILESTORE-AUDIT {0}:{4} {1} {2}@{3}",
+ "PUT", info.oid, user.getName(), info.repository.name, status.toString() ));
+
+ if (responseObject.error == null) {
+ response.setStatus(responseObject.successCode);
+ } else {
+ serialize(response, responseObject.error);
+ }
+ };
+
+ /**
+ * Handles a download
+ * Treated as hypermedia request if accept header contains Git-LFS MIME
+ * otherwise treated as a download of the blob
+ * @param request
+ * @param response
+ * @throws javax.servlet.ServletException
+ * @throws java.io.IOException
+ */
+ @Override
+ protected void doGet(HttpServletRequest request,
+ HttpServletResponse response) throws ServletException ,IOException {
+
+ UrlInfo info = getInfoFromRequest(request);
+
+ if (info == null || info.oid == null) {
+ sendError(response, HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ UserModel user = getUserOrAnonymous(request);
+
+ FilestoreModel model = gitblit.getObject(info.oid, user, info.repository);
+ long size = FilestoreManager.UNDEFINED_SIZE;
+
+ boolean isMetaRequest = AccessRestrictionFilter.hasContentInRequestHeader(request, "Accept", GIT_LFS_META_MIME);
+ FilestoreModel.Status status = Status.Unavailable;
+
+ if (model != null) {
+ size = model.getSize();
+ status = model.getStatus();
+ }
+
+ if (!isMetaRequest) {
+ status = gitblit.downloadBlob(info.oid, user, info.repository, response.getOutputStream());
+
+ logger.info(MessageFormat.format("FILESTORE-AUDIT {0}:{4} {1} {2}@{3}",
+ "GET", info.oid, user.getName(), info.repository.name, status.toString() ));
+ }
+
+ if (status == Status.Error_Unexpected_Stream_End) {
+ return;
+ }
+
+ IGitLFS.Response responseObject = getResponseForDownload(info.baseUrl,
+ info.oid, size, user.getName(), info.repository.name, status);
+
+ if (responseObject.error == null) {
+ response.setStatus(responseObject.successCode);
+
+ if (isMetaRequest) {
+ serialize(response, responseObject);
+ }
+ } else {
+ response.setStatus(responseObject.error.code);
+
+ if (isMetaRequest) {
+ serialize(response, responseObject.error);
+ }
+ }
+ };
+
+ private void sendError(HttpServletResponse response, int code) throws IOException {
+
+ String msg = "";
+
+ switch (code)
+ {
+ case HttpServletResponse.SC_NOT_FOUND: msg = "Not Found"; break;
+ case HttpServletResponse.SC_NOT_IMPLEMENTED: msg = "Not Implemented"; break;
+ case HttpServletResponse.SC_BAD_REQUEST: msg = "Malformed Git-LFS request"; break;
+
+ default: msg = "Unknown Error";
+ }
+
+ response.setStatus(code);
+ serialize(response, new IGitLFS.ObjectError(code, msg));
+ }
+
+ @SuppressWarnings("incomplete-switch")
+ private IGitLFS.Response getResponseForUpload(String baseUrl, String oid, long size, String user, String repo, FilestoreModel.Status state) {
+
+ switch (state) {
+ case AuthenticationRequired:
+ return new IGitLFS.Response(oid, size, 401, MessageFormat.format("Authentication required to write to repository {0}", repo));
+ case Error_Unauthorized:
+ return new IGitLFS.Response(oid, size, 403, MessageFormat.format("User {0}, does not have write permissions to repository {1}", user, repo));
+ case Error_Exceeds_Size_Limit:
+ return new IGitLFS.Response(oid, size, 509, MessageFormat.format("Object is larger than allowed limit of {1}", gitblit.getMaxUploadSize()));
+ case Error_Hash_Mismatch:
+ return new IGitLFS.Response(oid, size, 422, "Hash mismatch");
+ case Error_Invalid_Oid:
+ return new IGitLFS.Response(oid, size, 422, MessageFormat.format("{0} is not a valid oid", oid));
+ case Error_Invalid_Size:
+ return new IGitLFS.Response(oid, size, 422, MessageFormat.format("{0} is not a valid size", size));
+ case Error_Size_Mismatch:
+ return new IGitLFS.Response(oid, size, 422, "Object size mismatch");
+ case Deleted:
+ return new IGitLFS.Response(oid, size, 410, "Object was deleted : ".concat("TBD Reason") );
+ case Upload_In_Progress:
+ return new IGitLFS.Response(oid, size, 503, "File currently being uploaded by another user");
+ case Unavailable:
+ return new IGitLFS.Response(oid, size, 404, MessageFormat.format("Repository {0}, does not exist for user {1}", repo, user));
+ case Upload_Pending:
+ return new IGitLFS.Response(oid, size, 202, "upload", getObjectUri(baseUrl, repo, oid) );
+ case Available:
+ return new IGitLFS.Response(oid, size, 200, "upload", getObjectUri(baseUrl, repo, oid) );
+ }
+
+ return new IGitLFS.Response(oid, size, 500, "Unknown Error");
+ }
+
+ @SuppressWarnings("incomplete-switch")
+ private IGitLFS.Response getResponseForDownload(String baseUrl, String oid, long size, String user, String repo, FilestoreModel.Status state) {
+
+ switch (state) {
+ case Error_Unauthorized:
+ return new IGitLFS.Response(oid, size, 403, MessageFormat.format("User {0}, does not have read permissions to repository {1}", user, repo));
+ case Error_Invalid_Oid:
+ return new IGitLFS.Response(oid, size, 422, MessageFormat.format("{0} is not a valid oid", oid));
+ case Error_Unknown:
+ return new IGitLFS.Response(oid, size, 500, "Unknown Error");
+ case Deleted:
+ return new IGitLFS.Response(oid, size, 410, "Object was deleted : ".concat("TBD Reason") );
+ case Available:
+ return new IGitLFS.Response(oid, size, 200, "download", getObjectUri(baseUrl, repo, oid) );
+ }
+
+ return new IGitLFS.Response(oid, size, 404, "Object not available");
+ }
+
+
+ private String getObjectUri(String baseUrl, String repo, String oid) {
+ return baseUrl + "/" + repo + "/" + Constants.R_LFS + "objects/" + oid;
+ }
+
+
+ protected void serialize(HttpServletResponse response, Object o) throws IOException {
+ if (o != null) {
+ // Send JSON response
+ String json = JsonUtils.toJsonString(o);
+ response.setCharacterEncoding(Constants.ENCODING);
+ response.setContentType(GIT_LFS_META_MIME);
+ response.getWriter().append(json);
+ }
+ }
+
+ protected <X> X deserialize(HttpServletRequest request, HttpServletResponse response,
+ Class<X> clazz) {
+
+ String json = "";
+ try {
+
+ json = readJson(request, response);
+
+ return JsonUtils.fromJsonString(json.toString(), clazz);
+
+ } catch (Exception e) {
+ //Intentional silent fail
+ }
+
+ return null;
+ }
+
+ private String readJson(HttpServletRequest request, HttpServletResponse response)
+ throws IOException {
+ BufferedReader reader = request.getReader();
+ StringBuilder json = new StringBuilder();
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ json.append(line);
+ }
+ reader.close();
+
+ if (json.length() == 0) {
+ logger.error(MessageFormat.format("Failed to receive json data from {0}",
+ request.getRemoteAddr()));
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ return null;
+ }
+ return json.toString();
+ }
+
+ private UserModel getUserOrAnonymous(HttpServletRequest r) {
+ UserModel user = (UserModel) r.getUserPrincipal();
+ if (user != null) { return user; }
+ return UserModel.ANONYMOUS;
+ }
+
+ private static class UrlInfo {
+ public RepositoryModel repository;
+ public String oid;
+ public String baseUrl;
+
+ public UrlInfo(RepositoryModel repo, String oid, String baseUrl) {
+ this.repository = repo;
+ this.oid = oid;
+ this.baseUrl = baseUrl;
+ }
+ }
+
+ public static UrlInfo getInfoFromRequest(HttpServletRequest httpRequest) {
+
+ String url = httpRequest.getRequestURL().toString();
+ Pattern p = Pattern.compile(REGEX_PATH);
+ Matcher m = p.matcher(url);
+
+
+ if (m.find()) {
+ RepositoryModel repo = gitblit.getRepositoryModel(m.group(REGEX_GROUP_REPOSITORY));
+ String baseUrl = m.group(REGEX_GROUP_BASE_URI) + "/" + m.group(REGEX_GROUP_PREFIX);
+
+ if (m.group(REGEX_GROUP_ENDPOINT).equals("batch")) {
+ return new UrlInfo(repo, null, baseUrl);
+ } else {
+ return new UrlInfo(repo, m.group(REGEX_GROUP_ENDPOINT), baseUrl);
+ }
+ }
+
+ return null;
+ }
+
+
+ public interface IGitLFS {
+
+ @SuppressWarnings("serial")
+ public class Request implements Serializable
+ {
+ public String oid;
+ public long size;
+ }
+
+
+ @SuppressWarnings("serial")
+ public class Batch implements Serializable
+ {
+ public String operation;
+ public List<Request> objects;
+ }
+
+
+ @SuppressWarnings("serial")
+ public class Response implements Serializable
+ {
+ public String oid;
+ public long size;
+ public Map<String, HyperMediaLink> actions;
+ public ObjectError error;
+ public transient int successCode;
+
+ public Response(String id, long itemSize, int errorCode, String errorText) {
+ oid = id;
+ size = itemSize;
+ actions = null;
+ successCode = 0;
+ error = new ObjectError(errorCode, errorText);
+ }
+
+ public Response(String id, long itemSize, int actionCode, String action, String uri) {
+ oid = id;
+ size = itemSize;
+ error = null;
+ successCode = actionCode;
+ actions = new HashMap<String, HyperMediaLink>();
+ actions.put(action, new HyperMediaLink(action, uri));
+ }
+
+ }
+
+ @SuppressWarnings("serial")
+ public class BatchResponse implements Serializable {
+ public List<Response> objects;
+
+ public BatchResponse() {
+ objects = new ArrayList<Response>();
+ }
+ }
+
+
+ @SuppressWarnings("serial")
+ public class ObjectError implements Serializable
+ {
+ public String message;
+ public int code;
+ public String documentation_url;
+ public Integer request_id;
+
+ public ObjectError(int errorCode, String errorText) {
+ code = errorCode;
+ message = errorText;
+ request_id = null;
+ }
+ }
+
+ @SuppressWarnings("serial")
+ public class HyperMediaLink implements Serializable
+ {
+ public String href;
+ public transient String header;
+ //public Date expires_at;
+
+ public HyperMediaLink(String action, String uri) {
+ header = action;
+ href = uri;
+ }
+ }
+ }
+
+
+
+}
diff --git a/src/main/java/com/gitblit/servlet/GitFilter.java b/src/main/java/com/gitblit/servlet/GitFilter.java
index bb3d321..9522893 100644
--- a/src/main/java/com/gitblit/servlet/GitFilter.java
+++ b/src/main/java/com/gitblit/servlet/GitFilter.java
@@ -17,7 +17,9 @@
import java.text.MessageFormat;
-import javax.servlet.FilterConfig;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
import javax.servlet.http.HttpServletRequest;
import com.gitblit.Constants.AccessRestrictionType;
@@ -25,13 +27,14 @@
import com.gitblit.GitBlitException;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
+import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
-import dagger.ObjectGraph;
-
/**
* The GitFilter is an AccessRestrictionFilter which ensures that Git client
* requests for push, clone, or view restricted repositories are authenticated
@@ -40,24 +43,34 @@
* @author James Moger
*
*/
+@Singleton
public class GitFilter extends AccessRestrictionFilter {
protected static final String gitReceivePack = "/git-receive-pack";
protected static final String gitUploadPack = "/git-upload-pack";
-
+
+ protected static final String gitLfs = "/info/lfs";
+
protected static final String[] suffixes = { gitReceivePack, gitUploadPack, "/info/refs", "/HEAD",
- "/objects" };
+ "/objects", gitLfs };
private IStoredSettings settings;
private IFederationManager federationManager;
- @Override
- protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
- super.inject(dagger, filterConfig);
- this.settings = dagger.get(IStoredSettings.class);
- this.federationManager = dagger.get(IFederationManager.class);
+ @Inject
+ public GitFilter(
+ IStoredSettings settings,
+ IRuntimeManager runtimeManager,
+ IAuthenticationManager authenticationManager,
+ IRepositoryManager repositoryManager,
+ IFederationManager federationManager) {
+
+ super(runtimeManager, authenticationManager, repositoryManager);
+
+ this.settings = settings;
+ this.federationManager = federationManager;
}
/**
@@ -89,8 +102,8 @@
}
/**
- * Analyze the url and returns the action of the request. Return values are
- * either "/git-receive-pack" or "/git-upload-pack".
+ * Analyze the url and returns the action of the request. Return values are:
+ * "/git-receive-pack", "/git-upload-pack" or "/info/lfs".
*
* @param serverUrl
* @return action of the request
@@ -106,6 +119,8 @@
return gitReceivePack;
} else if (suffix.contains("?service=git-upload-pack")) {
return gitUploadPack;
+ } else if (suffix.startsWith(gitLfs)) {
+ return gitLfs;
} else {
return gitUploadPack;
}
@@ -134,7 +149,13 @@
* @return true if the server allows repository creation on-push
*/
@Override
- protected boolean isCreationAllowed() {
+ protected boolean isCreationAllowed(String action) {
+
+ //Repository must already exist before large files can be deposited
+ if (action.equals(gitLfs)) {
+ return false;
+ }
+
return settings.getBoolean(Keys.git.allowCreateOnPush, true);
}
@@ -146,9 +167,15 @@
* @return true if the action may be performed
*/
@Override
- protected boolean isActionAllowed(RepositoryModel repository, String action) {
+ protected boolean isActionAllowed(RepositoryModel repository, String action, String method) {
// the log here has been moved into ReceiveHook to provide clients with
// error messages
+ if (gitLfs.equals(action)) {
+ if (!method.matches("GET|POST|PUT|HEAD")) {
+ return false;
+ }
+ }
+
return true;
}
@@ -162,16 +189,25 @@
*
* @param repository
* @param action
+ * @param method
* @return true if authentication required
*/
@Override
- protected boolean requiresAuthentication(RepositoryModel repository, String action) {
+ protected boolean requiresAuthentication(RepositoryModel repository, String action, String method) {
if (gitUploadPack.equals(action)) {
// send to client
return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE);
} else if (gitReceivePack.equals(action)) {
// receive from client
return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH);
+ } else if (gitLfs.equals(action)) {
+
+ if (method.matches("GET|HEAD")) {
+ return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE);
+ } else {
+ //NOTE: Treat POST as PUT as as without reading message type cannot determine
+ return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH);
+ }
}
return false;
}
@@ -220,6 +256,12 @@
@Override
protected RepositoryModel createRepository(UserModel user, String repository, String action) {
boolean isPush = !StringUtils.isEmpty(action) && gitReceivePack.equals(action);
+
+ if (action.equals(gitLfs)) {
+ //Repository must already exist for any filestore actions
+ return null;
+ }
+
if (isPush) {
if (user.canCreate(repository)) {
// user is pushing to a new repository
@@ -271,4 +313,44 @@
// repository could not be created or action was not a push
return null;
}
+
+ /**
+ * Git lfs action uses an alternative authentication header,
+ * dependent on the viewing method.
+ *
+ * @param httpRequest
+ * @param action
+ * @return
+ */
+ @Override
+ protected String getAuthenticationHeader(HttpServletRequest httpRequest, String action) {
+
+ if (action.equals(gitLfs)) {
+ if (hasContentInRequestHeader(httpRequest, "Accept", FilestoreServlet.GIT_LFS_META_MIME)) {
+ return "LFS-Authenticate";
+ }
+ }
+
+ return super.getAuthenticationHeader(httpRequest, action);
+ }
+
+ /**
+ * Interrogates the request headers based on the action
+ * @param action
+ * @param request
+ * @return
+ */
+ @Override
+ protected boolean hasValidRequestHeader(String action,
+ HttpServletRequest request) {
+
+ if (action.equals(gitLfs) && request.getMethod().equals("POST")) {
+ if ( !hasContentInRequestHeader(request, "Accept", FilestoreServlet.GIT_LFS_META_MIME)
+ || !hasContentInRequestHeader(request, "Content-Type", FilestoreServlet.GIT_LFS_META_MIME)) {
+ return false;
+ }
+ }
+
+ return super.hasValidRequestHeader(action, request);
+ }
}
diff --git a/src/main/java/com/gitblit/servlet/GitServlet.java b/src/main/java/com/gitblit/servlet/GitServlet.java
index 93fe31d..941b4c5 100644
--- a/src/main/java/com/gitblit/servlet/GitServlet.java
+++ b/src/main/java/com/gitblit/servlet/GitServlet.java
@@ -20,6 +20,8 @@
import java.io.IOException;
import java.util.Enumeration;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig;
@@ -33,14 +35,11 @@
import org.eclipse.jgit.http.server.GitFilter;
-import com.gitblit.dagger.DaggerContext;
import com.gitblit.git.GitblitReceivePackFactory;
import com.gitblit.git.GitblitUploadPackFactory;
import com.gitblit.git.RepositoryResolver;
import com.gitblit.manager.IGitblit;
-import dagger.ObjectGraph;
-
/**
* The GitServlet provides http/https access to Git repositories.
* Access to this servlet is protected by the GitFilter.
@@ -48,24 +47,23 @@
* @author James Moger
*
*/
+@Singleton
public class GitServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private final GitFilter gitFilter;
- public GitServlet() {
+ @Inject
+ public GitServlet(IGitblit gitblit) {
gitFilter = new GitFilter();
+ gitFilter.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>(gitblit));
+ gitFilter.setUploadPackFactory(new GitblitUploadPackFactory<HttpServletRequest>(gitblit));
+ gitFilter.setReceivePackFactory(new GitblitReceivePackFactory<HttpServletRequest>(gitblit));
}
@Override
public void init(final ServletConfig config) throws ServletException {
- ServletContext context = config.getServletContext();
- ObjectGraph dagger = (ObjectGraph) context.getAttribute(DaggerContext.INJECTOR_NAME);
- IGitblit gitblit = dagger.get(IGitblit.class);
- gitFilter.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>(gitblit));
- gitFilter.setUploadPackFactory(new GitblitUploadPackFactory<HttpServletRequest>(gitblit));
- gitFilter.setReceivePackFactory(new GitblitReceivePackFactory<HttpServletRequest>(gitblit));
gitFilter.init(new FilterConfig() {
@Override
diff --git a/src/main/java/com/gitblit/servlet/GitblitContext.java b/src/main/java/com/gitblit/servlet/GitblitContext.java
index e5c59bd..750da79 100644
--- a/src/main/java/com/gitblit/servlet/GitblitContext.java
+++ b/src/main/java/com/gitblit/servlet/GitblitContext.java
@@ -24,6 +24,7 @@
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import javax.naming.Context;
import javax.naming.InitialContext;
@@ -31,16 +32,20 @@
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import com.gitblit.Constants;
-import com.gitblit.DaggerModule;
import com.gitblit.FileSettings;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.WebXmlSettings;
-import com.gitblit.dagger.DaggerContext;
import com.gitblit.extensions.LifeCycleListener;
+import com.gitblit.guice.CoreModule;
+import com.gitblit.guice.WebModule;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IFilestoreManager;
import com.gitblit.manager.IGitblit;
import com.gitblit.manager.IManager;
import com.gitblit.manager.INotificationManager;
@@ -48,28 +53,34 @@
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IServicesManager;
import com.gitblit.manager.IUserManager;
+import com.gitblit.tickets.ITicketService;
import com.gitblit.transport.ssh.IPublicKeyManager;
import com.gitblit.utils.ContainerUtils;
import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.servlet.GuiceServletContextListener;
/**
* This class is the main entry point for the entire webapp. It is a singleton
* created manually by Gitblit GO or dynamically by the WAR/Express servlet
- * container. This class instantiates and starts all managers. Servlets and
- * filters are instantiated defined in web.xml and instantiated by the servlet
- * container, but those servlets and filters use Dagger to manually inject their
- * dependencies.
+ * container. This class instantiates and starts all managers.
+ *
+ * Servlets and filters are injected which allows Gitblit to be completely
+ * code-driven.
*
* @author James Moger
*
*/
-public class GitblitContext extends DaggerContext {
+public class GitblitContext extends GuiceServletContextListener {
private static GitblitContext gitblit;
+ protected final Logger logger = LoggerFactory.getLogger(getClass());
+
private final List<IManager> managers = new ArrayList<IManager>();
private final IStoredSettings goSettings;
@@ -111,12 +122,16 @@
return null;
}
- /**
- * Returns Gitblit's Dagger injection modules.
- */
@Override
- protected Object [] getModules() {
- return new Object [] { new DaggerModule() };
+ protected Injector getInjector() {
+ return Guice.createInjector(getModules());
+ }
+
+ /**
+ * Returns Gitblit's Guice injection modules.
+ */
+ protected AbstractModule [] getModules() {
+ return new AbstractModule [] { new CoreModule(), new WebModule() };
}
/**
@@ -127,18 +142,20 @@
*/
@Override
public final void contextInitialized(ServletContextEvent contextEvent) {
+ super.contextInitialized(contextEvent);
+
ServletContext context = contextEvent.getServletContext();
- configureContext(context);
+ startCore(context);
}
/**
* Prepare runtime settings and start all manager instances.
*/
- protected void configureContext(ServletContext context) {
- ObjectGraph injector = getInjector(context);
+ protected void startCore(ServletContext context) {
+ Injector injector = (Injector) context.getAttribute(Injector.class.getName());
// create the runtime settings object
- IStoredSettings runtimeSettings = injector.get(IStoredSettings.class);
+ IStoredSettings runtimeSettings = injector.getInstance(IStoredSettings.class);
final File baseFolder;
if (goSettings != null) {
@@ -168,7 +185,7 @@
// Manually configure IRuntimeManager
logManager(IRuntimeManager.class);
- IRuntimeManager runtime = injector.get(IRuntimeManager.class);
+ IRuntimeManager runtime = injector.getInstance(IRuntimeManager.class);
runtime.setBaseFolder(baseFolder);
runtime.getStatus().isGO = goSettings != null;
runtime.getStatus().servletContainer = context.getServerInfo();
@@ -186,7 +203,10 @@
startManager(injector, IRepositoryManager.class);
startManager(injector, IProjectManager.class);
startManager(injector, IFederationManager.class);
+ startManager(injector, ITicketService.class);
startManager(injector, IGitblit.class);
+ startManager(injector, IServicesManager.class);
+ startManager(injector, IFilestoreManager.class);
// start the plugin manager last so that plugins can depend on
// deterministic access to all other managers in their start() methods
@@ -196,7 +216,7 @@
logger.info("All managers started.");
logger.info("");
- IPluginManager pluginManager = injector.get(IPluginManager.class);
+ IPluginManager pluginManager = injector.getInstance(IPluginManager.class);
for (LifeCycleListener listener : pluginManager.getExtensions(LifeCycleListener.class)) {
try {
listener.onStartup();
@@ -236,17 +256,21 @@
return defaultBaseFolder;
}
- protected <X extends IManager> X loadManager(ObjectGraph injector, Class<X> clazz) {
- X x = injector.get(clazz);
+ protected <X extends IManager> X loadManager(Injector injector, Class<X> clazz) {
+ X x = injector.getInstance(clazz);
return x;
}
- protected <X extends IManager> X startManager(ObjectGraph injector, Class<X> clazz) {
+ protected <X extends IManager> X startManager(Injector injector, Class<X> clazz) {
X x = loadManager(injector, clazz);
logManager(clazz);
- x.start();
- managers.add(x);
- return x;
+ return startManager(x);
+ }
+
+ protected <X extends IManager> X startManager(X x) {
+ x.start();
+ managers.add(x);
+ return x;
}
protected void logManager(Class<? extends IManager> clazz) {
@@ -254,11 +278,17 @@
logger.info("----[{}]----", clazz.getName());
}
+ @Override
+ public final void contextDestroyed(ServletContextEvent contextEvent) {
+ super.contextDestroyed(contextEvent);
+ ServletContext context = contextEvent.getServletContext();
+ destroyContext(context);
+ }
+
/**
* Gitblit is being shutdown either because the servlet container is
* shutting down or because the servlet container is re-deploying Gitblit.
*/
- @Override
protected void destroyContext(ServletContext context) {
logger.info("Gitblit context destroyed by servlet container.");
@@ -341,12 +371,10 @@
baseFolder.mkdirs();
// try to extract the data folder resource to the baseFolder
- File localSettings = new File(baseFolder, "gitblit.properties");
- if (!localSettings.exists()) {
- extractResources(context, "/WEB-INF/data/", baseFolder);
- }
+ extractResources(context, "/WEB-INF/data/", baseFolder);
// delegate all config to baseFolder/gitblit.properties file
+ File localSettings = new File(baseFolder, "gitblit.properties");
FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath());
// merge the stored settings into the runtime settings
@@ -428,7 +456,12 @@
}
protected void extractResources(ServletContext context, String path, File toDir) {
- for (String resource : context.getResourcePaths(path)) {
+ Set<String> resources = context.getResourcePaths(path);
+ if (resources == null) {
+ logger.warn("There are no WAR resources to extract from {}", path);
+ return;
+ }
+ for (String resource : resources) {
// extract the resource to the directory if it does not exist
File f = new File(toDir, resource.substring(path.length()));
if (!f.exists()) {
diff --git a/src/main/java/com/gitblit/servlet/JsonServlet.java b/src/main/java/com/gitblit/servlet/JsonServlet.java
index 4378c8a..abc0f29 100644
--- a/src/main/java/com/gitblit/servlet/JsonServlet.java
+++ b/src/main/java/com/gitblit/servlet/JsonServlet.java
@@ -21,6 +21,7 @@
import java.text.MessageFormat;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -28,7 +29,6 @@
import org.slf4j.LoggerFactory;
import com.gitblit.Constants;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.utils.JsonUtils;
import com.gitblit.utils.StringUtils;
@@ -38,7 +38,7 @@
* @author James Moger
*
*/
-public abstract class JsonServlet extends DaggerServlet {
+public abstract class JsonServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
diff --git a/src/main/java/com/gitblit/servlet/LogoServlet.java b/src/main/java/com/gitblit/servlet/LogoServlet.java
index 96f34af..d5d298b 100644
--- a/src/main/java/com/gitblit/servlet/LogoServlet.java
+++ b/src/main/java/com/gitblit/servlet/LogoServlet.java
@@ -21,24 +21,25 @@
import java.io.InputStream;
import java.io.OutputStream;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.manager.IRuntimeManager;
-import dagger.ObjectGraph;
-
/**
* Handles requests for logo.png
*
* @author James Moger
*
*/
-public class LogoServlet extends DaggerServlet {
+@Singleton
+public class LogoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@@ -46,9 +47,9 @@
private IRuntimeManager runtimeManager;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.runtimeManager = dagger.get(IRuntimeManager.class);
+ @Inject
+ public LogoServlet(IRuntimeManager runtimeManager) {
+ this.runtimeManager = runtimeManager;
}
@Override
diff --git a/src/main/java/com/gitblit/servlet/PagesFilter.java b/src/main/java/com/gitblit/servlet/PagesFilter.java
index e07d9b3..1d6c3db 100644
--- a/src/main/java/com/gitblit/servlet/PagesFilter.java
+++ b/src/main/java/com/gitblit/servlet/PagesFilter.java
@@ -15,6 +15,13 @@
*/
package com.gitblit.servlet;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+
/**
* The PagesFilter is an AccessRestrictionFilter which ensures the gh-pages
@@ -23,7 +30,17 @@
* @author James Moger
*
*/
+
+@Singleton
public class PagesFilter extends RawFilter {
+ @Inject
+ public PagesFilter(
+ IRuntimeManager runtimeManager,
+ IAuthenticationManager authenticationManager,
+ IRepositoryManager repositoryManager) {
+
+ super(runtimeManager, authenticationManager, repositoryManager);
+ }
}
diff --git a/src/main/java/com/gitblit/servlet/PagesServlet.java b/src/main/java/com/gitblit/servlet/PagesServlet.java
index defd9e6..1473e96 100644
--- a/src/main/java/com/gitblit/servlet/PagesServlet.java
+++ b/src/main/java/com/gitblit/servlet/PagesServlet.java
@@ -1,103 +1,116 @@
-/*
- * Copyright 2012 gitblit.com.
- *
- * 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.
- */
-package com.gitblit.servlet;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Date;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.Constants;
-import com.gitblit.utils.JGitUtils;
-
-/**
- * Serves the content of a gh-pages branch.
- *
- * @author James Moger
- *
- */
-public class PagesServlet extends RawServlet {
-
- private static final long serialVersionUID = 1L;
-
-
- /**
- * Returns an url to this servlet for the specified parameters.
- *
- * @param baseURL
- * @param repository
- * @param path
- * @return an url
- */
- public static String asLink(String baseURL, String repository, String path) {
- if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
- baseURL = baseURL.substring(0, baseURL.length() - 1);
- }
- return baseURL + Constants.PAGES + repository + "/" + (path == null ? "" : ("/" + path));
- }
-
- @Override
- protected String getBranch(String repository, HttpServletRequest request) {
- return "gh-pages";
- }
-
- @Override
- protected String getPath(String repository, String branch, HttpServletRequest request) {
- String pi = request.getPathInfo().substring(1);
- if (pi.equals(repository)) {
- return "";
- }
- String path = pi.substring(pi.indexOf(repository) + repository.length() + 1);
- if (path.endsWith("/")) {
- path = path.substring(0, path.length() - 1);
- }
- return path;
- }
-
- @Override
- protected boolean renderIndex() {
- return true;
- }
-
- @Override
- protected void setContentType(HttpServletResponse response, String contentType) {
- response.setContentType(contentType);;
- }
-
- @Override
- protected boolean streamFromRepo(HttpServletRequest request, HttpServletResponse response, Repository repository,
- RevCommit commit, String requestedPath) throws IOException {
-
- response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime());
- response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
-
- return super.streamFromRepo(request, response, repository, commit, requestedPath);
- }
-
- @Override
- protected void sendContent(HttpServletResponse response, Date date, InputStream is) throws ServletException, IOException {
- response.setDateHeader("Last-Modified", date.getTime());
- response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
-
- super.sendContent(response, date, is);
- }
-}
+/*
+ * Copyright 2012 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Date;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.Constants;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.utils.JGitUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * Serves the content of a gh-pages branch.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class PagesServlet extends RawServlet {
+
+ private static final long serialVersionUID = 1L;
+
+
+ /**
+ * Returns an url to this servlet for the specified parameters.
+ *
+ * @param baseURL
+ * @param repository
+ * @param path
+ * @return an url
+ */
+ public static String asLink(String baseURL, String repository, String path) {
+ if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
+ baseURL = baseURL.substring(0, baseURL.length() - 1);
+ }
+ return baseURL + Constants.PAGES + repository + "/" + (path == null ? "" : ("/" + path));
+ }
+
+ @Inject
+ public PagesServlet(
+ IRuntimeManager runtimeManager,
+ IRepositoryManager repositoryManager) {
+
+ super(runtimeManager, repositoryManager);
+ }
+
+ @Override
+ protected String getBranch(String repository, HttpServletRequest request) {
+ return "gh-pages";
+ }
+
+ @Override
+ protected String getPath(String repository, String branch, HttpServletRequest request) {
+ String pi = request.getPathInfo().substring(1);
+ if (pi.equals(repository)) {
+ return "";
+ }
+ String path = pi.substring(pi.indexOf(repository) + repository.length() + 1);
+ if (path.endsWith("/")) {
+ path = path.substring(0, path.length() - 1);
+ }
+ return path;
+ }
+
+ @Override
+ protected boolean renderIndex() {
+ return true;
+ }
+
+ @Override
+ protected void setContentType(HttpServletResponse response, String contentType) {
+ response.setContentType(contentType);;
+ }
+
+ @Override
+ protected boolean streamFromRepo(HttpServletRequest request, HttpServletResponse response, Repository repository,
+ RevCommit commit, String requestedPath) throws IOException {
+
+ response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime());
+ response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
+
+ return super.streamFromRepo(request, response, repository, commit, requestedPath);
+ }
+
+ @Override
+ protected void sendContent(HttpServletResponse response, Date date, InputStream is) throws ServletException, IOException {
+ response.setDateHeader("Last-Modified", date.getTime());
+ response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
+
+ super.sendContent(response, date, is);
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/ProxyFilter.java b/src/main/java/com/gitblit/servlet/ProxyFilter.java
index 46f59de..d7f096a 100644
--- a/src/main/java/com/gitblit/servlet/ProxyFilter.java
+++ b/src/main/java/com/gitblit/servlet/ProxyFilter.java
@@ -16,9 +16,13 @@
package com.gitblit.servlet;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
@@ -27,13 +31,10 @@
import ro.fortsoft.pf4j.PluginWrapper;
-import com.gitblit.dagger.DaggerFilter;
import com.gitblit.extensions.HttpRequestFilter;
import com.gitblit.manager.IPluginManager;
import com.gitblit.manager.IRuntimeManager;
-import dagger.ObjectGraph;
-
/**
* A request filter than allows registered extension request filters to access
* request data. The intended purpose is for server monitoring plugins.
@@ -41,15 +42,29 @@
* @author David Ostrovsky
* @since 1.6.0
*/
-public class ProxyFilter extends DaggerFilter {
- private List<HttpRequestFilter> filters;
+@Singleton
+public class ProxyFilter implements Filter {
+ private final IRuntimeManager runtimeManager;
+
+ private final IPluginManager pluginManager;
+
+ private final List<HttpRequestFilter> filters;
+
+ @Inject
+ public ProxyFilter(
+ IRuntimeManager runtimeManager,
+ IPluginManager pluginManager) {
+
+ this.runtimeManager = runtimeManager;
+ this.pluginManager = pluginManager;
+ this.filters = new ArrayList<>();
+
+ }
@Override
- protected void inject(ObjectGraph dagger, FilterConfig filterConfig) throws ServletException {
- IRuntimeManager runtimeManager = dagger.get(IRuntimeManager.class);
- IPluginManager pluginManager = dagger.get(IPluginManager.class);
+ public void init(FilterConfig filterConfig) throws ServletException {
- filters = pluginManager.getExtensions(HttpRequestFilter.class);
+ filters.addAll(pluginManager.getExtensions(HttpRequestFilter.class));
for (HttpRequestFilter f : filters) {
// wrap the filter config for Gitblit settings retrieval
PluginWrapper pluginWrapper = pluginManager.whichPlugin(f.getClass());
diff --git a/src/main/java/com/gitblit/servlet/PtServlet.java b/src/main/java/com/gitblit/servlet/PtServlet.java
index f69b444..5f577f8 100644
--- a/src/main/java/com/gitblit/servlet/PtServlet.java
+++ b/src/main/java/com/gitblit/servlet/PtServlet.java
@@ -22,7 +22,10 @@
import java.io.InputStream;
import java.io.OutputStream;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -34,11 +37,8 @@
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.eclipse.jgit.lib.FileMode;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.manager.IRuntimeManager;
-import dagger.ObjectGraph;
-
/**
* Handles requests for the Barnum pt (patchset tool).
*
@@ -47,7 +47,8 @@
* @author James Moger
*
*/
-public class PtServlet extends DaggerServlet {
+@Singleton
+public class PtServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@@ -55,9 +56,9 @@
private IRuntimeManager runtimeManager;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.runtimeManager = dagger.get(IRuntimeManager.class);
+ @Inject
+ public PtServlet(IRuntimeManager runtimeManager) {
+ this.runtimeManager = runtimeManager;
}
@Override
diff --git a/src/main/java/com/gitblit/servlet/RawFilter.java b/src/main/java/com/gitblit/servlet/RawFilter.java
index 34989c9..8913a19 100644
--- a/src/main/java/com/gitblit/servlet/RawFilter.java
+++ b/src/main/java/com/gitblit/servlet/RawFilter.java
@@ -15,9 +15,15 @@
*/
package com.gitblit.servlet;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
import org.eclipse.jgit.lib.Repository;
import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
@@ -28,8 +34,18 @@
* @author James Moger
*
*/
+@Singleton
public class RawFilter extends AccessRestrictionFilter {
+ @Inject
+ public RawFilter(
+ IRuntimeManager runtimeManager,
+ IAuthenticationManager authenticationManager,
+ IRepositoryManager repositoryManager) {
+
+ super(runtimeManager, authenticationManager, repositoryManager);
+ }
+
/**
* Extract the repository name from the url.
*
@@ -82,7 +98,7 @@
* @return true if the filter allows repository creation
*/
@Override
- protected boolean isCreationAllowed() {
+ protected boolean isCreationAllowed(String action) {
return false;
}
@@ -91,10 +107,11 @@
*
* @param repository
* @param action
+ * @param method
* @return true if the action may be performed
*/
@Override
- protected boolean isActionAllowed(RepositoryModel repository, String action) {
+ protected boolean isActionAllowed(RepositoryModel repository, String action, String method) {
return true;
}
@@ -103,10 +120,11 @@
*
* @param repository
* @param action
+ * @param method
* @return true if authentication required
*/
@Override
- protected boolean requiresAuthentication(RepositoryModel repository, String action) {
+ protected boolean requiresAuthentication(RepositoryModel repository, String action, String method) {
return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW);
}
diff --git a/src/main/java/com/gitblit/servlet/RawServlet.java b/src/main/java/com/gitblit/servlet/RawServlet.java
index ca41d0a..dca5773 100644
--- a/src/main/java/com/gitblit/servlet/RawServlet.java
+++ b/src/main/java/com/gitblit/servlet/RawServlet.java
@@ -24,12 +24,14 @@
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -48,7 +50,6 @@
import com.gitblit.Constants;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.PathModel;
@@ -56,8 +57,8 @@
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* Serves the content of a branch.
@@ -65,20 +66,24 @@
* @author James Moger
*
*/
-public class RawServlet extends DaggerServlet {
+@Singleton
+public class RawServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private transient Logger logger = LoggerFactory.getLogger(RawServlet.class);
- private IRuntimeManager runtimeManager;
+ private final IRuntimeManager runtimeManager;
- private IRepositoryManager repositoryManager;
+ private final IRepositoryManager repositoryManager;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.runtimeManager = dagger.get(IRuntimeManager.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
+ @Inject
+ public RawServlet(
+ IRuntimeManager runtimeManager,
+ IRepositoryManager repositoryManager) {
+
+ this.runtimeManager = runtimeManager;
+ this.repositoryManager = repositoryManager;
}
/**
@@ -161,23 +166,14 @@
}
// determine repository and resource from url
- String repository = "";
+ String repository = path;
Repository r = null;
- int offset = 0;
- while (r == null) {
- int slash = path.indexOf('/', offset);
- if (slash == -1) {
- repository = path;
- } else {
- repository = path.substring(0, slash);
- }
- offset = ( slash + 1 );
+ int terminator = repository.length();
+ do {
+ repository = repository.substring(0, terminator);
r = repositoryManager.getRepository(repository, false);
- if (repository.equals(path)) {
- // either only repository in url or no repository found
- break;
- }
- }
+ terminator = repository.lastIndexOf('/');
+ } while (r == null && terminator > -1 );
ServletContext context = request.getSession().getServletContext();
@@ -224,15 +220,39 @@
return;
}
+ Map<String, String> quickContentTypes = new HashMap<>();
+ quickContentTypes.put("html", "text/html");
+ quickContentTypes.put("htm", "text/html");
+ quickContentTypes.put("xml", "application/xml");
+ quickContentTypes.put("json", "application/json");
List<PathModel> pathEntries = JGitUtils.getFilesInPath(r, requestedPath, commit);
if (pathEntries.isEmpty()) {
// requested a specific resource
String file = StringUtils.getLastPathElement(requestedPath);
try {
- // query Tika for the content type
- Tika tika = new Tika();
- String contentType = tika.detect(file);
+
+ String ext = StringUtils.getFileExtension(file).toLowerCase();
+ // We can't parse out an extension for classic "dotfiles", so make a general assumption that
+ // they're text files to allow presenting them in browser instead of only for download.
+ //
+ // However, that only holds for files with no other extension included, for files that happen
+ // to start with a dot but also include an extension, process the extension normally.
+ // This logic covers .gitattributes, .gitignore, .zshrc, etc., but does not cover .mongorc.js, .zshrc.bak
+ boolean isExtensionlessDotfile = file.charAt(0) == '.' && (file.length() == 1 || file.indexOf('.', 1) < 0);
+ String contentType = isExtensionlessDotfile ? "text/plain" : quickContentTypes.get(ext);
+
+ if (contentType == null) {
+ List<String> exts = runtimeManager.getSettings().getStrings(Keys.web.prettyPrintExtensions);
+ if (exts.contains(ext)) {
+ // extension is a registered text type for pretty printing
+ contentType = "text/plain";
+ } else {
+ // query Tika for the content type
+ Tika tika = new Tika();
+ contentType = tika.detect(file);
+ }
+ }
if (contentType == null) {
// ask the container for the content type
@@ -244,7 +264,7 @@
}
}
- if (isTextType(contentType)) {
+ if (isTextType(contentType) || isTextDataType(contentType)) {
// load, interpret, and serve text content as UTF-8
String [] encodings = runtimeManager.getSettings().getStrings(Keys.web.blobEncodings).toArray(new String[0]);
@@ -344,7 +364,7 @@
if (pathEntries.get(0).path.indexOf('/') > -1) {
// we are in a subdirectory, add parent directory link
String pp = URLEncoder.encode(requestedPath, Constants.ENCODING);
- pathEntries.add(0, new PathModel("..", pp + "/..", 0, FileMode.TREE.getBits(), null, null));
+ pathEntries.add(0, new PathModel("..", pp + "/..", null, 0, FileMode.TREE.getBits(), null, null));
}
}
@@ -378,6 +398,13 @@
return false;
}
+ protected boolean isTextDataType(String contentType) {
+ if ("image/svg+xml".equals(contentType)) {
+ return true;
+ }
+ return false;
+ }
+
/**
* Override all text types to be plain text.
*
@@ -439,7 +466,7 @@
served = true;
}
} finally {
- tw.release();
+ tw.close();
rw.dispose();
}
diff --git a/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java b/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java
index 9bd3b3c..4e58d4d 100644
--- a/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java
+++ b/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java
@@ -18,32 +18,33 @@
import java.io.File;
import java.io.IOException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.utils.FileUtils;
-import dagger.ObjectGraph;
-
/**
* Handles requests for robots.txt
*
* @author James Moger
*
*/
-public class RobotsTxtServlet extends DaggerServlet {
+@Singleton
+public class RobotsTxtServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private IRuntimeManager runtimeManager;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.runtimeManager = dagger.get(IRuntimeManager.class);
+ @Inject
+ public RobotsTxtServlet(IRuntimeManager runtimeManager) {
+ this.runtimeManager = runtimeManager;
}
@Override
diff --git a/src/main/java/com/gitblit/servlet/RpcFilter.java b/src/main/java/com/gitblit/servlet/RpcFilter.java
index 23bf956..355bcb9 100644
--- a/src/main/java/com/gitblit/servlet/RpcFilter.java
+++ b/src/main/java/com/gitblit/servlet/RpcFilter.java
@@ -18,8 +18,9 @@
import java.io.IOException;
import java.text.MessageFormat;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@@ -29,11 +30,10 @@
import com.gitblit.Constants.RpcRequest;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
+import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.UserModel;
-import dagger.ObjectGraph;
-
/**
* The RpcFilter is a servlet filter that secures the RpcServlet.
*
@@ -47,17 +47,23 @@
* @author James Moger
*
*/
+@Singleton
public class RpcFilter extends AuthenticationFilter {
private IStoredSettings settings;
private IRuntimeManager runtimeManager;
- @Override
- protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
- super.inject(dagger, filterConfig);
- this.settings = dagger.get(IStoredSettings.class);
- this.runtimeManager = dagger.get(IRuntimeManager.class);
+ @Inject
+ public RpcFilter(
+ IStoredSettings settings,
+ IRuntimeManager runtimeManager,
+ IAuthenticationManager authenticationManager) {
+
+ super(authenticationManager);
+
+ this.settings = settings;
+ this.runtimeManager = runtimeManager;
}
/**
@@ -122,7 +128,7 @@
return;
} else {
// check user access for request
- if (user.canAdmin() || canAccess(user, requestType)) {
+ if (user.canAdmin() || !adminRequest) {
// authenticated request permitted.
// pass processing to the restricted servlet.
newSession(authenticatedRequest, httpResponse);
@@ -147,15 +153,4 @@
// pass processing to the restricted servlet.
chain.doFilter(authenticatedRequest, httpResponse);
}
-
- private boolean canAccess(UserModel user, RpcRequest requestType) {
- switch (requestType) {
- case GET_PROTOCOL:
- return true;
- case LIST_REPOSITORIES:
- return true;
- default:
- return user.canAdmin();
- }
- }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/gitblit/servlet/RpcServlet.java b/src/main/java/com/gitblit/servlet/RpcServlet.java
index b8cdfb0..9809a25 100644
--- a/src/main/java/com/gitblit/servlet/RpcServlet.java
+++ b/src/main/java/com/gitblit/servlet/RpcServlet.java
@@ -23,6 +23,8 @@
import java.util.List;
import java.util.Map;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -47,13 +49,12 @@
import com.gitblit.utils.RpcUtils;
import com.gitblit.utils.StringUtils;
-import dagger.ObjectGraph;
-
/**
* Handles remote procedure calls.
*
* @author James Moger
*/
+@Singleton
public class RpcServlet extends JsonServlet {
private static final long serialVersionUID = 1L;
@@ -64,10 +65,10 @@
private IGitblit gitblit;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.settings = dagger.get(IStoredSettings.class);
- this.gitblit = dagger.get(IGitblit.class);
+ @Inject
+ public RpcServlet(IStoredSettings settings, IGitblit gitblit) {
+ this.settings = settings;
+ this.gitblit = gitblit;
}
/**
diff --git a/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java b/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java
index 150dd68..e989ece 100644
--- a/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java
+++ b/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java
@@ -20,20 +20,20 @@
import java.text.MessageFormat;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* Handles requests for Sparkleshare Invites
@@ -41,7 +41,8 @@
* @author James Moger
*
*/
-public class SparkleShareInviteServlet extends DaggerServlet {
+@Singleton
+public class SparkleShareInviteServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@@ -53,12 +54,17 @@
private IRepositoryManager repositoryManager;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.settings = dagger.get(IStoredSettings.class);
- this.userManager = dagger.get(IUserManager.class);
- this.authenticationManager = dagger.get(IAuthenticationManager.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
+ @Inject
+ public SparkleShareInviteServlet(
+ IStoredSettings settings,
+ IUserManager userManager,
+ IAuthenticationManager authenticationManager,
+ IRepositoryManager repositoryManager) {
+
+ this.settings = settings;
+ this.userManager = userManager;
+ this.authenticationManager = authenticationManager;
+ this.repositoryManager = repositoryManager;
}
@Override
@@ -83,6 +89,8 @@
response.getWriter().append("SSH is not active on this server!");
return;
}
+ int sshDisplayPort = settings.getInteger(Keys.git.sshAdvertisedPort, sshPort);
+
// extract repo name from request
String repoUrl = request.getPathInfo().substring(1);
@@ -106,6 +114,10 @@
if (!StringUtils.isEmpty(url) && url.indexOf("localhost") == -1) {
host = new URL(url).getHost();
}
+ String sshDisplayHost = settings.getString(Keys.git.sshAdvertisedHost, "");
+ if(sshDisplayHost.isEmpty()) {
+ sshDisplayHost = host;
+ }
UserModel user;
if (StringUtils.isEmpty(username)) {
@@ -135,7 +147,7 @@
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
sb.append("<sparkleshare><invite>\n");
- sb.append(MessageFormat.format("<address>ssh://{0}@{1}:{2,number,0}/</address>\n", user.username, host, sshPort));
+ sb.append(MessageFormat.format("<address>ssh://{0}@{1}:{2,number,0}/</address>\n", user.username, sshDisplayHost, sshDisplayPort));
sb.append(MessageFormat.format("<remote_path>/{0}</remote_path>\n", model.name));
int fanoutPort = settings.getInteger(Keys.fanout.port, 0);
if (fanoutPort > 0) {
diff --git a/src/main/java/com/gitblit/servlet/SyndicationFilter.java b/src/main/java/com/gitblit/servlet/SyndicationFilter.java
index 78da47e..49348d0 100644
--- a/src/main/java/com/gitblit/servlet/SyndicationFilter.java
+++ b/src/main/java/com/gitblit/servlet/SyndicationFilter.java
@@ -18,8 +18,9 @@
import java.io.IOException;
import java.text.MessageFormat;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@@ -27,6 +28,7 @@
import javax.servlet.http.HttpServletResponse;
import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
@@ -34,8 +36,6 @@
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
-import dagger.ObjectGraph;
-
/**
* The SyndicationFilter is an AuthenticationFilter which ensures that feed
* requests for projects or view-restricted repositories have proper authentication
@@ -44,18 +44,24 @@
* @author James Moger
*
*/
+@Singleton
public class SyndicationFilter extends AuthenticationFilter {
private IRuntimeManager runtimeManager;
private IRepositoryManager repositoryManager;
private IProjectManager projectManager;
- @Override
- protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
- super.inject(dagger, filterConfig);
- this.runtimeManager = dagger.get(IRuntimeManager.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
- this.projectManager = dagger.get(IProjectManager.class);
+ @Inject
+ public SyndicationFilter(
+ IRuntimeManager runtimeManager,
+ IAuthenticationManager authenticationManager,
+ IRepositoryManager repositoryManager,
+ IProjectManager projectManager) {
+ super(authenticationManager);
+
+ this.runtimeManager = runtimeManager;
+ this.repositoryManager = repositoryManager;
+ this.projectManager = projectManager;
}
/**
diff --git a/src/main/java/com/gitblit/servlet/SyndicationServlet.java b/src/main/java/com/gitblit/servlet/SyndicationServlet.java
index e3c2596..39dbf2e 100644
--- a/src/main/java/com/gitblit/servlet/SyndicationServlet.java
+++ b/src/main/java/com/gitblit/servlet/SyndicationServlet.java
@@ -22,6 +22,8 @@
import java.util.List;
import java.util.Map;
+import javax.servlet.http.HttpServlet;
+
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -31,7 +33,6 @@
import com.gitblit.Constants;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.models.FeedEntryModel;
@@ -45,8 +46,8 @@
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.SyndicationUtils;
-
-import dagger.ObjectGraph;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* SyndicationServlet generates RSS 2.0 feeds and feed links.
@@ -56,7 +57,8 @@
* @author James Moger
*
*/
-public class SyndicationServlet extends DaggerServlet {
+@Singleton
+public class SyndicationServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@@ -68,11 +70,15 @@
private IProjectManager projectManager;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.settings = dagger.get(IStoredSettings.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
- this.projectManager = dagger.get(IProjectManager.class);
+ @Inject
+ public SyndicationServlet(
+ IStoredSettings settings,
+ IRepositoryManager repositoryManager,
+ IProjectManager projectManager) {
+
+ this.settings = settings;
+ this.repositoryManager = repositoryManager;
+ this.projectManager = projectManager;
}
/**
@@ -362,7 +368,7 @@
if (mountParameters) {
// mounted url
feedLink = MessageFormat.format("{0}/summary/{1}", gitblitUrl,
- StringUtils.encodeURL(feedName));
+ StringUtils.encodeURL(feedName.replace('/', fsc)));
} else {
// parameterized url
feedLink = MessageFormat.format("{0}/summary/?r={1}", gitblitUrl,
diff --git a/src/main/java/com/gitblit/tickets/BranchTicketService.java b/src/main/java/com/gitblit/tickets/BranchTicketService.java
index 5a42c6a..7bef435 100644
--- a/src/main/java/com/gitblit/tickets/BranchTicketService.java
+++ b/src/main/java/com/gitblit/tickets/BranchTicketService.java
@@ -19,7 +19,6 @@
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -31,21 +30,17 @@
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
-import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.events.RefsChangedEvent;
import org.eclipse.jgit.events.RefsChangedListener;
-import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefRename;
-import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -72,6 +67,8 @@
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* Implementation of a ticket service based on an orphan branch. All tickets
@@ -81,6 +78,7 @@
* @author James Moger
*
*/
+@Singleton
public class BranchTicketService extends ITicketService implements RefsChangedListener {
public static final String BRANCH = "refs/meta/gitblit/tickets";
@@ -91,6 +89,7 @@
private final Map<String, AtomicLong> lastAssignedId;
+ @Inject
public BranchTicketService(
IRuntimeManager runtimeManager,
IPluginManager pluginManager,
@@ -112,6 +111,7 @@
@Override
public BranchTicketService start() {
+ log.info("{} started", getClass().getSimpleName());
return this;
}
@@ -292,7 +292,7 @@
log.error("failed to read " + file, e);
} finally {
if (rw != null) {
- rw.release();
+ rw.close();
}
}
return null;
@@ -333,7 +333,7 @@
Set<String> ignorePaths = new HashSet<String>();
ignorePaths.add(file);
- for (DirCacheEntry entry : getTreeEntries(db, ignorePaths)) {
+ for (DirCacheEntry entry : JGitUtils.getTreeEntries(db, BRANCH, ignorePaths)) {
builder.add(entry);
}
@@ -348,7 +348,7 @@
} catch (IOException e) {
log.error("", e);
} finally {
- inserter.release();
+ inserter.close();
}
}
@@ -707,7 +707,7 @@
} finally {
// release the treewalk
if (treeWalk != null) {
- treeWalk.release();
+ treeWalk.close();
}
}
} finally {
@@ -799,120 +799,30 @@
}
}
- for (DirCacheEntry entry : getTreeEntries(db, ignorePaths)) {
+ for (DirCacheEntry entry : JGitUtils.getTreeEntries(db, BRANCH, ignorePaths)) {
builder.add(entry);
}
// finish the index
builder.finish();
} finally {
- inserter.release();
+ inserter.close();
}
return newIndex;
}
- /**
- * Returns all tree entries that do not match the ignore paths.
- *
- * @param db
- * @param ignorePaths
- * @param dcBuilder
- * @throws IOException
- */
- private List<DirCacheEntry> getTreeEntries(Repository db, Collection<String> ignorePaths) throws IOException {
- List<DirCacheEntry> list = new ArrayList<DirCacheEntry>();
- TreeWalk tw = null;
- try {
- ObjectId treeId = db.resolve(BRANCH + "^{tree}");
- if (treeId == null) {
- // branch does not exist yet, could be migrating tickets
- return list;
- }
- tw = new TreeWalk(db);
- int hIdx = tw.addTree(treeId);
- tw.setRecursive(true);
-
- while (tw.next()) {
- String path = tw.getPathString();
- CanonicalTreeParser hTree = null;
- if (hIdx != -1) {
- hTree = tw.getTree(hIdx, CanonicalTreeParser.class);
- }
- if (!ignorePaths.contains(path)) {
- // add all other tree entries
- if (hTree != null) {
- final DirCacheEntry entry = new DirCacheEntry(path);
- entry.setObjectId(hTree.getEntryObjectId());
- entry.setFileMode(hTree.getEntryFileMode());
- list.add(entry);
- }
- }
- }
- } finally {
- if (tw != null) {
- tw.release();
- }
- }
- return list;
- }
-
private boolean commitIndex(Repository db, DirCache index, String author, String message) throws IOException, ConcurrentRefUpdateException {
+ final boolean forceCommit = true;
boolean success = false;
-
+
ObjectId headId = db.resolve(BRANCH + "^{commit}");
if (headId == null) {
// create the branch
createTicketsBranch(db);
- headId = db.resolve(BRANCH + "^{commit}");
}
- ObjectInserter odi = db.newObjectInserter();
- try {
- // Create the in-memory index of the new/updated ticket
- ObjectId indexTreeId = index.writeTree(odi);
-
- // Create a commit object
- PersonIdent ident = new PersonIdent(author, "gitblit@localhost");
- CommitBuilder commit = new CommitBuilder();
- commit.setAuthor(ident);
- commit.setCommitter(ident);
- commit.setEncoding(Constants.ENCODING);
- commit.setMessage(message);
- commit.setParentId(headId);
- commit.setTreeId(indexTreeId);
-
- // Insert the commit into the repository
- ObjectId commitId = odi.insert(commit);
- odi.flush();
-
- RevWalk revWalk = new RevWalk(db);
- try {
- RevCommit revCommit = revWalk.parseCommit(commitId);
- RefUpdate ru = db.updateRef(BRANCH);
- ru.setNewObjectId(commitId);
- ru.setExpectedOldObjectId(headId);
- ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
- Result rc = ru.forceUpdate();
- switch (rc) {
- case NEW:
- case FORCED:
- case FAST_FORWARD:
- success = true;
- break;
- case REJECTED:
- case LOCK_FAILURE:
- throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD,
- ru.getRef(), rc);
- default:
- throw new JGitInternalException(MessageFormat.format(
- JGitText.get().updatingRefFailed, BRANCH, commitId.toString(),
- rc));
- }
- } finally {
- revWalk.release();
- }
- } finally {
- odi.release();
- }
+
+ success = JGitUtils.commitIndex(db, BRANCH, index, headId, forceCommit, author, "gitblit@localhost", message);
+
return success;
}
diff --git a/src/main/java/com/gitblit/tickets/FileTicketService.java b/src/main/java/com/gitblit/tickets/FileTicketService.java
index b3d8838..1e82f0d 100644
--- a/src/main/java/com/gitblit/tickets/FileTicketService.java
+++ b/src/main/java/com/gitblit/tickets/FileTicketService.java
@@ -42,6 +42,8 @@
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.FileUtils;
import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* Implementation of a ticket service based on a directory within the repository.
@@ -51,6 +53,7 @@
* @author James Moger
*
*/
+@Singleton
public class FileTicketService extends ITicketService {
private static final String JOURNAL = "journal.json";
@@ -59,6 +62,7 @@
private final Map<String, AtomicLong> lastAssignedId;
+ @Inject
public FileTicketService(
IRuntimeManager runtimeManager,
IPluginManager pluginManager,
@@ -77,6 +81,7 @@
@Override
public FileTicketService start() {
+ log.info("{} started", getClass().getSimpleName());
return this;
}
@@ -488,6 +493,10 @@
@Override
protected boolean deleteAllImpl(RepositoryModel repository) {
Repository db = repositoryManager.getRepository(repository.name);
+ if (db == null) {
+ // the tickets no longer exist because the db no longer exists
+ return true;
+ }
try {
File dir = new File(db.getDirectory(), TICKETS_PATH);
return FileUtils.delete(dir);
diff --git a/src/main/java/com/gitblit/tickets/ITicketService.java b/src/main/java/com/gitblit/tickets/ITicketService.java
index 4cf099f..e831003 100644
--- a/src/main/java/com/gitblit/tickets/ITicketService.java
+++ b/src/main/java/com/gitblit/tickets/ITicketService.java
@@ -36,6 +36,7 @@
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.extensions.TicketHook;
+import com.gitblit.manager.IManager;
import com.gitblit.manager.INotificationManager;
import com.gitblit.manager.IPluginManager;
import com.gitblit.manager.IRepositoryManager;
@@ -47,6 +48,7 @@
import com.gitblit.models.TicketModel.Change;
import com.gitblit.models.TicketModel.Field;
import com.gitblit.models.TicketModel.Patchset;
+import com.gitblit.models.TicketModel.PatchsetType;
import com.gitblit.models.TicketModel.Status;
import com.gitblit.tickets.TicketIndexer.Lucene;
import com.gitblit.utils.DeepCopier;
@@ -63,7 +65,7 @@
* @author James Moger
*
*/
-public abstract class ITicketService {
+public abstract class ITicketService implements IManager {
public static final String SETTING_UPDATE_DIFFSTATS = "migration.updateDiffstats";
@@ -176,12 +178,14 @@
* Start the service.
* @since 1.4.0
*/
+ @Override
public abstract ITicketService start();
/**
* Stop the service.
* @since 1.4.0
*/
+ @Override
public final ITicketService stop() {
indexer.close();
ticketsCache.invalidateAll();
@@ -1210,6 +1214,30 @@
TicketModel revisedTicket = updateTicket(repository, ticket.number, deletion);
return revisedTicket;
}
+
+ /**
+ * Deletes a patchset from a ticket.
+ *
+ * @param ticket
+ * @param patchset
+ * the patchset to delete (should be the highest revision)
+ * @param userName
+ * the user deleting the commit
+ * @return the revised ticket if the deletion was successful
+ * @since 1.8.0
+ */
+ public final TicketModel deletePatchset(TicketModel ticket, Patchset patchset, String userName) {
+ Change deletion = new Change(userName);
+ deletion.patchset = new Patchset();
+ deletion.patchset.number = patchset.number;
+ deletion.patchset.rev = patchset.rev;
+ deletion.patchset.type = PatchsetType.Delete;
+
+ RepositoryModel repository = repositoryManager.getRepositoryModel(ticket.repository);
+ TicketModel revisedTicket = updateTicket(repository, ticket.number, deletion);
+
+ return revisedTicket;
+ }
/**
* Commit a ticket change to the repository.
diff --git a/src/main/java/com/gitblit/tickets/NullTicketService.java b/src/main/java/com/gitblit/tickets/NullTicketService.java
index d410cdd..3947b94 100644
--- a/src/main/java/com/gitblit/tickets/NullTicketService.java
+++ b/src/main/java/com/gitblit/tickets/NullTicketService.java
@@ -28,6 +28,8 @@
import com.gitblit.models.TicketModel;
import com.gitblit.models.TicketModel.Attachment;
import com.gitblit.models.TicketModel.Change;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* Implementation of a ticket service that rejects everything.
@@ -35,8 +37,10 @@
* @author James Moger
*
*/
+@Singleton
public class NullTicketService extends ITicketService {
+ @Inject
public NullTicketService(
IRuntimeManager runtimeManager,
IPluginManager pluginManager,
@@ -58,6 +62,7 @@
@Override
public NullTicketService start() {
+ log.info("{} started", getClass().getSimpleName());
return this;
}
diff --git a/src/main/java/com/gitblit/tickets/QueryResult.java b/src/main/java/com/gitblit/tickets/QueryResult.java
index 7a2b1ab..f8d6d12 100644
--- a/src/main/java/com/gitblit/tickets/QueryResult.java
+++ b/src/main/java/com/gitblit/tickets/QueryResult.java
@@ -24,6 +24,8 @@
import com.gitblit.models.TicketModel.Patchset;
import com.gitblit.models.TicketModel.Status;
import com.gitblit.models.TicketModel.Type;
+import com.gitblit.models.TicketModel.Priority;
+import com.gitblit.models.TicketModel.Severity;
import com.gitblit.utils.StringUtils;
/**
@@ -62,6 +64,8 @@
public int commentsCount;
public int votesCount;
public int approvalsCount;
+ public Priority priority;
+ public Severity severity;
public int docId;
public int totalResults;
diff --git a/src/main/java/com/gitblit/tickets/RedisTicketService.java b/src/main/java/com/gitblit/tickets/RedisTicketService.java
index d773b0b..0f9ad17 100644
--- a/src/main/java/com/gitblit/tickets/RedisTicketService.java
+++ b/src/main/java/com/gitblit/tickets/RedisTicketService.java
@@ -43,6 +43,8 @@
import com.gitblit.models.TicketModel.Change;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* Implementation of a ticket service based on a Redis key-value store. All
@@ -53,6 +55,7 @@
* @author James Moger
*
*/
+@Singleton
public class RedisTicketService extends ITicketService {
private final JedisPool pool;
@@ -61,6 +64,7 @@
journal, ticket, counter
}
+ @Inject
public RedisTicketService(
IRuntimeManager runtimeManager,
IPluginManager pluginManager,
@@ -80,6 +84,10 @@
@Override
public RedisTicketService start() {
+ log.info("{} started", getClass().getSimpleName());
+ if (!isReady()) {
+ log.warn("{} is not ready!", getClass().getSimpleName());
+ }
return this;
}
diff --git a/src/main/java/com/gitblit/tickets/TicketIndexer.java b/src/main/java/com/gitblit/tickets/TicketIndexer.java
index 11ea3a7..e2d53af 100644
--- a/src/main/java/com/gitblit/tickets/TicketIndexer.java
+++ b/src/main/java/com/gitblit/tickets/TicketIndexer.java
@@ -103,7 +103,10 @@
mergesha(Type.STRING),
mergeto(Type.STRING),
patchsets(Type.INT),
- votes(Type.INT);
+ votes(Type.INT),
+ //NOTE: Indexing on the underlying value to allow flexibility on naming
+ priority(Type.INT),
+ severity(Type.INT);
final Type fieldType;
@@ -519,6 +522,8 @@
toDocField(doc, Lucene.watchedby, StringUtils.flattenStrings(ticket.getWatchers(), ";").toLowerCase());
toDocField(doc, Lucene.mentions, StringUtils.flattenStrings(ticket.getMentions(), ";").toLowerCase());
toDocField(doc, Lucene.votes, ticket.getVoters().size());
+ toDocField(doc, Lucene.priority, ticket.priority.getValue());
+ toDocField(doc, Lucene.severity, ticket.severity.getValue());
List<String> attachments = new ArrayList<String>();
for (Attachment attachment : ticket.getAttachments()) {
@@ -600,6 +605,8 @@
result.participants = unpackStrings(doc, Lucene.participants);
result.watchedby = unpackStrings(doc, Lucene.watchedby);
result.mentions = unpackStrings(doc, Lucene.mentions);
+ result.priority = TicketModel.Priority.fromObject(unpackInt(doc, Lucene.priority), TicketModel.Priority.defaultPriority);
+ result.severity = TicketModel.Severity.fromObject(unpackInt(doc, Lucene.severity), TicketModel.Severity.defaultSeverity);
if (!StringUtils.isEmpty(doc.get(Lucene.patchset.name()))) {
// unpack most recent patchset
diff --git a/src/main/java/com/gitblit/tickets/TicketNotifier.java b/src/main/java/com/gitblit/tickets/TicketNotifier.java
index d6217b3..5979cf2 100644
--- a/src/main/java/com/gitblit/tickets/TicketNotifier.java
+++ b/src/main/java/com/gitblit/tickets/TicketNotifier.java
@@ -135,6 +135,7 @@
StringBuilder html = new StringBuilder();
html.append("<head>");
html.append(readStyle());
+ html.append(readViewTicketAction(ticket));
html.append("</head>");
html.append("<body>");
html.append(MarkdownUtils.transformGFM(settings, markdown, ticket.repository));
@@ -613,6 +614,12 @@
return sb.toString();
}
+ protected String readViewTicketAction(TicketModel ticket) {
+ String action = readResource("viewTicket.html");
+ action = action.replace("${url}", ticketService.getTicketUrl(ticket));
+ return action;
+ }
+
protected String readResource(String resource) {
StringBuilder sb = new StringBuilder();
InputStream is = null;
diff --git a/src/main/java/com/gitblit/tickets/viewTicket.html b/src/main/java/com/gitblit/tickets/viewTicket.html
new file mode 100644
index 0000000..54e091c
--- /dev/null
+++ b/src/main/java/com/gitblit/tickets/viewTicket.html
@@ -0,0 +1,12 @@
+<script type="application/ld+json">
+{
+ "@context": "http://schema.org",
+ "@type": "EmailMessage",
+ "description": "View this Ticket in Gitblit",
+ "action": {
+ "@type": "ViewAction",
+ "url": "${url}",
+ "name": "View Ticket"
+ }
+}
+</script>
diff --git a/src/main/java/com/gitblit/transport/ssh/DisabledFilesystemFactory.java b/src/main/java/com/gitblit/transport/ssh/DisabledFilesystemFactory.java
index de661a4..9bab3b8 100644
--- a/src/main/java/com/gitblit/transport/ssh/DisabledFilesystemFactory.java
+++ b/src/main/java/com/gitblit/transport/ssh/DisabledFilesystemFactory.java
@@ -16,31 +16,22 @@
package com.gitblit.transport.ssh;
import java.io.IOException;
+import java.nio.file.FileSystem;
-import org.apache.sshd.common.Session;
import org.apache.sshd.common.file.FileSystemFactory;
-import org.apache.sshd.common.file.FileSystemView;
-import org.apache.sshd.common.file.SshFile;
+import org.apache.sshd.common.session.Session;
public class DisabledFilesystemFactory implements FileSystemFactory {
- @Override
- public FileSystemView createFileSystemView(Session session) throws IOException {
- return new FileSystemView() {
- @Override
- public SshFile getFile(SshFile baseDir, String file) {
- return null;
- }
-
- @Override
- public SshFile getFile(String file) {
- return null;
- }
-
- @Override
- public FileSystemView getNormalizedView() {
- return null;
- }
- };
- }
+ /**
+ * Create user specific file system.
+ *
+ * @param session The session created for the user
+ * @return The current {@link FileSystem} for the provided session
+ * @throws java.io.IOException when the filesystem can not be created
+ */
+ @Override
+ public FileSystem createFileSystem(Session session) throws IOException {
+ return null;
+ }
}
diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java b/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java
index a063dc7..1a2cd68 100644
--- a/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java
+++ b/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java
@@ -29,6 +29,7 @@
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.io.Files;
+import com.google.inject.Inject;
/**
* Manages public keys on the filesystem.
@@ -42,6 +43,7 @@
protected final Map<File, Long> lastModifieds;
+ @Inject
public FileKeyManager(IRuntimeManager runtimeManager) {
this.runtimeManager = runtimeManager;
this.lastModifieds = new ConcurrentHashMap<File, Long>();
diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java
new file mode 100644
index 0000000..db0741e
--- /dev/null
+++ b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package com.gitblit.transport.ssh;
+
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.security.KeyPair;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider;
+import org.apache.sshd.common.util.SecurityUtils;
+import org.bouncycastle.openssl.PEMDecryptorProvider;
+import org.bouncycastle.openssl.PEMEncryptedKeyPair;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.PasswordFinder;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
+
+/**
+ * This host key provider loads private keys from the specified files.
+ *
+ * Note that this class has a direct dependency on BouncyCastle and won't work
+ * unless it has been correctly registered as a security provider.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class FileKeyPairProvider extends AbstractKeyPairProvider {
+
+ private String[] files;
+ private PasswordFinder passwordFinder;
+
+ public FileKeyPairProvider() {
+ }
+
+ public FileKeyPairProvider(String[] files) {
+ this.files = files;
+ }
+
+ public FileKeyPairProvider(String[] files, PasswordFinder passwordFinder) {
+ this.files = files;
+ this.passwordFinder = passwordFinder;
+ }
+
+ public String[] getFiles() {
+ return files;
+ }
+
+ public void setFiles(String[] files) {
+ this.files = files;
+ }
+
+ public PasswordFinder getPasswordFinder() {
+ return passwordFinder;
+ }
+
+ public void setPasswordFinder(PasswordFinder passwordFinder) {
+ this.passwordFinder = passwordFinder;
+ }
+
+ public Iterable<KeyPair> loadKeys() {
+ if (!SecurityUtils.isBouncyCastleRegistered()) {
+ throw new IllegalStateException("BouncyCastle must be registered as a JCE provider");
+ }
+ return new Iterable<KeyPair>() {
+ @Override
+ public Iterator<KeyPair> iterator() {
+ return new Iterator<KeyPair>() {
+ private final Iterator<String> iterator = Arrays.asList(files).iterator();
+ private KeyPair nextKeyPair;
+ private boolean nextKeyPairSet = false;
+ @Override
+ public boolean hasNext() {
+ return nextKeyPairSet || setNextObject();
+ }
+ @Override
+ public KeyPair next() {
+ if (!nextKeyPairSet) {
+ if (!setNextObject()) {
+ throw new NoSuchElementException();
+ }
+ }
+ nextKeyPairSet = false;
+ return nextKeyPair;
+ }
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ private boolean setNextObject() {
+ while (iterator.hasNext()) {
+ String file = iterator.next();
+ nextKeyPair = doLoadKey(file);
+ if (nextKeyPair != null) {
+ nextKeyPairSet = true;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ };
+ }
+ };
+ }
+
+ protected KeyPair doLoadKey(String file) {
+ try {
+ PEMParser r = new PEMParser(new InputStreamReader(new FileInputStream(file)));
+ try {
+ Object o = r.readObject();
+
+ JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter();
+ pemConverter.setProvider("BC");
+ if (passwordFinder != null && o instanceof PEMEncryptedKeyPair) {
+ JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder();
+ PEMDecryptorProvider pemDecryptor = decryptorBuilder.build(passwordFinder.getPassword());
+ o = pemConverter.getKeyPair(((PEMEncryptedKeyPair) o).decryptKeyPair(pemDecryptor));
+ }
+
+ if (o instanceof PEMKeyPair) {
+ o = pemConverter.getKeyPair((PEMKeyPair)o);
+ return (KeyPair) o;
+ } else if (o instanceof KeyPair) {
+ return (KeyPair) o;
+ }
+ } finally {
+ r.close();
+ }
+ } catch (Exception e) {
+ log.warn("Unable to read key " + file, e);
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java b/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java
index 357b34a..bf78378 100644
--- a/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java
+++ b/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java
@@ -20,6 +20,8 @@
import java.util.List;
import java.util.Map;
+import com.google.inject.Inject;
+
/**
* Memory public key manager.
*
@@ -30,6 +32,7 @@
final Map<String, List<SshKey>> keys;
+ @Inject
public MemoryKeyManager() {
keys = new HashMap<String, List<SshKey>>();
}
diff --git a/src/main/java/com/gitblit/transport/ssh/NonForwardingFilter.java b/src/main/java/com/gitblit/transport/ssh/NonForwardingFilter.java
index 4bd75d5..29f7750 100644
--- a/src/main/java/com/gitblit/transport/ssh/NonForwardingFilter.java
+++ b/src/main/java/com/gitblit/transport/ssh/NonForwardingFilter.java
@@ -15,13 +15,14 @@
*/
package com.gitblit.transport.ssh;
-import org.apache.sshd.common.ForwardingFilter;
-import org.apache.sshd.common.Session;
import org.apache.sshd.common.SshdSocketAddress;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.server.forward.ForwardingFilter;
public class NonForwardingFilter implements ForwardingFilter {
+
@Override
- public boolean canConnect(SshdSocketAddress address, Session session) {
+ public boolean canConnect(Type type, SshdSocketAddress address, Session session) {
return false;
}
diff --git a/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java b/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java
index 0761d84..fcd3e19 100644
--- a/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java
+++ b/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java
@@ -17,6 +17,8 @@
import java.util.List;
+import com.google.inject.Inject;
+
/**
* Rejects all public key management requests.
*
@@ -25,6 +27,7 @@
*/
public class NullKeyManager extends IPublicKeyManager {
+ @Inject
public NullKeyManager() {
}
diff --git a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java
index 6bcc039..5a94c9a 100644
--- a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java
+++ b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java
@@ -25,12 +25,12 @@
import java.text.MessageFormat;
import java.util.concurrent.atomic.AtomicBoolean;
-import org.apache.sshd.SshServer;
import org.apache.sshd.common.io.IoServiceFactoryFactory;
import org.apache.sshd.common.io.mina.MinaServiceFactoryFactory;
import org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory;
-import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.CachingPublicKeyAuthenticator;
import org.bouncycastle.openssl.PEMWriter;
import org.eclipse.jgit.internal.JGitText;
import org.slf4j.Logger;
@@ -98,8 +98,8 @@
hostKeyPairProvider.setFiles(new String [] { rsaKeyStore.getPath(), dsaKeyStore.getPath(), dsaKeyStore.getPath() });
// Client public key authenticator
- CachingPublicKeyAuthenticator keyAuthenticator =
- new CachingPublicKeyAuthenticator(gitblit.getPublicKeyManager(), gitblit);
+ SshKeyAuthenticator keyAuthenticator =
+ new SshKeyAuthenticator(gitblit.getPublicKeyManager(), gitblit);
// Configure the preferred SSHD backend
String sshBackendStr = settings.getString(Keys.git.sshBackend,
@@ -125,8 +125,11 @@
sshd.setPort(addr.getPort());
sshd.setHost(addr.getHostName());
sshd.setKeyPairProvider(hostKeyPairProvider);
- sshd.setPublickeyAuthenticator(keyAuthenticator);
+ sshd.setPublickeyAuthenticator(new CachingPublicKeyAuthenticator(keyAuthenticator));
sshd.setPasswordAuthenticator(new UsernamePasswordAuthenticator(gitblit));
+ if (settings.getBoolean(Keys.git.sshWithKrb5, false)) {
+ sshd.setGSSAuthenticator(new SshKrbAuthenticator(settings, gitblit));
+ }
sshd.setSessionFactory(new SshServerSessionFactory());
sshd.setFileSystemFactory(new DisabledFilesystemFactory());
sshd.setTcpipForwardingFilter(new NonForwardingFilter());
@@ -143,14 +146,22 @@
}
public String formatUrl(String gituser, String servername, String repository) {
- if (sshd.getPort() == DEFAULT_PORT) {
+ IStoredSettings settings = gitblit.getSettings();
+
+ int port = sshd.getPort();
+ int displayPort = settings.getInteger(Keys.git.sshAdvertisedPort, port);
+ String displayServername = settings.getString(Keys.git.sshAdvertisedHost, "");
+ if(displayServername.isEmpty()) {
+ displayServername = servername;
+ }
+ if (displayPort == DEFAULT_PORT) {
// standard port
- return MessageFormat.format("ssh://{0}@{1}/{2}", gituser, servername,
+ return MessageFormat.format("ssh://{0}@{1}/{2}", gituser, displayServername,
repository);
} else {
// non-standard port
return MessageFormat.format("ssh://{0}@{1}:{2,number,0}/{3}",
- gituser, servername, sshd.getPort(), repository);
+ gituser, displayServername, displayPort, repository);
}
}
@@ -192,7 +203,7 @@
try {
((SshCommandFactory) sshd.getCommandFactory()).stop();
sshd.stop();
- } catch (InterruptedException e) {
+ } catch (IOException e) {
log.error("SSH Daemon stop interrupted", e);
}
}
diff --git a/src/main/java/com/gitblit/transport/ssh/SshDaemonClient.java b/src/main/java/com/gitblit/transport/ssh/SshDaemonClient.java
index a5d4c3d..af25251 100644
--- a/src/main/java/com/gitblit/transport/ssh/SshDaemonClient.java
+++ b/src/main/java/com/gitblit/transport/ssh/SshDaemonClient.java
@@ -17,7 +17,7 @@
import java.net.SocketAddress;
-import org.apache.sshd.common.Session.AttributeKey;
+import org.apache.sshd.common.session.Session.AttributeKey;
import com.gitblit.models.UserModel;
diff --git a/src/main/java/com/gitblit/transport/ssh/SshKey.java b/src/main/java/com/gitblit/transport/ssh/SshKey.java
index 9c99d1a..9fd1005 100644
--- a/src/main/java/com/gitblit/transport/ssh/SshKey.java
+++ b/src/main/java/com/gitblit/transport/ssh/SshKey.java
@@ -22,7 +22,8 @@
import org.apache.commons.codec.binary.Base64;
import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.util.Buffer;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
import org.eclipse.jgit.lib.Constants;
import com.gitblit.Constants.AccessPermission;
@@ -72,7 +73,7 @@
}
final byte[] bin = Base64.decodeBase64(Constants.encodeASCII(parts[1]));
try {
- publicKey = new Buffer(bin).getRawPublicKey();
+ publicKey = new ByteArrayBuffer(bin).getRawPublicKey();
} catch (SshException e) {
throw new RuntimeException(e);
}
@@ -145,7 +146,7 @@
public String getRawData() {
if (rawData == null && publicKey != null) {
// build the raw data manually from the public key
- Buffer buf = new Buffer();
+ Buffer buf = new ByteArrayBuffer();
// 1: identify the algorithm
buf.putRawPublicKey(publicKey);
diff --git a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java b/src/main/java/com/gitblit/transport/ssh/SshKeyAuthenticator.java
similarity index 62%
rename from src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java
rename to src/main/java/com/gitblit/transport/ssh/SshKeyAuthenticator.java
index e804a0d..dc9d8a4 100644
--- a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java
+++ b/src/main/java/com/gitblit/transport/ssh/SshKeyAuthenticator.java
@@ -16,15 +16,10 @@
package com.gitblit.transport.ssh;
import java.security.PublicKey;
-import java.util.HashMap;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import org.apache.sshd.common.Session;
-import org.apache.sshd.common.SessionListener;
-import org.apache.sshd.server.PublickeyAuthenticator;
+import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
import org.apache.sshd.server.session.ServerSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,7 +32,7 @@
* Authenticates an SSH session against a public key.
*
*/
-public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, SessionListener {
+public class SshKeyAuthenticator implements PublickeyAuthenticator {
protected final Logger log = LoggerFactory.getLogger(getClass());
@@ -45,30 +40,13 @@
protected final IAuthenticationManager authManager;
- private final Map<ServerSession, Map<PublicKey, Boolean>> cache = new ConcurrentHashMap<ServerSession, Map<PublicKey, Boolean>>();
-
- public CachingPublicKeyAuthenticator(IPublicKeyManager keyManager, IAuthenticationManager authManager) {
+ public SshKeyAuthenticator(IPublicKeyManager keyManager, IAuthenticationManager authManager) {
this.keyManager = keyManager;
this.authManager = authManager;
}
@Override
- public boolean authenticate(String username, PublicKey key, ServerSession session) {
- Map<PublicKey, Boolean> map = cache.get(session);
- if (map == null) {
- map = new HashMap<PublicKey, Boolean>();
- cache.put(session, map);
- session.addListener(this);
- }
- if (map.containsKey(key)) {
- return map.get(key);
- }
- boolean result = doAuthenticate(username, key, session);
- map.put(key, result);
- return result;
- }
-
- private boolean doAuthenticate(String username, PublicKey suppliedKey, ServerSession session) {
+ public boolean authenticate(String username, PublicKey suppliedKey, ServerSession session) {
SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY);
Preconditions.checkState(client.getUser() == null);
username = username.toLowerCase(Locale.US);
@@ -96,17 +74,4 @@
log.warn("could not authenticate {} for SSH using the supplied public key", username);
return false;
}
-
- @Override
- public void sessionCreated(Session session) {
- }
-
- @Override
- public void sessionEvent(Session sesssion, Event event) {
- }
-
- @Override
- public void sessionClosed(Session session) {
- cache.remove(session);
- }
}
diff --git a/src/main/java/com/gitblit/transport/ssh/SshKrbAuthenticator.java b/src/main/java/com/gitblit/transport/ssh/SshKrbAuthenticator.java
new file mode 100644
index 0000000..b6d233c
--- /dev/null
+++ b/src/main/java/com/gitblit/transport/ssh/SshKrbAuthenticator.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2015 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.transport.ssh;
+
+import java.util.Locale;
+
+import org.apache.sshd.server.auth.gss.GSSAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.models.UserModel;
+
+public class SshKrbAuthenticator extends GSSAuthenticator {
+
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+ protected final IAuthenticationManager authManager;
+ protected final boolean stripDomain;
+
+
+ public SshKrbAuthenticator(IStoredSettings settings, IAuthenticationManager authManager) {
+ this.authManager = authManager;
+
+ String keytabString = settings.getString(Keys.git.sshKrb5Keytab, "");
+ if(! keytabString.isEmpty()) {
+ setKeytabFile(keytabString);
+ }
+
+ String servicePrincipalName = settings.getString(Keys.git.sshKrb5ServicePrincipalName, "");
+ if(! servicePrincipalName.isEmpty()) {
+ setServicePrincipalName(servicePrincipalName);
+ }
+
+ this.stripDomain = settings.getBoolean(Keys.git.sshKrb5StripDomain, false);
+ }
+
+ @Override
+ public boolean validateIdentity(ServerSession session, String identity) {
+ log.info("identify with kerberos {}", identity);
+ SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY);
+ if (client.getUser() != null) {
+ log.info("{} has already authenticated!", identity);
+ return true;
+ }
+ String username = identity.toLowerCase(Locale.US);
+ if (stripDomain) {
+ int p = username.indexOf('@');
+ if (p > 0) {
+ username = username.substring(0, p);
+ }
+ }
+ UserModel user = authManager.authenticate(username);
+ if (user != null) {
+ client.setUser(user);
+ return true;
+ }
+ log.warn("could not authenticate {} for SSH", username);
+ return false;
+ }
+}
diff --git a/src/main/java/com/gitblit/transport/ssh/SshServerSession.java b/src/main/java/com/gitblit/transport/ssh/SshServerSession.java
index d12a6be..02504ec 100644
--- a/src/main/java/com/gitblit/transport/ssh/SshServerSession.java
+++ b/src/main/java/com/gitblit/transport/ssh/SshServerSession.java
@@ -19,10 +19,10 @@
import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.io.IoSession;
import org.apache.sshd.server.ServerFactoryManager;
-import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.session.ServerSessionImpl;
// Expose addition of close session listeners
-class SshServerSession extends ServerSession {
+class SshServerSession extends ServerSessionImpl {
SshServerSession(ServerFactoryManager server, IoSession ioSession) throws Exception {
super(server, ioSession);
diff --git a/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java b/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java
index 0c018f0..bc67cec 100644
--- a/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java
+++ b/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java
@@ -67,6 +67,6 @@
@Override
protected AbstractSession doCreateSession(IoSession ioSession) throws Exception {
- return new SshServerSession(server, ioSession);
+ return new SshServerSession(getServer(), ioSession);
}
}
diff --git a/src/main/java/com/gitblit/transport/ssh/UsernamePasswordAuthenticator.java b/src/main/java/com/gitblit/transport/ssh/UsernamePasswordAuthenticator.java
index c4e69dc..e9e2d7e 100644
--- a/src/main/java/com/gitblit/transport/ssh/UsernamePasswordAuthenticator.java
+++ b/src/main/java/com/gitblit/transport/ssh/UsernamePasswordAuthenticator.java
@@ -17,7 +17,7 @@
import java.util.Locale;
-import org.apache.sshd.server.PasswordAuthenticator;
+import org.apache.sshd.server.auth.password.PasswordAuthenticator;
import org.apache.sshd.server.session.ServerSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,13 +51,13 @@
}
username = username.toLowerCase(Locale.US);
- UserModel user = authManager.authenticate(username, password.toCharArray());
+ UserModel user = authManager.authenticate(username, password.toCharArray(), null);
if (user != null) {
client.setUser(user);
return true;
}
- log.warn("could not authenticate {} for SSH using the supplied password", username);
+ log.warn("could not authenticate {} ({}) for SSH using the supplied password", username, client.getRemoteAddress());
return false;
}
}
diff --git a/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java b/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java
index 852756a..ec6f729 100644
--- a/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java
+++ b/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java
@@ -200,13 +200,18 @@
}
private String formatUrl(String hostname, int port, String username) {
- if (port == 22) {
+ int displayPort = settings.getInteger(Keys.git.sshAdvertisedPort, port);
+ String displayHostname = settings.getString(Keys.git.sshAdvertisedHost, "");
+ if(displayHostname.isEmpty()) {
+ displayHostname = hostname;
+ }
+ if (displayPort == 22) {
// standard port
- return MessageFormat.format("{0}@{1}/REPOSITORY.git", username, hostname);
+ return MessageFormat.format("{0}@{1}/REPOSITORY.git", username, displayHostname);
} else {
// non-standard port
return MessageFormat.format("ssh://{0}@{1}:{2,number,0}/REPOSITORY.git",
- username, hostname, port);
+ username, displayHostname, displayPort);
}
}
}
diff --git a/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java
index f18b99b..02764db 100644
--- a/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java
+++ b/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java
@@ -27,6 +27,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.manager.IGitblit;
import com.gitblit.utils.StringUtils;
@@ -73,15 +74,20 @@
protected String getRepositoryUrl(String repository) {
String username = getContext().getClient().getUsername();
- String hostname = getHostname();
- int port = getContext().getGitblit().getSettings().getInteger(Keys.git.sshPort, 0);
- if (port == 22) {
+ IStoredSettings settings = getContext().getGitblit().getSettings();
+ String displayHostname = settings.getString(Keys.git.sshAdvertisedHost, "");
+ if(displayHostname.isEmpty()) {
+ displayHostname = getHostname();
+ }
+ int port = settings.getInteger(Keys.git.sshPort, 0);
+ int displayPort = settings.getInteger(Keys.git.sshAdvertisedPort, port);
+ if (displayPort == 22) {
// standard port
- return MessageFormat.format("{0}@{1}/{2}.git", username, hostname, repository);
+ return MessageFormat.format("{0}@{1}/{2}.git", username, displayHostname, repository);
} else {
// non-standard port
return MessageFormat.format("ssh://{0}@{1}:{2,number,0}/{3}",
- username, hostname, port, repository);
+ username, displayHostname, displayPort, repository);
}
}
diff --git a/src/main/java/com/gitblit/utils/ActivityUtils.java b/src/main/java/com/gitblit/utils/ActivityUtils.java
index ba5599a..ab1dad9 100644
--- a/src/main/java/com/gitblit/utils/ActivityUtils.java
+++ b/src/main/java/com/gitblit/utils/ActivityUtils.java
@@ -169,7 +169,7 @@
if (width <= 0) {
width = 50;
}
- String emailHash = StringUtils.getMD5(email);
+ String emailHash = StringUtils.getMD5(email.toLowerCase());
String url = MessageFormat.format(
"https://www.gravatar.com/avatar/{0}?s={1,number,0}&d=identicon", emailHash, width);
return url;
@@ -188,7 +188,7 @@
if (width <= 0) {
width = 50;
}
- String emailHash = StringUtils.getMD5(email);
+ String emailHash = StringUtils.getMD5(email.toLowerCase());
String url = MessageFormat.format(
"https://www.gravatar.com/avatar/{0}?s={1,number,0}&d=mm", emailHash, width);
return url;
diff --git a/src/main/java/com/gitblit/utils/CompressionUtils.java b/src/main/java/com/gitblit/utils/CompressionUtils.java
index d4bfbb3..1b8e6fc 100644
--- a/src/main/java/com/gitblit/utils/CompressionUtils.java
+++ b/src/main/java/com/gitblit/utils/CompressionUtils.java
@@ -16,6 +16,9 @@
package com.gitblit.utils;
import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.MessageFormat;
@@ -28,9 +31,11 @@
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
+import org.apache.commons.io.IOUtils;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
@@ -41,6 +46,11 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.gitblit.GitBlit;
+import com.gitblit.manager.IFilestoreManager;
+import com.gitblit.models.FilestoreModel;
+import com.gitblit.models.FilestoreModel.Status;
+
/**
* Collection of static methods for retrieving information from a repository.
*
@@ -87,7 +97,7 @@
* @return true if repository was successfully zipped to supplied output
* stream
*/
- public static boolean zip(Repository repository, String basePath, String objectId,
+ public static boolean zip(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId,
OutputStream os) {
RevCommit commit = JGitUtils.getCommit(repository, objectId);
if (commit == null) {
@@ -115,16 +125,40 @@
continue;
}
tw.getObjectId(id, 0);
-
+
+ ObjectLoader loader = repository.open(id);
+
ZipArchiveEntry entry = new ZipArchiveEntry(tw.getPathString());
- entry.setSize(reader.getObjectSize(id, Constants.OBJ_BLOB));
+
+ FilestoreModel filestoreItem = null;
+
+ if (JGitUtils.isPossibleFilestoreItem(loader.getSize())) {
+ filestoreItem = JGitUtils.getFilestoreItem(tw.getObjectReader().open(id));
+ }
+
+ final long size = (filestoreItem == null) ? loader.getSize() : filestoreItem.getSize();
+
+ entry.setSize(size);
entry.setComment(commit.getName());
entry.setUnixMode(mode.getBits());
entry.setTime(modified);
zos.putArchiveEntry(entry);
+
+ if (filestoreItem == null) {
+ //Copy repository stored file
+ loader.copyTo(zos);
+ } else {
+ //Copy filestore file
+ try (FileInputStream streamIn = new FileInputStream(filestoreManager.getStoragePath(filestoreItem.oid))) {
+ IOUtils.copyLarge(streamIn, zos);
+ } catch (Throwable e) {
+ LOGGER.error(MessageFormat.format("Failed to archive filestore item {0}", filestoreItem.oid), e);
- ObjectLoader ldr = repository.open(id);
- ldr.copyTo(zos);
+ //Handle as per other errors
+ throw e;
+ }
+ }
+
zos.closeArchiveEntry();
}
zos.finish();
@@ -132,7 +166,7 @@
} catch (IOException e) {
error(e, repository, "{0} failed to zip files from commit {1}", commit.getName());
} finally {
- tw.release();
+ tw.close();
rw.dispose();
}
return success;
@@ -151,9 +185,9 @@
* @return true if repository was successfully zipped to supplied output
* stream
*/
- public static boolean tar(Repository repository, String basePath, String objectId,
+ public static boolean tar(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId,
OutputStream os) {
- return tar(null, repository, basePath, objectId, os);
+ return tar(null, repository, filestoreManager, basePath, objectId, os);
}
/**
@@ -169,9 +203,9 @@
* @return true if repository was successfully zipped to supplied output
* stream
*/
- public static boolean gz(Repository repository, String basePath, String objectId,
+ public static boolean gz(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId,
OutputStream os) {
- return tar(CompressorStreamFactory.GZIP, repository, basePath, objectId, os);
+ return tar(CompressorStreamFactory.GZIP, repository, filestoreManager, basePath, objectId, os);
}
/**
@@ -187,9 +221,9 @@
* @return true if repository was successfully zipped to supplied output
* stream
*/
- public static boolean xz(Repository repository, String basePath, String objectId,
+ public static boolean xz(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId,
OutputStream os) {
- return tar(CompressorStreamFactory.XZ, repository, basePath, objectId, os);
+ return tar(CompressorStreamFactory.XZ, repository, filestoreManager, basePath, objectId, os);
}
/**
@@ -205,10 +239,10 @@
* @return true if repository was successfully zipped to supplied output
* stream
*/
- public static boolean bzip2(Repository repository, String basePath, String objectId,
+ public static boolean bzip2(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId,
OutputStream os) {
- return tar(CompressorStreamFactory.BZIP2, repository, basePath, objectId, os);
+ return tar(CompressorStreamFactory.BZIP2, repository, filestoreManager, basePath, objectId, os);
}
/**
@@ -227,7 +261,7 @@
* @return true if repository was successfully zipped to supplied output
* stream
*/
- private static boolean tar(String algorithm, Repository repository, String basePath, String objectId,
+ private static boolean tar(String algorithm, Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId,
OutputStream os) {
RevCommit commit = JGitUtils.getCommit(repository, objectId);
if (commit == null) {
@@ -263,6 +297,7 @@
if (mode == FileMode.GITLINK || mode == FileMode.TREE) {
continue;
}
+
tw.getObjectId(id, 0);
ObjectLoader loader = repository.open(id);
@@ -278,9 +313,34 @@
TarArchiveEntry entry = new TarArchiveEntry(tw.getPathString());
entry.setMode(mode.getBits());
entry.setModTime(modified);
- entry.setSize(loader.getSize());
+
+ FilestoreModel filestoreItem = null;
+
+ if (JGitUtils.isPossibleFilestoreItem(loader.getSize())) {
+ filestoreItem = JGitUtils.getFilestoreItem(tw.getObjectReader().open(id));
+ }
+
+ final long size = (filestoreItem == null) ? loader.getSize() : filestoreItem.getSize();
+
+ entry.setSize(size);
tos.putArchiveEntry(entry);
- loader.copyTo(tos);
+
+ if (filestoreItem == null) {
+ //Copy repository stored file
+ loader.copyTo(tos);
+ } else {
+ //Copy filestore file
+ try (FileInputStream streamIn = new FileInputStream(filestoreManager.getStoragePath(filestoreItem.oid))) {
+
+ IOUtils.copyLarge(streamIn, tos);
+ } catch (Throwable e) {
+ LOGGER.error(MessageFormat.format("Failed to archive filestore item {0}", filestoreItem.oid), e);
+
+ //Handle as per other errors
+ throw e;
+ }
+ }
+
tos.closeArchiveEntry();
}
}
@@ -291,7 +351,7 @@
} catch (IOException e) {
error(e, repository, "{0} failed to {1} stream files from commit {2}", algorithm, commit.getName());
} finally {
- tw.release();
+ tw.close();
rw.dispose();
}
return success;
diff --git a/src/main/java/com/gitblit/utils/DiffStatFormatter.java b/src/main/java/com/gitblit/utils/DiffStatFormatter.java
index 572046e..a6c3ae2 100644
--- a/src/main/java/com/gitblit/utils/DiffStatFormatter.java
+++ b/src/main/java/com/gitblit/utils/DiffStatFormatter.java
@@ -20,6 +20,7 @@
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.io.NullOutputStream;
import com.gitblit.models.PathModel.PathChangeModel;
@@ -37,9 +38,9 @@
private PathChangeModel path;
- public DiffStatFormatter(String commitId) {
+ public DiffStatFormatter(String commitId, Repository repository) {
super(NullOutputStream.INSTANCE);
- diffStat = new DiffStat(commitId);
+ diffStat = new DiffStat(commitId, repository);
}
@Override
diff --git a/src/main/java/com/gitblit/utils/DiffUtils.java b/src/main/java/com/gitblit/utils/DiffUtils.java
index dd2a780..41aab4c 100644
--- a/src/main/java/com/gitblit/utils/DiffUtils.java
+++ b/src/main/java/com/gitblit/utils/DiffUtils.java
@@ -52,6 +52,27 @@
private static final Logger LOGGER = LoggerFactory.getLogger(DiffUtils.class);
/**
+ * Callback interface for binary diffs. All the getDiff methods here take an optional handler;
+ * if given and the {@link DiffOutputType} is {@link DiffOutputType#HTML HTML}, it is responsible
+ * for displaying a binary diff.
+ */
+ public interface BinaryDiffHandler {
+
+ /**
+ * Renders a binary diff. The result must be valid HTML, it will be inserted into an HTML table cell.
+ * May return {@code null} if the default behavior (which is typically just a textual note "Bnary
+ * files differ") is desired.
+ *
+ * @param diffEntry
+ * current diff entry
+ *
+ * @return the rendered diff as HTML, or {@code null} if the default is desired.
+ */
+ public String renderBinaryDiff(final DiffEntry diffEntry);
+
+ }
+
+ /**
* Enumeration for the diff output types.
*/
public static enum DiffOutputType {
@@ -68,6 +89,40 @@
}
/**
+ * Enumeration for the diff comparator types.
+ */
+ public static enum DiffComparator {
+ SHOW_WHITESPACE(RawTextComparator.DEFAULT),
+ IGNORE_WHITESPACE(RawTextComparator.WS_IGNORE_ALL),
+ IGNORE_LEADING(RawTextComparator.WS_IGNORE_LEADING),
+ IGNORE_TRAILING(RawTextComparator.WS_IGNORE_TRAILING),
+ IGNORE_CHANGES(RawTextComparator.WS_IGNORE_CHANGE);
+
+ public final RawTextComparator textComparator;
+
+ DiffComparator(RawTextComparator textComparator) {
+ this.textComparator = textComparator;
+ }
+
+ public DiffComparator getOpposite() {
+ return this == SHOW_WHITESPACE ? IGNORE_WHITESPACE : SHOW_WHITESPACE;
+ }
+
+ public String getTranslationKey() {
+ return "gb." + name().toLowerCase();
+ }
+
+ public static DiffComparator forName(String name) {
+ for (DiffComparator type : values()) {
+ if (type.name().equalsIgnoreCase(name)) {
+ return type;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
* Encapsulates the output of a diff.
*/
public static class DiffOutput implements Serializable {
@@ -102,13 +157,16 @@
public final List<PathChangeModel> paths = new ArrayList<PathChangeModel>();
private final String commitId;
+
+ private final Repository repository;
- public DiffStat(String commitId) {
+ public DiffStat(String commitId, Repository repository) {
this.commitId = commitId;
+ this.repository = repository;
}
public PathChangeModel addPath(DiffEntry entry) {
- PathChangeModel pcm = PathChangeModel.from(entry, commitId);
+ PathChangeModel pcm = PathChangeModel.from(entry, commitId, repository);
paths.add(pcm);
return pcm;
}
@@ -172,12 +230,50 @@
*
* @param repository
* @param commit
+ * @param comparator
* @param outputType
+ * @param tabLength
* @return the diff
*/
public static DiffOutput getCommitDiff(Repository repository, RevCommit commit,
- DiffOutputType outputType) {
- return getDiff(repository, null, commit, null, outputType);
+ DiffComparator comparator, DiffOutputType outputType, int tabLength) {
+ return getDiff(repository, null, commit, null, comparator, outputType, tabLength);
+ }
+
+ /**
+ * Returns the complete diff of the specified commit compared to its primary parent.
+ *
+ * @param repository
+ * @param commit
+ * @param comparator
+ * @param outputType
+ * @param handler
+ * to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
+ * May be {@code null}, resulting in the default behavior.
+ * @param tabLength
+ * @return the diff
+ */
+ public static DiffOutput getCommitDiff(Repository repository, RevCommit commit,
+ DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
+ return getDiff(repository, null, commit, null, comparator, outputType, handler, tabLength);
+ }
+
+
+ /**
+ * Returns the diff for the specified file or folder from the specified
+ * commit compared to its primary parent.
+ *
+ * @param repository
+ * @param commit
+ * @param path
+ * @param comparator
+ * @param outputType
+ * @param tabLength
+ * @return the diff
+ */
+ public static DiffOutput getDiff(Repository repository, RevCommit commit, String path,
+ DiffComparator comparator, DiffOutputType outputType, int tabLength) {
+ return getDiff(repository, null, commit, path, comparator, outputType, tabLength);
}
/**
@@ -187,12 +283,17 @@
* @param repository
* @param commit
* @param path
+ * @param comparator
* @param outputType
+ * @param handler
+ * to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
+ * May be {@code null}, resulting in the default behavior.
+ * @param tabLength
* @return the diff
*/
public static DiffOutput getDiff(Repository repository, RevCommit commit, String path,
- DiffOutputType outputType) {
- return getDiff(repository, null, commit, path, outputType);
+ DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
+ return getDiff(repository, null, commit, path, comparator, outputType, handler, tabLength);
}
/**
@@ -201,12 +302,34 @@
* @param repository
* @param baseCommit
* @param commit
+ * @param comparator
* @param outputType
+ * @param tabLength
+ *
* @return the diff
*/
public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
- DiffOutputType outputType) {
- return getDiff(repository, baseCommit, commit, null, outputType);
+ DiffComparator comparator, DiffOutputType outputType, int tabLength) {
+ return getDiff(repository, baseCommit, commit, null, comparator, outputType, tabLength);
+ }
+
+ /**
+ * Returns the complete diff between the two specified commits.
+ *
+ * @param repository
+ * @param baseCommit
+ * @param commit
+ * @param comparator
+ * @param outputType
+ * @param handler
+ * to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
+ * May be {@code null}, resulting in the default behavior.
+ * @param tabLength
+ * @return the diff
+ */
+ public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
+ DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
+ return getDiff(repository, baseCommit, commit, null, comparator, outputType, handler, tabLength);
}
/**
@@ -221,27 +344,54 @@
* if the path is specified, the diff is restricted to that file
* or folder. if unspecified, the diff is for the entire commit.
* @param outputType
+ * @param diffComparator
+ * @param tabLength
* @return the diff
*/
public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
- String path, DiffOutputType outputType) {
+ String path, DiffComparator diffComparator, DiffOutputType outputType, int tabLength) {
+ return getDiff(repository, baseCommit, commit, path, diffComparator, outputType, null, tabLength);
+ }
+
+ /**
+ * Returns the diff between two commits for the specified file.
+ *
+ * @param repository
+ * @param baseCommit
+ * if base commit is null the diff is to the primary parent of
+ * the commit.
+ * @param commit
+ * @param path
+ * if the path is specified, the diff is restricted to that file
+ * or folder. if unspecified, the diff is for the entire commit.
+ * @param comparator
+ * @param outputType
+ * @param handler
+ * to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
+ * May be {@code null}, resulting in the default behavior.
+ * @param tabLength
+ * @return the diff
+ */
+ public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit, String path,
+ DiffComparator comparator, DiffOutputType outputType, final BinaryDiffHandler handler, int tabLength) {
DiffStat stat = null;
String diff = null;
try {
- final ByteArrayOutputStream os = new ByteArrayOutputStream();
- RawTextComparator cmp = RawTextComparator.DEFAULT;
+ ByteArrayOutputStream os = null;
+
DiffFormatter df;
switch (outputType) {
case HTML:
- df = new GitBlitDiffFormatter(os, commit.getName());
+ df = new GitBlitDiffFormatter(commit.getName(), repository, path, handler, tabLength);
break;
case PLAIN:
default:
+ os = new ByteArrayOutputStream();
df = new DiffFormatter(os);
break;
}
df.setRepository(repository);
- df.setDiffComparator(cmp);
+ df.setDiffComparator((comparator == null ? DiffComparator.SHOW_WHITESPACE : comparator).textComparator);
df.setDetectRenames(true);
RevTree commitTree = commit.getTree();
@@ -271,6 +421,7 @@
} else {
df.format(diffEntries);
}
+ df.flush();
if (df instanceof GitBlitDiffFormatter) {
// workaround for complex private methods in DiffFormatter
diff = ((GitBlitDiffFormatter) df).getHtml();
@@ -278,7 +429,6 @@
} else {
diff = os.toString();
}
- df.flush();
} catch (Throwable t) {
LOGGER.error("failed to generate commit diff!", t);
}
@@ -401,7 +551,7 @@
DiffStat stat = null;
try {
RawTextComparator cmp = RawTextComparator.DEFAULT;
- DiffStatFormatter df = new DiffStatFormatter(commit.getName());
+ DiffStatFormatter df = new DiffStatFormatter(commit.getName(), repository);
df.setRepository(repository);
df.setDiffComparator(cmp);
df.setDetectRenames(true);
diff --git a/src/main/java/com/gitblit/utils/FileUtils.java b/src/main/java/com/gitblit/utils/FileUtils.java
index e7f0104..ad2509d 100644
--- a/src/main/java/com/gitblit/utils/FileUtils.java
+++ b/src/main/java/com/gitblit/utils/FileUtils.java
@@ -140,9 +140,10 @@
public static String readContent(File file, String lineEnding) {
StringBuilder sb = new StringBuilder();
InputStreamReader is = null;
+ BufferedReader reader = null;
try {
is = new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8"));
- BufferedReader reader = new BufferedReader(is);
+ reader = new BufferedReader(is);
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
@@ -154,6 +155,14 @@
System.err.println("Failed to read content of " + file.getAbsolutePath());
t.printStackTrace();
} finally {
+ if (reader != null){
+ try {
+ reader.close();
+ } catch (IOException ioe) {
+ System.err.println("Failed to close file " + file.getAbsolutePath());
+ ioe.printStackTrace();
+ }
+ }
if (is != null) {
try {
is.close();
diff --git a/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java b/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java
index 47ff143..8ebadbf 100644
--- a/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java
+++ b/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java
@@ -1,236 +1,536 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * 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.
- */
-package com.gitblit.utils;
-
-import static org.eclipse.jgit.lib.Constants.encode;
-import static org.eclipse.jgit.lib.Constants.encodeASCII;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.text.MessageFormat;
-
-import org.eclipse.jgit.diff.DiffEntry;
-import org.eclipse.jgit.diff.DiffFormatter;
-import org.eclipse.jgit.diff.RawText;
-import org.eclipse.jgit.util.RawParseUtils;
-
-import com.gitblit.models.PathModel.PathChangeModel;
-import com.gitblit.utils.DiffUtils.DiffStat;
-
-/**
- * Generates an html snippet of a diff in Gitblit's style, tracks changed paths,
- * and calculates diff stats.
- *
- * @author James Moger
- *
- */
-public class GitBlitDiffFormatter extends DiffFormatter {
-
- private final OutputStream os;
-
- private final DiffStat diffStat;
-
- private PathChangeModel currentPath;
-
- private int left, right;
-
- public GitBlitDiffFormatter(OutputStream os, String commitId) {
- super(os);
- this.os = os;
- this.diffStat = new DiffStat(commitId);
- }
-
- @Override
- public void format(DiffEntry ent) throws IOException {
- currentPath = diffStat.addPath(ent);
- super.format(ent);
- }
-
- /**
- * Output a hunk header
- *
- * @param aStartLine
- * within first source
- * @param aEndLine
- * within first source
- * @param bStartLine
- * within second source
- * @param bEndLine
- * within second source
- * @throws IOException
- */
- @Override
- protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine)
- throws IOException {
- os.write("<tr><th>..</th><th>..</th><td class='hunk_header'>".getBytes());
- os.write('@');
- os.write('@');
- writeRange('-', aStartLine + 1, aEndLine - aStartLine);
- writeRange('+', bStartLine + 1, bEndLine - bStartLine);
- os.write(' ');
- os.write('@');
- os.write('@');
- os.write("</td></tr>\n".getBytes());
- left = aStartLine + 1;
- right = bStartLine + 1;
- }
-
- protected void writeRange(final char prefix, final int begin, final int cnt) throws IOException {
- os.write(' ');
- os.write(prefix);
- switch (cnt) {
- case 0:
- // If the range is empty, its beginning number must
- // be the
- // line just before the range, or 0 if the range is
- // at the
- // start of the file stream. Here, begin is always 1
- // based,
- // so an empty file would produce "0,0".
- //
- os.write(encodeASCII(begin - 1));
- os.write(',');
- os.write('0');
- break;
-
- case 1:
- // If the range is exactly one line, produce only
- // the number.
- //
- os.write(encodeASCII(begin));
- break;
-
- default:
- os.write(encodeASCII(begin));
- os.write(',');
- os.write(encodeASCII(cnt));
- break;
- }
- }
-
- @Override
- protected void writeLine(final char prefix, final RawText text, final int cur)
- throws IOException {
- // update entry diffstat
- currentPath.update(prefix);
-
- // output diff
- os.write("<tr>".getBytes());
- switch (prefix) {
- case '+':
- os.write(("<th></th><th>" + (right++) + "</th>").getBytes());
- os.write("<td><div class=\"diff add2\">".getBytes());
- break;
- case '-':
- os.write(("<th>" + (left++) + "</th><th></th>").getBytes());
- os.write("<td><div class=\"diff remove2\">".getBytes());
- break;
- default:
- os.write(("<th>" + (left++) + "</th><th>" + (right++) + "</th>").getBytes());
- os.write("<td>".getBytes());
- break;
- }
- os.write(prefix);
- String line = text.getString(cur);
- line = StringUtils.escapeForHtml(line, false);
- os.write(encode(line));
- switch (prefix) {
- case '+':
- case '-':
- os.write("</div>".getBytes());
- break;
- default:
- os.write("</td>".getBytes());
- }
- os.write("</tr>\n".getBytes());
- }
-
- /**
- * Workaround function for complex private methods in DiffFormatter. This
- * sets the html for the diff headers.
- *
- * @return
- */
- public String getHtml() {
- ByteArrayOutputStream bos = (ByteArrayOutputStream) os;
- String html = RawParseUtils.decode(bos.toByteArray());
- String[] lines = html.split("\n");
- StringBuilder sb = new StringBuilder();
- boolean inFile = false;
- String oldnull = "a/dev/null";
- for (String line : lines) {
- if (line.startsWith("index")) {
- // skip index lines
- } else if (line.startsWith("new file")) {
- // skip new file lines
- } else if (line.startsWith("\\ No newline")) {
- // skip no new line
- } else if (line.startsWith("---") || line.startsWith("+++")) {
- // skip --- +++ lines
- } else if (line.startsWith("diff")) {
- line = StringUtils.convertOctal(line);
- if (line.indexOf(oldnull) > -1) {
- // a is null, use b
- line = line.substring(("diff --git " + oldnull).length()).trim();
- // trim b/
- line = line.substring(2).trim();
- } else {
- // use a
- line = line.substring("diff --git ".length()).trim();
- line = line.substring(line.startsWith("\"a/") ? 3 : 2);
- line = line.substring(0, line.indexOf(" b/") > -1 ? line.indexOf(" b/") : line.indexOf("\"b/")).trim();
- }
-
- if (line.charAt(0) == '"') {
- line = line.substring(1);
- }
- if (line.charAt(line.length() - 1) == '"') {
- line = line.substring(0, line.length() - 1);
- }
- if (inFile) {
- sb.append("</tbody></table></div>\n");
- inFile = false;
- }
-
- sb.append(MessageFormat.format("<div class='header'><div class=\"diffHeader\" id=\"{0}\"><i class=\"icon-file\"></i> ", line)).append(line).append("</div></div>");
- sb.append("<div class=\"diff\">");
- sb.append("<table><tbody>");
- inFile = true;
- } else {
- boolean gitLinkDiff = line.length() > 0 && line.substring(1).startsWith("Subproject commit");
- if (gitLinkDiff) {
- sb.append("<tr><th></th><th></th>");
- if (line.charAt(0) == '+') {
- sb.append("<td><div class=\"diff add2\">");
- } else {
- sb.append("<td><div class=\"diff remove2\">");
- }
- }
- sb.append(line);
- if (gitLinkDiff) {
- sb.append("</div></td></tr>");
- }
- }
- }
- sb.append("</table></div>");
- return sb.toString();
- }
-
- public DiffStat getDiffStat() {
- return diffStat;
- }
-}
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.utils;
+
+import static org.eclipse.jgit.lib.Constants.encode;
+import static org.eclipse.jgit.lib.Constants.encodeASCII;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.Localizer;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.RawParseUtils;
+
+import com.gitblit.models.PathModel.PathChangeModel;
+import com.gitblit.utils.DiffUtils.BinaryDiffHandler;
+import com.gitblit.utils.DiffUtils.DiffStat;
+import com.gitblit.wicket.GitBlitWebApp;
+
+/**
+ * Generates an html snippet of a diff in Gitblit's style, tracks changed paths, and calculates diff stats.
+ *
+ * @author James Moger
+ * @author Tom <tw201207@gmail.com>
+ *
+ */
+public class GitBlitDiffFormatter extends DiffFormatter {
+
+ /** Regex pattern identifying trailing whitespace. */
+ private static final Pattern trailingWhitespace = Pattern.compile("(\\s+?)\r?\n?$");
+
+ /**
+ * gitblit.properties key for the per-file limit on the number of diff lines.
+ */
+ private static final String DIFF_LIMIT_PER_FILE_KEY = "web.maxDiffLinesPerFile";
+
+ /**
+ * gitblit.properties key for the global limit on the number of diff lines in a commitdiff.
+ */
+ private static final String GLOBAL_DIFF_LIMIT_KEY = "web.maxDiffLines";
+
+ /**
+ * Diffs with more lines are not shown in commitdiffs. (Similar to what GitHub does.) Can be reduced
+ * (but not increased) through gitblit.properties key {@link #DIFF_LIMIT_PER_FILE_KEY}.
+ */
+ private static final int DIFF_LIMIT_PER_FILE = 4000;
+
+ /**
+ * Global diff limit. Commitdiffs with more lines are truncated. Can be reduced (but not increased)
+ * through gitblit.properties key {@link #GLOBAL_DIFF_LIMIT_KEY}.
+ */
+ private static final int GLOBAL_DIFF_LIMIT = 20000;
+
+ private static final boolean CONVERT_TABS = true;
+
+ private final DiffOutputStream os;
+
+ private final DiffStat diffStat;
+
+ private PathChangeModel currentPath;
+
+ private int left, right;
+
+ /**
+ * If a single file diff in a commitdiff produces more than this number of lines, we don't display
+ * the diff. First, it's too taxing on the browser: it'll spend an awful lot of time applying the
+ * CSS rules (despite my having optimized them). And second, no human can read a diff with thousands
+ * of lines and make sense of it.
+ * <p>
+ * Set to {@link #DIFF_LIMIT_PER_FILE} for commitdiffs, and to -1 (switches off the limit) for
+ * single-file diffs.
+ * </p>
+ */
+ private final int maxDiffLinesPerFile;
+
+ /**
+ * Global limit on the number of diff lines. Set to {@link #GLOBAL_DIFF_LIMIT} for commitdiffs, and
+ * to -1 (switched off the limit) for single-file diffs.
+ */
+ private final int globalDiffLimit;
+
+ /** Number of lines for the current file diff. Set to zero when a new DiffEntry is started. */
+ private int nofLinesCurrent;
+ /**
+ * Position in the stream when we try to write the first line. Used to rewind when we detect that
+ * the diff is too large.
+ */
+ private int startCurrent;
+ /** Flag set to true when we rewind. Reset to false when we start a new DiffEntry. */
+ private boolean isOff;
+ /** The current diff entry. */
+ private DiffEntry entry;
+
+ // Global limit stuff.
+
+ /** Total number of lines written before the current diff entry. */
+ private int totalNofLinesPrevious;
+ /** Running total of the number of diff lines written. Updated until we exceed the global limit. */
+ private int totalNofLinesCurrent;
+ /** Stream position to reset to if we decided to truncate the commitdiff. */
+ private int truncateTo;
+ /** Whether we decided to truncate the commitdiff. */
+ private boolean truncated;
+ /** If {@link #truncated}, contains all entries skipped. */
+ private final List<DiffEntry> skipped = new ArrayList<DiffEntry>();
+
+ private int tabLength;
+
+ /**
+ * A {@link ResettableByteArrayOutputStream} that intercept the "Binary files differ" message produced
+ * by the super implementation. Unfortunately the super implementation has far too many things private;
+ * otherwise we'd just have re-implemented {@link GitBlitDiffFormatter#format(DiffEntry) format(DiffEntry)}
+ * completely without ever calling the super implementation.
+ */
+ private static class DiffOutputStream extends ResettableByteArrayOutputStream {
+
+ private static final String BINARY_DIFFERENCE = "Binary files differ\n";
+
+ private GitBlitDiffFormatter formatter;
+ private BinaryDiffHandler binaryDiffHandler;
+
+ public void setFormatter(GitBlitDiffFormatter formatter, BinaryDiffHandler handler) {
+ this.formatter = formatter;
+ this.binaryDiffHandler = handler;
+ }
+
+ @Override
+ public void write(byte[] b, int offset, int length) {
+ if (binaryDiffHandler != null
+ && RawParseUtils.decode(Arrays.copyOfRange(b, offset, offset + length)).contains(BINARY_DIFFERENCE))
+ {
+ String binaryDiff = binaryDiffHandler.renderBinaryDiff(formatter.entry);
+ if (binaryDiff != null) {
+ byte[] bb = ("<tr><td colspan='4' align='center'>" + binaryDiff + "</td></tr>").getBytes(StandardCharsets.UTF_8);
+ super.write(bb, 0, bb.length);
+ return;
+ }
+ }
+ super.write(b, offset, length);
+ }
+
+ }
+
+ public GitBlitDiffFormatter(String commitId, Repository repository, String path, BinaryDiffHandler handler, int tabLength) {
+ super(new DiffOutputStream());
+ this.os = (DiffOutputStream) getOutputStream();
+ this.os.setFormatter(this, handler);
+ this.diffStat = new DiffStat(commitId, repository);
+ this.tabLength = tabLength;
+ // If we have a full commitdiff, install maxima to avoid generating a super-long diff listing that
+ // will only tax the browser too much.
+ maxDiffLinesPerFile = path != null ? -1 : getLimit(DIFF_LIMIT_PER_FILE_KEY, 500, DIFF_LIMIT_PER_FILE);
+ globalDiffLimit = path != null ? -1 : getLimit(GLOBAL_DIFF_LIMIT_KEY, 1000, GLOBAL_DIFF_LIMIT);
+ }
+
+ /**
+ * Determines a limit to use for HTML diff output.
+ *
+ * @param key
+ * to use to read the value from the GitBlit settings, if available.
+ * @param minimum
+ * minimum value to enforce
+ * @param maximum
+ * maximum (and default) value to enforce
+ * @return the limit
+ */
+ private int getLimit(String key, int minimum, int maximum) {
+ if (Application.exists()) {
+ Application application = Application.get();
+ if (application instanceof GitBlitWebApp) {
+ GitBlitWebApp webApp = (GitBlitWebApp) application;
+ int configValue = webApp.settings().getInteger(key, maximum);
+ if (configValue < minimum) {
+ return minimum;
+ } else if (configValue < maximum) {
+ return configValue;
+ }
+ }
+ }
+ return maximum;
+ }
+
+ /**
+ * Returns a localized message string, if there is a localization; otherwise the given default value.
+ *
+ * @param key
+ * message key for the message
+ * @param defaultValue
+ * to use if no localization for the message can be found
+ * @return the possibly localized message
+ */
+ private String getMsg(String key, String defaultValue) {
+ if (Application.exists()) {
+ Localizer localizer = Application.get().getResourceSettings().getLocalizer();
+ if (localizer != null) {
+ // Use getStringIgnoreSettings because we don't want exceptions here if the key is missing!
+ return localizer.getStringIgnoreSettings(key, null, null, defaultValue);
+ }
+ }
+ return defaultValue;
+ }
+
+ @Override
+ public void format(DiffEntry ent) throws IOException {
+ currentPath = diffStat.addPath(ent);
+ nofLinesCurrent = 0;
+ isOff = false;
+ entry = ent;
+ if (!truncated) {
+ totalNofLinesPrevious = totalNofLinesCurrent;
+ if (globalDiffLimit > 0 && totalNofLinesPrevious > globalDiffLimit) {
+ truncated = true;
+ isOff = true;
+ }
+ truncateTo = os.size();
+ } else {
+ isOff = true;
+ }
+ if (truncated) {
+ skipped.add(ent);
+ } else {
+ // Produce a header here and now
+ String path;
+ String id;
+ if (ChangeType.DELETE.equals(ent.getChangeType())) {
+ path = ent.getOldPath();
+ id = ent.getOldId().name();
+ } else {
+ path = ent.getNewPath();
+ id = ent.getNewId().name();
+ }
+ StringBuilder sb = new StringBuilder(MessageFormat.format("<div class='header'><div class=\"diffHeader\" id=\"n{0}\"><i class=\"icon-file\"></i> ", id));
+ sb.append(StringUtils.escapeForHtml(path, false)).append("</div></div>");
+ sb.append("<div class=\"diff\"><table cellpadding='0'><tbody>\n");
+ os.write(sb.toString().getBytes());
+ }
+ // Keep formatting, but if off, don't produce anything anymore. We just keep on counting.
+ super.format(ent);
+ if (!truncated) {
+ // Close the table
+ os.write("</tbody></table></div>\n".getBytes());
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ if (truncated) {
+ os.resetTo(truncateTo);
+ }
+ super.flush();
+ }
+
+ /**
+ * Rewind and issue a message that the diff is too large.
+ */
+ private void reset() {
+ if (!isOff) {
+ os.resetTo(startCurrent);
+ writeFullWidthLine(getMsg("gb.diffFileDiffTooLarge", "Diff too large"));
+ totalNofLinesCurrent = totalNofLinesPrevious;
+ isOff = true;
+ }
+ }
+
+ /**
+ * Writes an initial table row containing information about added/removed/renamed/copied files. In case
+ * of a deletion, we also suppress generating the diff; it's not interesting. (All lines removed.)
+ */
+ private void handleChange() {
+ // XXX Would be nice if we could generate blob links for the cases handled here. Alas, we lack the repo
+ // name, and cannot reliably determine it here. We could get the .git directory of a Repository, if we
+ // passed in the repo, and then take the name of the parent directory, but that'd fail for repos nested
+ // in GitBlit projects. And we don't know if the repo is inside a project or is a top-level repo.
+ //
+ // That's certainly solvable (just pass along more information), but would require a larger rewrite than
+ // I'm prepared to do now.
+ String message;
+ switch (entry.getChangeType()) {
+ case ADD:
+ message = getMsg("gb.diffNewFile", "New file");
+ break;
+ case DELETE:
+ message = getMsg("gb.diffDeletedFile", "File was deleted");
+ isOff = true;
+ break;
+ case RENAME:
+ message = MessageFormat.format(getMsg("gb.diffRenamedFile", "File was renamed from {0}"), entry.getOldPath());
+ break;
+ case COPY:
+ message = MessageFormat.format(getMsg("gb.diffCopiedFile", "File was copied from {0}"), entry.getOldPath());
+ break;
+ default:
+ return;
+ }
+ writeFullWidthLine(message);
+ }
+
+ /**
+ * Output a hunk header
+ *
+ * @param aStartLine
+ * within first source
+ * @param aEndLine
+ * within first source
+ * @param bStartLine
+ * within second source
+ * @param bEndLine
+ * within second source
+ * @throws IOException
+ */
+ @Override
+ protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine) throws IOException {
+ if (nofLinesCurrent++ == 0) {
+ handleChange();
+ startCurrent = os.size();
+ }
+ if (!isOff) {
+ totalNofLinesCurrent++;
+ if (nofLinesCurrent > maxDiffLinesPerFile && maxDiffLinesPerFile > 0) {
+ reset();
+ } else {
+ os.write("<tr><th class='diff-line' data-lineno='..'></th><th class='diff-line' data-lineno='..'></th><th class='diff-state'></th><td class='hunk_header'>"
+ .getBytes());
+ os.write('@');
+ os.write('@');
+ writeRange('-', aStartLine + 1, aEndLine - aStartLine);
+ writeRange('+', bStartLine + 1, bEndLine - bStartLine);
+ os.write(' ');
+ os.write('@');
+ os.write('@');
+ os.write("</td></tr>\n".getBytes());
+ }
+ }
+ left = aStartLine + 1;
+ right = bStartLine + 1;
+ }
+
+ protected void writeRange(final char prefix, final int begin, final int cnt) throws IOException {
+ os.write(' ');
+ os.write(prefix);
+ switch (cnt) {
+ case 0:
+ // If the range is empty, its beginning number must be the
+ // line just before the range, or 0 if the range is at the
+ // start of the file stream. Here, begin is always 1 based,
+ // so an empty file would produce "0,0".
+ //
+ os.write(encodeASCII(begin - 1));
+ os.write(',');
+ os.write('0');
+ break;
+
+ case 1:
+ // If the range is exactly one line, produce only the number.
+ //
+ os.write(encodeASCII(begin));
+ break;
+
+ default:
+ os.write(encodeASCII(begin));
+ os.write(',');
+ os.write(encodeASCII(cnt));
+ break;
+ }
+ }
+
+ /**
+ * Writes a line spanning the full width of the code view, including the gutter.
+ *
+ * @param text
+ * to put on that line; will be HTML-escaped.
+ */
+ private void writeFullWidthLine(String text) {
+ try {
+ os.write("<tr><td class='diff-cell' colspan='4'>".getBytes());
+ os.write(StringUtils.escapeForHtml(text, false).getBytes());
+ os.write("</td></tr>\n".getBytes());
+ } catch (IOException ex) {
+ // Cannot happen with a ByteArrayOutputStream
+ }
+ }
+
+ @Override
+ protected void writeLine(final char prefix, final RawText text, final int cur) throws IOException {
+ if (nofLinesCurrent++ == 0) {
+ handleChange();
+ startCurrent = os.size();
+ }
+ // update entry diffstat
+ currentPath.update(prefix);
+ if (isOff) {
+ return;
+ }
+ totalNofLinesCurrent++;
+ if (nofLinesCurrent > maxDiffLinesPerFile && maxDiffLinesPerFile > 0) {
+ reset();
+ } else {
+ // output diff
+ os.write("<tr>".getBytes());
+ switch (prefix) {
+ case '+':
+ os.write(("<th class='diff-line'></th><th class='diff-line' data-lineno='" + (right++) + "'></th>").getBytes());
+ os.write("<th class='diff-state diff-state-add'></th>".getBytes());
+ os.write("<td class='diff-cell add2'>".getBytes());
+ break;
+ case '-':
+ os.write(("<th class='diff-line' data-lineno='" + (left++) + "'></th><th class='diff-line'></th>").getBytes());
+ os.write("<th class='diff-state diff-state-sub'></th>".getBytes());
+ os.write("<td class='diff-cell remove2'>".getBytes());
+ break;
+ default:
+ os.write(("<th class='diff-line' data-lineno='" + (left++) + "'></th><th class='diff-line' data-lineno='" + (right++) + "'></th>").getBytes());
+ os.write("<th class='diff-state'></th>".getBytes());
+ os.write("<td class='diff-cell context2'>".getBytes());
+ break;
+ }
+ os.write(encode(codeLineToHtml(prefix, text.getString(cur))));
+ os.write("</td></tr>\n".getBytes());
+ }
+ }
+
+ /**
+ * Convert the given code line to HTML.
+ *
+ * @param prefix
+ * the diff prefix (+/-) indicating whether the line was added or removed.
+ * @param line
+ * the line to format as HTML
+ * @return the HTML-formatted line, safe for inserting as is into HTML.
+ */
+ private String codeLineToHtml(final char prefix, final String line) {
+ if ((prefix == '+' || prefix == '-')) {
+ // Highlight trailing whitespace on deleted/added lines.
+ Matcher matcher = trailingWhitespace.matcher(line);
+ if (matcher.find()) {
+ StringBuilder result = new StringBuilder(StringUtils.escapeForHtml(line.substring(0, matcher.start()), CONVERT_TABS, tabLength));
+ result.append("<span class='trailingws-").append(prefix == '+' ? "add" : "sub").append("'>");
+ result.append(StringUtils.escapeForHtml(matcher.group(1), false));
+ result.append("</span>");
+ return result.toString();
+ }
+ }
+ return StringUtils.escapeForHtml(line, CONVERT_TABS, tabLength);
+ }
+
+ /**
+ * Workaround function for complex private methods in DiffFormatter. This sets the html for the diff headers.
+ *
+ * @return
+ */
+ public String getHtml() {
+ String html = RawParseUtils.decode(os.toByteArray());
+ String[] lines = html.split("\n");
+ StringBuilder sb = new StringBuilder();
+ for (String line : lines) {
+ if (line.startsWith("index") || line.startsWith("similarity")
+ || line.startsWith("rename from ") || line.startsWith("rename to ")) {
+ // skip index lines
+ } else if (line.startsWith("new file") || line.startsWith("deleted file")) {
+ // skip new file lines
+ } else if (line.startsWith("\\ No newline")) {
+ // skip no new line
+ } else if (line.startsWith("---") || line.startsWith("+++")) {
+ // skip --- +++ lines
+ } else if (line.startsWith("diff")) {
+ // skip diff lines
+ } else {
+ boolean gitLinkDiff = line.length() > 0 && line.substring(1).startsWith("Subproject commit");
+ if (gitLinkDiff) {
+ sb.append("<tr><th class='diff-line'></th><th class='diff-line'></th>");
+ if (line.charAt(0) == '+') {
+ sb.append("<th class='diff-state diff-state-add'></th><td class=\"diff-cell add2\">");
+ } else {
+ sb.append("<th class='diff-state diff-state-sub'></th><td class=\"diff-cell remove2\">");
+ }
+ line = StringUtils.escapeForHtml(line.substring(1), CONVERT_TABS, tabLength);
+ }
+ sb.append(line);
+ if (gitLinkDiff) {
+ sb.append("</td></tr>");
+ }
+ sb.append('\n');
+ }
+ }
+ if (truncated) {
+ sb.append(MessageFormat.format("<div class='header'><div class='diffHeader'>{0}</div></div>",
+ StringUtils.escapeForHtml(getMsg("gb.diffTruncated", "Diff truncated after the above file"), false)));
+ // List all files not shown. We can be sure we do have at least one path in skipped.
+ sb.append("<div class='diff'><table cellpadding='0'><tbody><tr><td class='diff-cell' colspan='4'>");
+ String deletedSuffix = StringUtils.escapeForHtml(getMsg("gb.diffDeletedFileSkipped", "(deleted)"), false);
+ boolean first = true;
+ for (DiffEntry entry : skipped) {
+ if (!first) {
+ sb.append('\n');
+ }
+ if (ChangeType.DELETE.equals(entry.getChangeType())) {
+ sb.append("<span id=\"n" + entry.getOldId().name() + "\">" + StringUtils.escapeForHtml(entry.getOldPath(), false) + ' ' + deletedSuffix + "</span>");
+ } else {
+ sb.append("<span id=\"n" + entry.getNewId().name() + "\">" + StringUtils.escapeForHtml(entry.getNewPath(), false) + "</span>");
+ }
+ first = false;
+ }
+ skipped.clear();
+ sb.append("</td></tr></tbody></table></div>");
+ }
+ return sb.toString();
+ }
+
+ public DiffStat getDiffStat() {
+ return diffStat;
+ }
+}
diff --git a/src/main/java/com/gitblit/utils/HtmlBuilder.java b/src/main/java/com/gitblit/utils/HtmlBuilder.java
new file mode 100644
index 0000000..6208ea8
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/HtmlBuilder.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2014 Tom <tw201207@gmail.com>
+ *
+ * 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.
+ */
+package com.gitblit.utils;
+
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.parser.Tag;
+
+/**
+ * Simple helper class to hide some common setup needed to use JSoup as an HTML generator.
+ * A {@link HtmlBuilder} has a root element that can be manipulated in all the usual JSoup
+ * ways (but not added to some {@link Document}); to generate HTML for that root element,
+ * the builder's {@link #toString()} method can be used. (Or one can invoke toString()
+ * directly on the root element.) By default, a HTML builder does not pretty-print the HTML.
+ *
+ * @author Tom <tw201207@gmail.com>
+ */
+public class HtmlBuilder {
+
+ private final Document shell;
+ private final Element root;
+
+ /**
+ * Creates a new HTML builder with a root element with the given {@code tagName}.
+ *
+ * @param tagName
+ * of the {@link Element} to set as the root element.
+ */
+ public HtmlBuilder(String tagName) {
+ this(new Element(Tag.valueOf(tagName), ""));
+ }
+
+ /**
+ * Creates a new HTML builder for the given element.
+ *
+ * @param element
+ * to set as the root element of this HTML builder.
+ */
+ public HtmlBuilder(Element element) {
+ shell = Document.createShell("");
+ shell.outputSettings().prettyPrint(false);
+ shell.body().appendChild(element);
+ root = element;
+ }
+
+ /** @return the root element of this HTML builder */
+ public Element getRoot() {
+ return root;
+ }
+
+ /** @return the root element of this HTML builder */
+ public Element root() {
+ return root;
+ }
+
+ /** @return whether this HTML builder will pretty-print the HTML generated by {@link #toString()} */
+ public boolean prettyPrint() {
+ return shell.outputSettings().prettyPrint();
+ }
+
+ /**
+ * Sets whether this HTML builder will produce pretty-printed HTML in its {@link #toString()} method.
+ *
+ * @param pretty
+ * whether to pretty-print
+ * @return the HTML builder itself
+ */
+ public HtmlBuilder prettyPrint(boolean pretty) {
+ shell.outputSettings().prettyPrint(pretty);
+ return this;
+ }
+
+ /** @return the HTML for the root element. */
+ @Override
+ public String toString() {
+ return root.toString();
+ }
+
+ /** @return the {@link Document} used as generation shell. */
+ protected Document getShell() {
+ return shell;
+ }
+}
diff --git a/src/main/java/com/gitblit/utils/HttpUtils.java b/src/main/java/com/gitblit/utils/HttpUtils.java
index 818ed49..2fd8d89 100644
--- a/src/main/java/com/gitblit/utils/HttpUtils.java
+++ b/src/main/java/com/gitblit/utils/HttpUtils.java
@@ -78,6 +78,7 @@
}
}
+ // try to use reverse-proxy's context
String context = request.getContextPath();
String forwardedContext = request.getHeader("X-Forwarded-Context");
if (StringUtils.isEmpty(forwardedContext)) {
@@ -92,13 +93,24 @@
context = context.substring(1);
}
+ // try to use reverse-proxy's hostname
+ String host = request.getServerName();
+ String forwardedHost = request.getHeader("X-Forwarded-Host");
+ if (StringUtils.isEmpty(forwardedHost)) {
+ forwardedHost = request.getHeader("X_Forwarded_Host");
+ }
+ if (!StringUtils.isEmpty(forwardedHost)) {
+ host = forwardedHost;
+ }
+
+ // build result
StringBuilder sb = new StringBuilder();
sb.append(scheme);
sb.append("://");
- sb.append(request.getServerName());
+ sb.append(host);
if (("http".equals(scheme) && port != 80)
|| ("https".equals(scheme) && port != 443)) {
- sb.append(":" + port);
+ sb.append(":").append(port);
}
sb.append(context);
return sb.toString();
diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java
index da51ea9..adcbb4d 100644
--- a/src/main/java/com/gitblit/utils/JGitUtils.java
+++ b/src/main/java/com/gitblit/utils/JGitUtils.java
@@ -21,6 +21,7 @@
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
@@ -28,6 +29,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.filefilter.TrueFileFilter;
@@ -35,15 +37,21 @@
import org.eclipse.jgit.api.FetchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.TagCommand;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawTextComparator;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.StopWalkException;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.BlobBasedConfig;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
@@ -61,6 +69,7 @@
import org.eclipse.jgit.lib.TreeFormatter;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.merge.RecursiveMerger;
+import org.eclipse.jgit.merge.ThreeWayMerger;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
@@ -73,6 +82,7 @@
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.OrTreeFilter;
@@ -84,12 +94,17 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
+import com.gitblit.manager.GitblitManager;
+import com.gitblit.models.FilestoreModel;
import com.gitblit.models.GitNote;
import com.gitblit.models.PathModel;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.models.RefModel;
import com.gitblit.models.SubmoduleModel;
+import com.gitblit.servlet.FilestoreServlet;
+import com.google.common.base.Strings;
/**
* Collection of static methods for retrieving information from a repository.
@@ -690,7 +705,10 @@
if (commit == null) {
return new Date(0);
}
- return commit.getAuthorIdent().getWhen();
+ if (commit.getAuthorIdent() != null) {
+ return commit.getAuthorIdent().getWhen();
+ }
+ return getCommitDate(commit);
}
/**
@@ -773,7 +791,7 @@
}
} finally {
rw.dispose();
- tw.release();
+ tw.close();
}
return content;
}
@@ -884,7 +902,64 @@
} catch (IOException e) {
error(e, repository, "{0} failed to get files for commit {1}", commit.getName());
} finally {
- tw.release();
+ tw.close();
+ }
+ Collections.sort(list);
+ return list;
+ }
+
+ /**
+ * Returns the list of files in the specified folder at the specified
+ * commit. If the repository does not exist or is empty, an empty list is
+ * returned.
+ *
+ * This is modified version that implements path compression feature.
+ *
+ * @param repository
+ * @param path
+ * if unspecified, root folder is assumed.
+ * @param commit
+ * if null, HEAD is assumed.
+ * @return list of files in specified path
+ */
+ public static List<PathModel> getFilesInPath2(Repository repository, String path, RevCommit commit) {
+
+ List<PathModel> list = new ArrayList<PathModel>();
+ if (!hasCommits(repository)) {
+ return list;
+ }
+ if (commit == null) {
+ commit = getCommit(repository, null);
+ }
+ final TreeWalk tw = new TreeWalk(repository);
+ try {
+
+ tw.addTree(commit.getTree());
+ final boolean isPathEmpty = Strings.isNullOrEmpty(path);
+
+ if (!isPathEmpty) {
+ PathFilter f = PathFilter.create(path);
+ tw.setFilter(f);
+ }
+
+ tw.setRecursive(true);
+ List<String> paths = new ArrayList<>();
+
+ while (tw.next()) {
+ String child = isPathEmpty ? tw.getPathString()
+ : tw.getPathString().replaceFirst(String.format("%s/", path), "");
+ paths.add(child);
+ }
+
+ for(String p: PathUtils.compressPaths(paths)) {
+ String pathString = isPathEmpty ? p : String.format("%s/%s", path, p);
+ list.add(getPathModel(repository, pathString, path, commit));
+ }
+
+ } catch (IOException e) {
+ error(e, repository, "{0} failed to get files for commit {1}", commit.getName());
+ } finally {
+ tw.close();
}
Collections.sort(list);
return list;
@@ -932,23 +1007,40 @@
tw.setRecursive(true);
tw.addTree(commit.getTree());
while (tw.next()) {
- list.add(new PathChangeModel(tw.getPathString(), tw.getPathString(), 0, tw
- .getRawMode(0), tw.getObjectId(0).getName(), commit.getId().getName(),
+ long size = 0;
+ FilestoreModel filestoreItem = null;
+ ObjectId objectId = tw.getObjectId(0);
+
+ try {
+ if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) {
+
+ size = tw.getObjectReader().getObjectSize(objectId, Constants.OBJ_BLOB);
+
+ if (isPossibleFilestoreItem(size)) {
+ filestoreItem = getFilestoreItem(tw.getObjectReader().open(objectId));
+ }
+ }
+ } catch (Throwable t) {
+ error(t, null, "failed to retrieve blob size for " + tw.getPathString());
+ }
+
+ list.add(new PathChangeModel(tw.getPathString(), tw.getPathString(),filestoreItem, size, tw
+ .getRawMode(0), objectId.getName(), commit.getId().getName(),
ChangeType.ADD));
}
- tw.release();
+ tw.close();
} else {
RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
- DiffStatFormatter df = new DiffStatFormatter(commit.getName());
+ DiffStatFormatter df = new DiffStatFormatter(commit.getName(), repository);
df.setRepository(repository);
df.setDiffComparator(RawTextComparator.DEFAULT);
df.setDetectRenames(true);
List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree());
for (DiffEntry diff : diffs) {
// create the path change model
- PathChangeModel pcm = PathChangeModel.from(diff, commit.getName());
-
- if (calculateDiffStat) {
+ PathChangeModel pcm = PathChangeModel.from(diff, commit.getName(), repository);
+
+ if (calculateDiffStat) {
// update file diffstats
df.format(diff);
PathChangeModel pathStat = df.getDiffStat().getPath(pcm.path);
@@ -991,7 +1083,7 @@
RevCommit start = rw.parseCommit(startRange);
RevCommit end = rw.parseCommit(endRange);
list.addAll(getFilesInRange(repository, start, end));
- rw.release();
+ rw.close();
} catch (Throwable t) {
error(t, repository, "{0} failed to determine files in range {1}..{2}!", startCommit, endCommit);
}
@@ -1022,7 +1114,7 @@
List<DiffEntry> diffEntries = df.scan(startCommit.getTree(), endCommit.getTree());
for (DiffEntry diff : diffEntries) {
- PathChangeModel pcm = PathChangeModel.from(diff, endCommit.getName());
+ PathChangeModel pcm = PathChangeModel.from(diff, endCommit.getName(), repository);
list.add(pcm);
}
Collections.sort(list);
@@ -1089,7 +1181,7 @@
} catch (IOException e) {
error(e, repository, "{0} failed to get documents for commit {1}", commit.getName());
} finally {
- tw.release();
+ tw.close();
}
Collections.sort(list);
return list;
@@ -1106,22 +1198,100 @@
private static PathModel getPathModel(TreeWalk tw, String basePath, RevCommit commit) {
String name;
long size = 0;
+
if (StringUtils.isEmpty(basePath)) {
name = tw.getPathString();
} else {
name = tw.getPathString().substring(basePath.length() + 1);
}
ObjectId objectId = tw.getObjectId(0);
+ FilestoreModel filestoreItem = null;
+
try {
if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) {
+
size = tw.getObjectReader().getObjectSize(objectId, Constants.OBJ_BLOB);
+
+ if (isPossibleFilestoreItem(size)) {
+ filestoreItem = getFilestoreItem(tw.getObjectReader().open(objectId));
+ }
}
} catch (Throwable t) {
error(t, null, "failed to retrieve blob size for " + tw.getPathString());
}
- return new PathModel(name, tw.getPathString(), size, tw.getFileMode(0).getBits(),
+ return new PathModel(name, tw.getPathString(), filestoreItem, size, tw.getFileMode(0).getBits(),
objectId.getName(), commit.getName());
}
+
+ public static boolean isPossibleFilestoreItem(long size) {
+ return ( (size >= com.gitblit.Constants.LEN_FILESTORE_META_MIN)
+ && (size <= com.gitblit.Constants.LEN_FILESTORE_META_MAX));
+ }
+
+ /**
+ *
+ * @return Representative FilestoreModel if valid, otherwise null
+ */
+ public static FilestoreModel getFilestoreItem(ObjectLoader obj){
+ try {
+ final byte[] blob = obj.getCachedBytes(com.gitblit.Constants.LEN_FILESTORE_META_MAX);
+ final String meta = new String(blob, "UTF-8");
+
+ return FilestoreModel.fromMetaString(meta);
+
+ } catch (LargeObjectException e) {
+ //Intentionally failing silent
+ } catch (Exception e) {
+ error(e, null, "failed to retrieve filestoreItem " + obj.toString());
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a path model by path string
+ *
+ * @param repo
+ * @param path
+ * @param filter
+ * @param commit
+ * @return a path model of the specified object
+ */
+ private static PathModel getPathModel(Repository repo, String path, String filter, RevCommit commit)
+ throws IOException {
+
+ long size = 0;
+ FilestoreModel filestoreItem = null;
+ TreeWalk tw = TreeWalk.forPath(repo, path, commit.getTree());
+ String pathString = path;
+
+ if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) {
+
+ pathString = PathUtils.getLastPathComponent(pathString);
+
+ size = tw.getObjectReader().getObjectSize(tw.getObjectId(0), Constants.OBJ_BLOB);
+
+ if (isPossibleFilestoreItem(size)) {
+ filestoreItem = getFilestoreItem(tw.getObjectReader().open(tw.getObjectId(0)));
+ }
+ } else if (tw.isSubtree()) {
+
+ // do not display dirs that are behind in the path
+ if (!Strings.isNullOrEmpty(filter)) {
+ pathString = path.replaceFirst(filter + "/", "");
+ }
+
+ // remove the last slash from path in displayed link
+ if (pathString != null && pathString.charAt(pathString.length()-1) == '/') {
+ pathString = pathString.substring(0, pathString.length()-1);
+ }
+ }
+
+ return new PathModel(pathString, tw.getPathString(), filestoreItem, size, tw.getFileMode(0).getBits(),
+ tw.getObjectId(0).getName(), commit.getName());
+
+ }
+
/**
* Returns a permissions representation of the mode bits.
@@ -1575,13 +1745,9 @@
* @return true if successful
*/
public static boolean deleteBranchRef(Repository repository, String branch) {
- String branchName = branch;
- if (!branchName.startsWith(Constants.R_HEADS)) {
- branchName = Constants.R_HEADS + branch;
- }
try {
- RefUpdate refUpdate = repository.updateRef(branchName, false);
+ RefUpdate refUpdate = repository.updateRef(branch, false);
refUpdate.setForceUpdate(true);
RefUpdate.Result result = refUpdate.delete();
switch (result) {
@@ -1592,10 +1758,10 @@
return true;
default:
LOGGER.error(MessageFormat.format("{0} failed to delete to {1} returned result {2}",
- repository.getDirectory().getAbsolutePath(), branchName, result));
+ repository.getDirectory().getAbsolutePath(), branch, result));
}
} catch (Throwable t) {
- error(t, repository, "{0} failed to delete {1}", branchName);
+ error(t, repository, "{0} failed to delete {1}", branch);
}
return false;
}
@@ -1946,7 +2112,7 @@
error(t, repository, "{0} can't find {1} in commit {2}", path, commit.name());
} finally {
rw.dispose();
- tw.release();
+ tw.close();
}
return commitId;
}
@@ -2120,10 +2286,10 @@
success = false;
}
} finally {
- revWalk.release();
+ revWalk.close();
}
} finally {
- odi.release();
+ odi.close();
}
} catch (Throwable t) {
error(t, repository, "Failed to create orphan branch {1} in repository {0}", branchName);
@@ -2194,212 +2360,385 @@
}
return false;
}
-
- /**
- * Returns true if the commit identified by commitId is an ancestor or the
- * the commit identified by tipId.
- *
- * @param repository
- * @param commitId
- * @param tipId
- * @return true if there is the commit is an ancestor of the tip
- */
- public static boolean isMergedInto(Repository repository, String commitId, String tipId) {
- try {
- return isMergedInto(repository, repository.resolve(commitId), repository.resolve(tipId));
- } catch (Exception e) {
- LOGGER.error("Failed to determine isMergedInto", e);
- }
- return false;
- }
-
- /**
- * Returns true if the commit identified by commitId is an ancestor or the
- * the commit identified by tipId.
- *
- * @param repository
- * @param commitId
- * @param tipId
- * @return true if there is the commit is an ancestor of the tip
- */
- public static boolean isMergedInto(Repository repository, ObjectId commitId, ObjectId tipCommitId) {
- // traverse the revlog looking for a commit chain between the endpoints
- RevWalk rw = new RevWalk(repository);
- try {
- // must re-lookup RevCommits to workaround undocumented RevWalk bug
- RevCommit tip = rw.lookupCommit(tipCommitId);
- RevCommit commit = rw.lookupCommit(commitId);
- return rw.isMergedInto(commit, tip);
- } catch (Exception e) {
- LOGGER.error("Failed to determine isMergedInto", e);
- } finally {
- rw.dispose();
- }
- return false;
- }
-
- /**
- * Returns the merge base of two commits or null if there is no common
- * ancestry.
- *
- * @param repository
- * @param commitIdA
- * @param commitIdB
- * @return the commit id of the merge base or null if there is no common base
- */
- public static String getMergeBase(Repository repository, ObjectId commitIdA, ObjectId commitIdB) {
- RevWalk rw = new RevWalk(repository);
- try {
- RevCommit a = rw.lookupCommit(commitIdA);
- RevCommit b = rw.lookupCommit(commitIdB);
-
- rw.setRevFilter(RevFilter.MERGE_BASE);
- rw.markStart(a);
- rw.markStart(b);
- RevCommit mergeBase = rw.next();
- if (mergeBase == null) {
- return null;
- }
- return mergeBase.getName();
- } catch (Exception e) {
- LOGGER.error("Failed to determine merge base", e);
- } finally {
- rw.dispose();
- }
- return null;
- }
-
- public static enum MergeStatus {
- NOT_MERGEABLE, FAILED, ALREADY_MERGED, MERGEABLE, MERGED;
- }
-
- /**
- * Determines if we can cleanly merge one branch into another. Returns true
- * if we can merge without conflict, otherwise returns false.
- *
- * @param repository
- * @param src
- * @param toBranch
- * @return true if we can merge without conflict
- */
- public static MergeStatus canMerge(Repository repository, String src, String toBranch) {
- RevWalk revWalk = null;
- try {
- revWalk = new RevWalk(repository);
- RevCommit branchTip = revWalk.lookupCommit(repository.resolve(toBranch));
- RevCommit srcTip = revWalk.lookupCommit(repository.resolve(src));
- if (revWalk.isMergedInto(srcTip, branchTip)) {
- // already merged
- return MergeStatus.ALREADY_MERGED;
- } else if (revWalk.isMergedInto(branchTip, srcTip)) {
- // fast-forward
- return MergeStatus.MERGEABLE;
- }
- RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
- boolean canMerge = merger.merge(branchTip, srcTip);
- if (canMerge) {
- return MergeStatus.MERGEABLE;
- }
- } catch (IOException e) {
- LOGGER.error("Failed to determine canMerge", e);
+
+ /**
+ * Returns true if the commit identified by commitId is an ancestor or the
+ * the commit identified by tipId.
+ *
+ * @param repository
+ * @param commitId
+ * @param tipId
+ * @return true if there is the commit is an ancestor of the tip
+ */
+ public static boolean isMergedInto(Repository repository, String commitId, String tipId) {
+ try {
+ return isMergedInto(repository, repository.resolve(commitId), repository.resolve(tipId));
+ } catch (Exception e) {
+ LOGGER.error("Failed to determine isMergedInto", e);
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the commit identified by commitId is an ancestor or the
+ * the commit identified by tipId.
+ *
+ * @param repository
+ * @param commitId
+ * @param tipId
+ * @return true if there is the commit is an ancestor of the tip
+ */
+ public static boolean isMergedInto(Repository repository, ObjectId commitId, ObjectId tipCommitId) {
+ // traverse the revlog looking for a commit chain between the endpoints
+ RevWalk rw = new RevWalk(repository);
+ try {
+ // must re-lookup RevCommits to workaround undocumented RevWalk bug
+ RevCommit tip = rw.lookupCommit(tipCommitId);
+ RevCommit commit = rw.lookupCommit(commitId);
+ return rw.isMergedInto(commit, tip);
+ } catch (Exception e) {
+ LOGGER.error("Failed to determine isMergedInto", e);
} finally {
- if (revWalk != null) {
- revWalk.release();
- }
- }
- return MergeStatus.NOT_MERGEABLE;
- }
-
-
- public static class MergeResult {
- public final MergeStatus status;
- public final String sha;
-
- MergeResult(MergeStatus status, String sha) {
- this.status = status;
- this.sha = sha;
- }
- }
-
- /**
- * Tries to merge a commit into a branch. If there are conflicts, the merge
- * will fail.
- *
- * @param repository
- * @param src
- * @param toBranch
- * @param committer
- * @param message
- * @return the merge result
- */
- public static MergeResult merge(Repository repository, String src, String toBranch,
- PersonIdent committer, String message) {
-
- if (!toBranch.startsWith(Constants.R_REFS)) {
- // branch ref doesn't start with ref, assume this is a branch head
- toBranch = Constants.R_HEADS + toBranch;
- }
-
- RevWalk revWalk = null;
- try {
- revWalk = new RevWalk(repository);
- RevCommit branchTip = revWalk.lookupCommit(repository.resolve(toBranch));
- RevCommit srcTip = revWalk.lookupCommit(repository.resolve(src));
- if (revWalk.isMergedInto(srcTip, branchTip)) {
- // already merged
- return new MergeResult(MergeStatus.ALREADY_MERGED, null);
- }
- RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
- boolean merged = merger.merge(branchTip, srcTip);
- if (merged) {
- // create a merge commit and a reference to track the merge commit
- ObjectId treeId = merger.getResultTreeId();
- ObjectInserter odi = repository.newObjectInserter();
- try {
- // Create a commit object
- CommitBuilder commitBuilder = new CommitBuilder();
- commitBuilder.setCommitter(committer);
- commitBuilder.setAuthor(committer);
- commitBuilder.setEncoding(Constants.CHARSET);
- if (StringUtils.isEmpty(message)) {
- message = MessageFormat.format("merge {0} into {1}", srcTip.getName(), branchTip.getName());
- }
- commitBuilder.setMessage(message);
- commitBuilder.setParentIds(branchTip.getId(), srcTip.getId());
- commitBuilder.setTreeId(treeId);
-
- // Insert the merge commit into the repository
- ObjectId mergeCommitId = odi.insert(commitBuilder);
- odi.flush();
-
- // set the merge ref to the merge commit
- RevCommit mergeCommit = revWalk.parseCommit(mergeCommitId);
- RefUpdate mergeRefUpdate = repository.updateRef(toBranch);
- mergeRefUpdate.setNewObjectId(mergeCommitId);
- mergeRefUpdate.setRefLogMessage("commit: " + mergeCommit.getShortMessage(), false);
- RefUpdate.Result rc = mergeRefUpdate.update();
- switch (rc) {
- case FAST_FORWARD:
- // successful, clean merge
+ rw.dispose();
+ }
+ return false;
+ }
+
+ /**
+ * Returns the merge base of two commits or null if there is no common
+ * ancestry.
+ *
+ * @param repository
+ * @param commitIdA
+ * @param commitIdB
+ * @return the commit id of the merge base or null if there is no common base
+ */
+ public static String getMergeBase(Repository repository, ObjectId commitIdA, ObjectId commitIdB) {
+ RevWalk rw = new RevWalk(repository);
+ try {
+ RevCommit a = rw.lookupCommit(commitIdA);
+ RevCommit b = rw.lookupCommit(commitIdB);
+
+ rw.setRevFilter(RevFilter.MERGE_BASE);
+ rw.markStart(a);
+ rw.markStart(b);
+ RevCommit mergeBase = rw.next();
+ if (mergeBase == null) {
+ return null;
+ }
+ return mergeBase.getName();
+ } catch (Exception e) {
+ LOGGER.error("Failed to determine merge base", e);
+ } finally {
+ rw.dispose();
+ }
+ return null;
+ }
+
+ public static enum MergeStatus {
+ MISSING_INTEGRATION_BRANCH, MISSING_SRC_BRANCH, NOT_MERGEABLE, FAILED, ALREADY_MERGED, MERGEABLE, MERGED;
+ }
+
+ /**
+ * Determines if we can cleanly merge one branch into another. Returns true
+ * if we can merge without conflict, otherwise returns false.
+ *
+ * @param repository
+ * @param src
+ * @param toBranch
+ * @return true if we can merge without conflict
+ */
+ public static MergeStatus canMerge(Repository repository, String src, String toBranch) {
+ RevWalk revWalk = null;
+ try {
+ revWalk = new RevWalk(repository);
+ ObjectId branchId = repository.resolve(toBranch);
+ if (branchId == null) {
+ return MergeStatus.MISSING_INTEGRATION_BRANCH;
+ }
+ ObjectId srcId = repository.resolve(src);
+ if (srcId == null) {
+ return MergeStatus.MISSING_SRC_BRANCH;
+ }
+ RevCommit branchTip = revWalk.lookupCommit(branchId);
+ RevCommit srcTip = revWalk.lookupCommit(srcId);
+ if (revWalk.isMergedInto(srcTip, branchTip)) {
+ // already merged
+ return MergeStatus.ALREADY_MERGED;
+ } else if (revWalk.isMergedInto(branchTip, srcTip)) {
+ // fast-forward
+ return MergeStatus.MERGEABLE;
+ }
+ RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
+ boolean canMerge = merger.merge(branchTip, srcTip);
+ if (canMerge) {
+ return MergeStatus.MERGEABLE;
+ }
+ } catch (NullPointerException e) {
+ LOGGER.error("Failed to determine canMerge", e);
+ } catch (IOException e) {
+ LOGGER.error("Failed to determine canMerge", e);
+ } finally {
+ if (revWalk != null) {
+ revWalk.close();
+ }
+ }
+ return MergeStatus.NOT_MERGEABLE;
+ }
+
+
+ public static class MergeResult {
+ public final MergeStatus status;
+ public final String sha;
+
+ MergeResult(MergeStatus status, String sha) {
+ this.status = status;
+ this.sha = sha;
+ }
+ }
+
+ /**
+ * Tries to merge a commit into a branch. If there are conflicts, the merge
+ * will fail.
+ *
+ * @param repository
+ * @param src
+ * @param toBranch
+ * @param committer
+ * @param message
+ * @return the merge result
+ */
+ public static MergeResult merge(Repository repository, String src, String toBranch,
+ PersonIdent committer, String message) {
+
+ if (!toBranch.startsWith(Constants.R_REFS)) {
+ // branch ref doesn't start with ref, assume this is a branch head
+ toBranch = Constants.R_HEADS + toBranch;
+ }
+
+ RevWalk revWalk = null;
+ try {
+ revWalk = new RevWalk(repository);
+ RevCommit branchTip = revWalk.lookupCommit(repository.resolve(toBranch));
+ RevCommit srcTip = revWalk.lookupCommit(repository.resolve(src));
+ if (revWalk.isMergedInto(srcTip, branchTip)) {
+ // already merged
+ return new MergeResult(MergeStatus.ALREADY_MERGED, null);
+ }
+ RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
+ boolean merged = merger.merge(branchTip, srcTip);
+ if (merged) {
+ // create a merge commit and a reference to track the merge commit
+ ObjectId treeId = merger.getResultTreeId();
+ ObjectInserter odi = repository.newObjectInserter();
+ try {
+ // Create a commit object
+ CommitBuilder commitBuilder = new CommitBuilder();
+ commitBuilder.setCommitter(committer);
+ commitBuilder.setAuthor(committer);
+ commitBuilder.setEncoding(Constants.CHARSET);
+ if (StringUtils.isEmpty(message)) {
+ message = MessageFormat.format("merge {0} into {1}", srcTip.getName(), branchTip.getName());
+ }
+ commitBuilder.setMessage(message);
+ commitBuilder.setParentIds(branchTip.getId(), srcTip.getId());
+ commitBuilder.setTreeId(treeId);
+
+ // Insert the merge commit into the repository
+ ObjectId mergeCommitId = odi.insert(commitBuilder);
+ odi.flush();
+
+ // set the merge ref to the merge commit
+ RevCommit mergeCommit = revWalk.parseCommit(mergeCommitId);
+ RefUpdate mergeRefUpdate = repository.updateRef(toBranch);
+ mergeRefUpdate.setNewObjectId(mergeCommitId);
+ mergeRefUpdate.setRefLogMessage("commit: " + mergeCommit.getShortMessage(), false);
+ RefUpdate.Result rc = mergeRefUpdate.update();
+ switch (rc) {
+ case FAST_FORWARD:
+ // successful, clean merge
break;
- default:
- throw new GitBlitException(MessageFormat.format("Unexpected result \"{0}\" when merging commit {1} into {2} in {3}",
- rc.name(), srcTip.getName(), branchTip.getName(), repository.getDirectory()));
- }
-
- // return the merge commit id
- return new MergeResult(MergeStatus.MERGED, mergeCommitId.getName());
- } finally {
- odi.release();
- }
- }
- } catch (IOException e) {
- LOGGER.error("Failed to merge", e);
+ default:
+ throw new GitBlitException(MessageFormat.format("Unexpected result \"{0}\" when merging commit {1} into {2} in {3}",
+ rc.name(), srcTip.getName(), branchTip.getName(), repository.getDirectory()));
+ }
+
+ // return the merge commit id
+ return new MergeResult(MergeStatus.MERGED, mergeCommitId.getName());
+ } finally {
+ odi.close();
+ }
+ }
+ } catch (IOException e) {
+ LOGGER.error("Failed to merge", e);
} finally {
- if (revWalk != null) {
- revWalk.release();
- }
- }
- return new MergeResult(MergeStatus.FAILED, null);
- }
+ if (revWalk != null) {
+ revWalk.close();
+ }
+ }
+ return new MergeResult(MergeStatus.FAILED, null);
+ }
+
+
+ /**
+ * Returns the LFS URL for the given oid
+ * Currently assumes that the Gitblit Filestore is used
+ *
+ * @param baseURL
+ * @param repository name
+ * @param oid of lfs item
+ * @return the lfs item URL
+ */
+ public static String getLfsRepositoryUrl(String baseURL, String repositoryName, String oid) {
+
+ if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
+ baseURL = baseURL.substring(0, baseURL.length() - 1);
+ }
+
+ return baseURL + com.gitblit.Constants.R_PATH
+ + repositoryName + "/"
+ + com.gitblit.Constants.R_LFS
+ + "objects/" + oid;
+
+ }
+
+ /**
+ * Returns all tree entries that do not match the ignore paths.
+ *
+ * @param db
+ * @param ignorePaths
+ * @param dcBuilder
+ * @throws IOException
+ */
+ public static List<DirCacheEntry> getTreeEntries(Repository db, String branch, Collection<String> ignorePaths) throws IOException {
+ List<DirCacheEntry> list = new ArrayList<DirCacheEntry>();
+ TreeWalk tw = null;
+ try {
+ ObjectId treeId = db.resolve(branch + "^{tree}");
+ if (treeId == null) {
+ // branch does not exist yet
+ return list;
+ }
+ tw = new TreeWalk(db);
+ int hIdx = tw.addTree(treeId);
+ tw.setRecursive(true);
+
+ while (tw.next()) {
+ String path = tw.getPathString();
+ CanonicalTreeParser hTree = null;
+ if (hIdx != -1) {
+ hTree = tw.getTree(hIdx, CanonicalTreeParser.class);
+ }
+ if (!ignorePaths.contains(path)) {
+ // add all other tree entries
+ if (hTree != null) {
+ final DirCacheEntry entry = new DirCacheEntry(path);
+ entry.setObjectId(hTree.getEntryObjectId());
+ entry.setFileMode(hTree.getEntryFileMode());
+ list.add(entry);
+ }
+ }
+ }
+ } finally {
+ if (tw != null) {
+ tw.close();
+ }
+ }
+ return list;
+ }
+
+ public static boolean commitIndex(Repository db, String branch, DirCache index,
+ ObjectId parentId, boolean forceCommit,
+ String author, String authorEmail, String message) throws IOException, ConcurrentRefUpdateException {
+ boolean success = false;
+
+ ObjectId headId = db.resolve(branch + "^{commit}");
+ ObjectId baseId = parentId;
+ if (baseId == null || headId == null) { return false; }
+
+ ObjectInserter odi = db.newObjectInserter();
+ try {
+ // Create the in-memory index of the new/updated ticket
+ ObjectId indexTreeId = index.writeTree(odi);
+
+ // Create a commit object
+ PersonIdent ident = new PersonIdent(author, authorEmail);
+
+ if (forceCommit == false) {
+ ThreeWayMerger merger = MergeStrategy.RECURSIVE.newMerger(db, true);
+ merger.setObjectInserter(odi);
+ merger.setBase(baseId);
+ boolean mergeSuccess = merger.merge(indexTreeId, headId);
+
+ if (mergeSuccess) {
+ indexTreeId = merger.getResultTreeId();
+ } else {
+ //Manual merge required
+ return false;
+ }
+ }
+
+ CommitBuilder commit = new CommitBuilder();
+ commit.setAuthor(ident);
+ commit.setCommitter(ident);
+ commit.setEncoding(com.gitblit.Constants.ENCODING);
+ commit.setMessage(message);
+ commit.setParentId(headId);
+ commit.setTreeId(indexTreeId);
+
+ // Insert the commit into the repository
+ ObjectId commitId = odi.insert(commit);
+ odi.flush();
+
+ RevWalk revWalk = new RevWalk(db);
+ try {
+ RevCommit revCommit = revWalk.parseCommit(commitId);
+ RefUpdate ru = db.updateRef(branch);
+ ru.setForceUpdate(forceCommit);
+ ru.setNewObjectId(commitId);
+ ru.setExpectedOldObjectId(headId);
+ ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
+ Result rc = ru.update();
+
+ switch (rc) {
+ case NEW:
+ case FORCED:
+ case FAST_FORWARD:
+ success = true;
+ break;
+ case REJECTED:
+ case LOCK_FAILURE:
+ throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD,
+ ru.getRef(), rc);
+ default:
+ throw new JGitInternalException(MessageFormat.format(
+ JGitText.get().updatingRefFailed, branch, commitId.toString(),
+ rc));
+ }
+ } finally {
+ revWalk.close();
+ }
+ } finally {
+ odi.close();
+ }
+ return success;
+ }
+
+ /**
+ * Returns true if the commit identified by commitId is at the tip of it's branch.
+ *
+ * @param repository
+ * @param commitId
+ * @return true if the given commit is the tip
+ */
+ public static boolean isTip(Repository repository, String commitId) {
+ try {
+ RefModel tip = getBranch(repository, commitId);
+ return (tip != null);
+ } catch (Exception e) {
+ LOGGER.error("Failed to determine isTip", e);
+ }
+ return false;
+ }
+
}
diff --git a/src/main/java/com/gitblit/utils/JSoupXssFilter.java b/src/main/java/com/gitblit/utils/JSoupXssFilter.java
index 5ab7953..aec2241 100644
--- a/src/main/java/com/gitblit/utils/JSoupXssFilter.java
+++ b/src/main/java/com/gitblit/utils/JSoupXssFilter.java
@@ -20,18 +20,23 @@
import org.jsoup.safety.Cleaner;
import org.jsoup.safety.Whitelist;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
/**
* Implementation of an XSS filter based on JSoup.
*
* @author James Moger
*
*/
+@Singleton
public class JSoupXssFilter implements XssFilter {
private final Cleaner none;
private final Cleaner relaxed;
+ @Inject
public JSoupXssFilter() {
none = new Cleaner(Whitelist.none());
relaxed = new Cleaner(getRelaxedWhiteList());
diff --git a/src/main/java/com/gitblit/utils/JsonUtils.java b/src/main/java/com/gitblit/utils/JsonUtils.java
index be7148c..f389776 100644
--- a/src/main/java/com/gitblit/utils/JsonUtils.java
+++ b/src/main/java/com/gitblit/utils/JsonUtils.java
@@ -46,6 +46,7 @@
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
@@ -79,23 +80,29 @@
/**
* Convert a json string to an object of the specified type.
- *
+ *
* @param json
* @param clazz
- * @return an object
+ * @return the deserialized object
+ * @throws JsonParseException
+ * @throws JsonSyntaxException
*/
- public static <X> X fromJsonString(String json, Class<X> clazz) {
+ public static <X> X fromJsonString(String json, Class<X> clazz) throws JsonParseException,
+ JsonSyntaxException {
return gson().fromJson(json, clazz);
}
/**
* Convert a json string to an object of the specified type.
- *
+ *
* @param json
- * @param clazz
- * @return an object
+ * @param type
+ * @return the deserialized object
+ * @throws JsonParseException
+ * @throws JsonSyntaxException
*/
- public static <X> X fromJsonString(String json, Type type) {
+ public static <X> X fromJsonString(String json, Type type) throws JsonParseException,
+ JsonSyntaxException {
return gson().fromJson(json, type);
}
diff --git a/src/main/java/com/gitblit/utils/MarkdownUtils.java b/src/main/java/com/gitblit/utils/MarkdownUtils.java
index 2ebfdb2..e0c9dd4 100644
--- a/src/main/java/com/gitblit/utils/MarkdownUtils.java
+++ b/src/main/java/com/gitblit/utils/MarkdownUtils.java
@@ -16,6 +16,7 @@
package com.gitblit.utils;
import static org.pegdown.Extensions.ALL;
+import static org.pegdown.Extensions.ANCHORLINKS;
import static org.pegdown.Extensions.SMARTYPANTS;
import java.io.IOException;
@@ -76,7 +77,7 @@
*/
public static String transformMarkdown(String markdown, LinkRenderer linkRenderer) {
try {
- PegDownProcessor pd = new PegDownProcessor(ALL & ~SMARTYPANTS);
+ PegDownProcessor pd = new PegDownProcessor(ALL & ~SMARTYPANTS & ~ANCHORLINKS);
RootNode astRoot = pd.parseMarkdown(markdown.toCharArray());
return new WorkaroundHtmlSerializer(linkRenderer == null ? new LinkRenderer() : linkRenderer).toHtml(astRoot);
} catch (ParsingTimeoutException e) {
diff --git a/src/main/java/com/gitblit/utils/MetricUtils.java b/src/main/java/com/gitblit/utils/MetricUtils.java
index 4703102..62427e6 100644
--- a/src/main/java/com/gitblit/utils/MetricUtils.java
+++ b/src/main/java/com/gitblit/utils/MetricUtils.java
@@ -136,7 +136,7 @@
Iterable<RevCommit> revlog = revWalk;
for (RevCommit rev : revlog) {
- Date d = JGitUtils.getCommitDate(rev);
+ Date d = JGitUtils.getAuthorDate(rev);
String p = df.format(d);
if (!metricMap.containsKey(p)) {
metricMap.put(p, new Metric(p));
diff --git a/src/main/java/com/gitblit/utils/PathUtils.java b/src/main/java/com/gitblit/utils/PathUtils.java
new file mode 100644
index 0000000..a3c7d8d
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/PathUtils.java
@@ -0,0 +1,92 @@
+package com.gitblit.utils;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+
+import java.util.*;
+
+/**
+ * Utils for handling path strings
+ *
+ */
+public class PathUtils {
+
+ private PathUtils() {}
+
+ /**
+ * Compress paths containing no files
+ *
+ * @param paths lines from `git ls-tree -r --name-only ${branch}`
+ * @return compressed paths
+ */
+ public static List<String> compressPaths(final Iterable<String> paths) {
+
+ ArrayList<String> pathList = new ArrayList<>();
+ Map<String, List<String[]>> folderRoots = new LinkedHashMap<>();
+
+ for (String s: paths) {
+ String[] components = s.split("/");
+
+ // File in current directory
+ if (components.length == 1) {
+ pathList.add(components[0]);
+
+ // Directory path
+ } else {
+ List<String[]> rootedPaths = folderRoots.get(components[0]);
+ if (rootedPaths == null) {
+ rootedPaths = new ArrayList<>();
+ }
+ rootedPaths.add(components);
+ folderRoots.put(components[0], rootedPaths);
+ }
+ }
+
+ for (String folder: folderRoots.keySet()) {
+ List<String[]> matchingPaths = folderRoots.get(folder);
+
+ if (matchingPaths.size() == 1) {
+ pathList.add(toStringPath(matchingPaths.get(0)));
+ } else {
+ pathList.add(longestCommonSequence(matchingPaths));
+ }
+ }
+ return pathList;
+ }
+
+ /**
+ * Get last path component
+ *
+ *
+ * @param path string path separated by slashes
+ * @return rightmost entry
+ */
+ public static String getLastPathComponent(final String path) {
+ return Iterables.getLast(Splitter.on("/").omitEmptyStrings().split(path), path);
+ }
+
+ private static String toStringPath(final String[] pathComponents) {
+ List<String> tmp = Arrays.asList(pathComponents);
+ return Joiner.on('/').join(tmp.subList(0,tmp.size()-1)) + '/';
+ }
+
+
+ private static String longestCommonSequence(final List<String[]> paths) {
+
+ StringBuilder path = new StringBuilder();
+
+ for (int i = 0; i < paths.get(0).length; i++) {
+ String current = paths.get(0)[i];
+ for (int j = 1; j < paths.size(); j++) {
+ if (!current.equals(paths.get(j)[i])) {
+ return path.toString();
+ }
+ }
+ path.append(current);
+ path.append('/');
+ }
+ return path.toString();
+ }
+
+}
diff --git a/src/main/java/com/gitblit/utils/RefLogUtils.java b/src/main/java/com/gitblit/utils/RefLogUtils.java
index f08c99e..355c120 100644
--- a/src/main/java/com/gitblit/utils/RefLogUtils.java
+++ b/src/main/java/com/gitblit/utils/RefLogUtils.java
@@ -294,10 +294,10 @@
rc));
}
} finally {
- revWalk.release();
+ revWalk.close();
}
} finally {
- odi.release();
+ odi.close();
}
} catch (Throwable t) {
error(t, repository, "Failed to commit reflog entry to {0}");
@@ -395,12 +395,12 @@
}
// release the treewalk
- treeWalk.release();
+ treeWalk.close();
// finish temporary in-core index used for this commit
dcBuilder.finish();
} finally {
- inserter.release();
+ inserter.close();
}
return inCoreIndex;
}
diff --git a/src/main/java/com/gitblit/utils/ResettableByteArrayOutputStream.java b/src/main/java/com/gitblit/utils/ResettableByteArrayOutputStream.java
new file mode 100644
index 0000000..7df0693
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/ResettableByteArrayOutputStream.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2014 Tom <tw201207@gmail.com>
+//
+// 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.
+package com.gitblit.utils;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * A {@link ByteArrayOutputStream} that can be reset to a specified position.
+ *
+ * @author Tom <tw201207@gmail.com>
+ */
+public class ResettableByteArrayOutputStream extends ByteArrayOutputStream {
+
+ /**
+ * Reset the stream to the given position. If {@code mark} is <= 0, see {@link #reset()}.
+ * A no-op if the stream contains less than {@code mark} bytes. Otherwise, resets the
+ * current writing position to {@code mark}. Previously allocated buffer space will be
+ * reused in subsequent writes.
+ *
+ * @param mark
+ * to set the current writing position to.
+ */
+ public synchronized void resetTo(int mark) {
+ if (mark <= 0) {
+ reset();
+ } else if (mark < count) {
+ count = mark;
+ }
+ }
+
+}
diff --git a/src/main/java/com/gitblit/utils/StringUtils.java b/src/main/java/com/gitblit/utils/StringUtils.java
index 087de54..643c52c 100644
--- a/src/main/java/com/gitblit/utils/StringUtils.java
+++ b/src/main/java/com/gitblit/utils/StringUtils.java
@@ -79,6 +79,19 @@
* @return plain text escaped for html
*/
public static String escapeForHtml(String inStr, boolean changeSpace) {
+ return escapeForHtml(inStr, changeSpace, 4);
+ }
+
+ /**
+ * Prepare text for html presentation. Replace sensitive characters with
+ * html entities.
+ *
+ * @param inStr
+ * @param changeSpace
+ * @param tabLength
+ * @return plain text escaped for html
+ */
+ public static String escapeForHtml(String inStr, boolean changeSpace, int tabLength) {
StringBuilder retStr = new StringBuilder();
int i = 0;
while (i < inStr.length()) {
@@ -93,7 +106,9 @@
} else if (changeSpace && inStr.charAt(i) == ' ') {
retStr.append(" ");
} else if (changeSpace && inStr.charAt(i) == '\t') {
- retStr.append(" ");
+ for (int j = 0; j < tabLength; j++) {
+ retStr.append(" ");
+ }
} else {
retStr.append(inStr.charAt(i));
}
diff --git a/src/main/java/com/gitblit/utils/X509Utils.java b/src/main/java/com/gitblit/utils/X509Utils.java
index f0c1b9d..a2650be 100644
--- a/src/main/java/com/gitblit/utils/X509Utils.java
+++ b/src/main/java/com/gitblit/utils/X509Utils.java
@@ -61,6 +61,7 @@
import java.util.zip.ZipOutputStream;
import javax.crypto.Cipher;
+import javax.naming.ldap.LdapName;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
@@ -80,7 +81,10 @@
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.PrincipalUtil;
import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.openssl.PEMEncryptor;
import org.bouncycastle.openssl.PEMWriter;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
@@ -883,8 +887,11 @@
if (pemFile.exists()) {
pemFile.delete();
}
- PEMWriter pemWriter = new PEMWriter(new FileWriter(pemFile));
- pemWriter.writeObject(pair.getPrivate(), "DES-EDE3-CBC", clientMetadata.password.toCharArray(), new SecureRandom());
+ JcePEMEncryptorBuilder builder = new JcePEMEncryptorBuilder("DES-EDE3-CBC");
+ builder.setSecureRandom(new SecureRandom());
+ PEMEncryptor pemEncryptor = builder.build(clientMetadata.password.toCharArray());
+ JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(pemFile));
+ pemWriter.writeObject(pair.getPrivate(), pemEncryptor);
pemWriter.writeObject(userCert);
pemWriter.writeObject(caCert);
pemWriter.flush();
@@ -1111,17 +1118,18 @@
}
public static X509Metadata getMetadata(X509Certificate cert) {
- // manually split DN into OID components
- // this is instead of parsing with LdapName which:
- // (1) I don't trust the order of values
- // (2) it filters out values like EMAILADDRESS
- String dn = cert.getSubjectDN().getName();
Map<String, String> oids = new HashMap<String, String>();
- for (String kvp : dn.split(",")) {
- String [] val = kvp.trim().split("=");
- String oid = val[0].toUpperCase().trim();
- String data = val[1].trim();
- oids.put(oid, data);
+ try {
+ String dn = cert.getSubjectDN().getName();
+ LdapName ldapName = new LdapName(dn);
+ for (int i = 0; i < ldapName.size(); i++) {
+ String [] val = ldapName.get(i).trim().split("=", 2);
+ String oid = val[0].toUpperCase().trim();
+ String data = val[1].trim();
+ oids.put(oid, data);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
X509Metadata metadata = new X509Metadata(oids.get("CN"), "whocares");
diff --git a/src/main/java/com/gitblit/utils/cli/CmdLineParser.java b/src/main/java/com/gitblit/utils/cli/CmdLineParser.java
index e698eb5..09ae836 100644
--- a/src/main/java/com/gitblit/utils/cli/CmdLineParser.java
+++ b/src/main/java/com/gitblit/utils/cli/CmdLineParser.java
@@ -429,5 +429,15 @@
public boolean isMultiValued() {
return false;
}
+
+ @Override
+ public boolean help() {
+ return true;
+ }
+
+ @Override
+ public String[] forbids() {
+ return new String [0];
+ }
}
}
diff --git a/src/main/java/com/gitblit/wicket/FilestoreUI.java b/src/main/java/com/gitblit/wicket/FilestoreUI.java
new file mode 100644
index 0000000..8837ba1
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/FilestoreUI.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.wicket;
+
+import org.apache.wicket.markup.html.basic.Label;
+
+import com.gitblit.models.FilestoreModel;
+import com.gitblit.models.FilestoreModel.Status;
+
+/**
+ * Common filestore ui methods and classes.
+ *
+ * @author Paul Martin
+ *
+ */
+public class FilestoreUI {
+
+ public static Label getStatusIcon(String wicketId, FilestoreModel item) {
+ return getStatusIcon(wicketId, item.getStatus());
+ }
+
+ public static Label getStatusIcon(String wicketId, Status status) {
+ Label label = new Label(wicketId);
+
+ switch (status) {
+ case Upload_Pending:
+ WicketUtils.setCssClass(label, "fa fa-spinner fa-fw file-negative");
+ break;
+ case Upload_In_Progress:
+ WicketUtils.setCssClass(label, "fa fa-spinner fa-spin fa-fw file-positive");
+ break;
+ case Available:
+ WicketUtils.setCssClass(label, "fa fa-check fa-fw file-positive");
+ break;
+ case Deleted:
+ WicketUtils.setCssClass(label, "fa fa-ban fa-fw file-negative");
+ break;
+ case Unavailable:
+ WicketUtils.setCssClass(label, "fa fa-times fa-fw file-negative");
+ break;
+ default:
+ WicketUtils.setCssClass(label, "fa fa-exclamation-triangle fa-fw file-negative");
+ }
+ WicketUtils.setHtmlTooltip(label, status.toString());
+
+ return label;
+ }
+
+}
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
index d19630c..c10d887 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
@@ -37,12 +37,14 @@
import com.gitblit.extensions.GitblitWicketPlugin;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IFilestoreManager;
import com.gitblit.manager.IGitblit;
import com.gitblit.manager.INotificationManager;
import com.gitblit.manager.IPluginManager;
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IServicesManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.tickets.ITicketService;
import com.gitblit.transport.ssh.IPublicKeyManager;
@@ -57,11 +59,13 @@
import com.gitblit.wicket.pages.ComparePage;
import com.gitblit.wicket.pages.DocPage;
import com.gitblit.wicket.pages.DocsPage;
+import com.gitblit.wicket.pages.EditFilePage;
import com.gitblit.wicket.pages.EditMilestonePage;
import com.gitblit.wicket.pages.EditRepositoryPage;
import com.gitblit.wicket.pages.EditTicketPage;
import com.gitblit.wicket.pages.ExportTicketPage;
import com.gitblit.wicket.pages.FederationRegistrationPage;
+import com.gitblit.wicket.pages.FilestorePage;
import com.gitblit.wicket.pages.ForkPage;
import com.gitblit.wicket.pages.ForksPage;
import com.gitblit.wicket.pages.GitSearchPage;
@@ -90,7 +94,11 @@
import com.gitblit.wicket.pages.TreePage;
import com.gitblit.wicket.pages.UserPage;
import com.gitblit.wicket.pages.UsersPage;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+@Singleton
public class GitBlitWebApp extends WebApplication implements GitblitWicketApp {
private final Class<? extends WebPage> homePageClass = MyDashboardPage.class;
@@ -99,6 +107,10 @@
private final Map<String, CacheControl> cacheablePages = new HashMap<String, CacheControl>();
+ private final Provider<IPublicKeyManager> publicKeyManagerProvider;
+
+ private final Provider<ITicketService> ticketServiceProvider;
+
private final IStoredSettings settings;
private final XssFilter xssFilter;
@@ -113,8 +125,6 @@
private final IAuthenticationManager authenticationManager;
- private final IPublicKeyManager publicKeyManager;
-
private final IRepositoryManager repositoryManager;
private final IProjectManager projectManager;
@@ -123,19 +133,29 @@
private final IGitblit gitblit;
+ private final IServicesManager services;
+
+ private final IFilestoreManager filestoreManager;
+
+ @Inject
public GitBlitWebApp(
+ Provider<IPublicKeyManager> publicKeyManagerProvider,
+ Provider<ITicketService> ticketServiceProvider,
IRuntimeManager runtimeManager,
IPluginManager pluginManager,
INotificationManager notificationManager,
IUserManager userManager,
IAuthenticationManager authenticationManager,
- IPublicKeyManager publicKeyManager,
IRepositoryManager repositoryManager,
IProjectManager projectManager,
IFederationManager federationManager,
- IGitblit gitblit) {
+ IGitblit gitblit,
+ IServicesManager services,
+ IFilestoreManager filestoreManager) {
super();
+ this.publicKeyManagerProvider = publicKeyManagerProvider;
+ this.ticketServiceProvider = ticketServiceProvider;
this.settings = runtimeManager.getSettings();
this.xssFilter = runtimeManager.getXssFilter();
this.runtimeManager = runtimeManager;
@@ -143,11 +163,12 @@
this.notificationManager = notificationManager;
this.userManager = userManager;
this.authenticationManager = authenticationManager;
- this.publicKeyManager = publicKeyManager;
this.repositoryManager = repositoryManager;
this.projectManager = projectManager;
this.federationManager = federationManager;
this.gitblit = gitblit;
+ this.services = services;
+ this.filestoreManager = filestoreManager;
}
@Override
@@ -210,6 +231,7 @@
// setup the markup document urls
mount("/docs", DocsPage.class, "r", "h");
mount("/doc", DocPage.class, "r", "h", "f");
+ mount("/editfile", EditFilePage.class, "r", "h", "f");
// federation urls
mount("/proposal", ReviewProposalPage.class, "t");
@@ -224,6 +246,9 @@
mount("/user", UserPage.class, "user");
mount("/forks", ForksPage.class, "r");
mount("/fork", ForkPage.class, "r");
+
+ // filestore URL
+ mount("/filestore", FilestorePage.class);
// allow started Wicket plugins to initialize
for (PluginWrapper pluginWrapper : pluginManager.getPlugins()) {
@@ -392,7 +417,7 @@
*/
@Override
public IPublicKeyManager keys() {
- return publicKeyManager;
+ return publicKeyManagerProvider.get();
}
/* (non-Javadoc)
@@ -428,11 +453,19 @@
}
/* (non-Javadoc)
+ * @see com.gitblit.wicket.Webapp#services()
+ */
+ @Override
+ public IServicesManager services() {
+ return services;
+ }
+
+ /* (non-Javadoc)
* @see com.gitblit.wicket.Webapp#tickets()
*/
@Override
public ITicketService tickets() {
- return gitblit.getTicketService();
+ return ticketServiceProvider.get();
}
/* (non-Javadoc)
@@ -454,4 +487,9 @@
public static GitBlitWebApp get() {
return (GitBlitWebApp) WebApplication.get();
}
+
+ @Override
+ public IFilestoreManager filestore() {
+ return filestoreManager;
+ }
}
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
index eb92e2d..cee7eab 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -561,6 +561,7 @@
gb.enhancementTickets = enhancements
gb.taskTickets = tasks
gb.questionTickets = questions
+gb.maintenanceTickets = maintenance
gb.requestTickets = enhancements & tasks
gb.yourCreatedTickets = created by you
gb.yourWatchedTickets = watched by you
@@ -741,4 +742,40 @@
gb.permission = Permission
gb.sshKeyPermissionDescription = Specify the access permission for the SSH key
gb.transportPreference = Transport Preference
-gb.transportPreferenceDescription = Set the transport that you prefer to use for cloning
\ No newline at end of file
+gb.transportPreferenceDescription = Set the transport that you prefer to use for cloning
+gb.priority = priority
+gb.severity = severity
+gb.sortHighestPriority = highest priority
+gb.sortLowestPriority = lowest priority
+gb.sortHighestSeverity = highest severity
+gb.sortLowestSeverity = lowest severity
+gb.missingIntegrationBranchMore = The target integration branch does not exist in the repository!
+gb.diffDeletedFileSkipped = (deleted)
+gb.diffFileDiffTooLarge = Diff too large
+gb.diffNewFile = New file
+gb.diffDeletedFile = File was deleted
+gb.diffRenamedFile = File was renamed from {0}
+gb.diffCopiedFile = File was copied from {0}
+gb.diffTruncated = Diff truncated after the above file
+gb.opacityAdjust = Adjust opacity
+gb.blinkComparator = Blink comparator
+gb.imgdiffSubtract = Subtract (black = identical)
+gb.deleteRepositoryHeader = Delete Repository
+gb.deleteRepositoryDescription = Deleted repositories will be unrecoverable.
+gb.show_whitespace = show whitespace
+gb.ignore_whitespace = ignore whitespace
+gb.allRepositories = All Repositories
+gb.oid = object id
+gb.filestore = filestore
+gb.filestoreStats = Filestore contains {0} files with a total size of {1}. ({2} remaining)
+gb.statusChangedOn = status changed on
+gb.statusChangedBy = status changed by
+gb.filestoreHelp = How to use the Filestore?
+gb.editFile = edit file
+gb.continueEditing = Continue Editing
+gb.commitChanges = Commit Changes
+gb.fileNotMergeable = Unable to commit {0}. This file can not be automatically merged.
+gb.fileCommitted = Successfully committed {0}.
+gb.deletePatchset = Delete Patchset {0}
+gb.deletePatchsetSuccess = Deleted Patchset {0}.
+gb.deletePatchsetFailure = Error deleting Patchset {0}.
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties
index 3ec330b..eca3fd2 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties
@@ -743,3 +743,13 @@
gb.sshKeyPermissionDescription = Geben Sie die Zugriffberechtigung f\u00fcr den SSH Key an
gb.transportPreference = \u00dcbertragungseinstellungen
gb.transportPreferenceDescription = Geben Sie die \u00dcbertragungsart an, die Sie f\u00fcr das Klonen bevorzugen
+gb.diffDeletedFileSkipped = (gel\u00f6scht)
+gb.diffFileDiffTooLarge = Zu viele \u00c4nderungen; Diff wird nicht angezeigt
+gb.diffNewFile = Neue Datei
+gb.diffDeletedFile = Datei wurde gel\u00f6scht
+gb.diffRenamedFile = Datei umbenannt von {0}
+gb.diffCopiedFile = Datei kopiert von {0}
+gb.diffTruncated = Diff nach obiger Datei abgeschnitten
+gb.opacityAdjust = Transparenz
+gb.blinkComparator = Blinkkomparator
+gb.imgdiffSubtract = Pixeldifferenz (schwarz = identisch)
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties
index b888bee..d479b3d 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties
@@ -672,3 +672,13 @@
gb.mergeToDescription = branche d'int\u00e9gration par d\u00e9faut pour fusionner les correctifs li\u00e9s aux tickets
gb.myTickets = mes tickets
gb.yourAssignedTickets = dont vous \u00eates responsable
+gb.diffDeletedFileSkipped = (effac\u00e9)
+gb.diffFileDiffTooLarge = Trop de diff\u00e9rences, affichage supprim\u00e9e
+gb.diffNewFile = Nouveau fichier
+gb.diffDeletedFile = Fichier a \u00e9t\u00e9 effac\u00e9
+gb.diffRenamedFile = Fichier renomm\u00e9 de {0}
+gb.diffCopiedFile = Fichier copi\u00e9 de {0}
+gb.diffTruncated = Affichage de diff\u00e9rences supprim\u00e9e apr\u00e8s le fichier ci-dessus
+gb.opacityAdjust = ajuster l'opacit\u00e9
+gb.blinkComparator = Comparateur \u00e0 clignotement
+gb.imgdiffSubtract = Diff\u00e9rence (noir = identique)
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties
new file mode 100644
index 0000000..16a9c86
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties
@@ -0,0 +1,772 @@
+#!
+#! created/edited by Popeye version 0.54 (popeye.sourceforge.net)
+#! encoding:ISO-8859-1
+gb.about = \u95dc\u65bc
+gb.acceptNewPatchsets = \u5141\u8a31\u88dc\u4e01
+gb.acceptNewPatchsetsDescription = \u63a5\u53d7\u5230\u7248\u672c\u5009\u9032\u884c\u4fee\u88dc\u52d5\u4f5c
+gb.acceptNewTickets = \u5141\u8a31\u5efa\u7acb\u4efb\u52d9\u55ae
+gb.acceptNewTicketsDescription = \u5141\u8a31\u65b0\u589e"\u81ed\u87f2","\u512a\u5316","\u4efb\u52d9"\u5404\u985e\u578b\u4efb\u52d9\u55ae
+gb.accessDenied = \u62d2\u7d55\u5b58\u53d6
+gb.accessLevel = \u5b58\u53d6\u7b49\u7d1a
+gb.accessPermissions = \u5b58\u53d6\u6b0a\u9650
+gb.accessPermissionsDescription = restrict access by users and teams
+gb.accessPermissionsForTeamDescription = set team members and grant access to specific restricted repositories
+gb.accessPermissionsForUserDescription = set team memberships or grant access to specific restricted repositories
+gb.accessPolicy = \u5b58\u53d6\u653f\u7b56
+gb.accessPolicyDescription = \u9078\u64c7\u7528\u4f86\u63a7\u5236\u6587\u4ef6\u5eab\u7684\u5b58\u53d6\u653f\u7b56\u4ee5\u53ca\u6b0a\u9650\u8a2d\u5b9a
+gb.accessRestriction = \u9650\u5236\u5b58\u53d6
+gb.accountPreferences = \u5e33\u865f\u8a2d\u5b9a
+gb.accountPreferencesDescription = \u8a2d\u5b9a\u5e33\u865f\u9810\u8a2d\u503c
+gb.action = \u52d5\u4f5c
+gb.active = \u6d3b\u52d5
+gb.activeAuthors = \u6d3b\u8e8d\u7528\u6236
+gb.activeRepositories = \u6d3b\u8e8d\u7248\u672c\u5eab
+gb.activity = \u6d3b\u52d5
+gb.add = \u65b0\u589e
+gb.addComment = \u65b0\u589e\u8a3b\u89e3
+gb.addedNCommits = {0}\u500b\u6a94\u6848\u63d0\u4ea4\u5b8c\u7562
+gb.addedOneCommit = \u63d0\u4ea41\u500b\u6a94\u6848
+gb.addition = addition
+gb.addSshKey = \u65b0\u589e SSH Key
+gb.administration = \u7ba1\u7406\u6b0a\u9650
+gb.administrator = \u7ba1\u7406\u54e1
+gb.administratorPermission = Gitblit \u7ba1\u7406\u54e1
+gb.affiliationChanged = affiliation changed
+gb.age = \u6642\u9593
+gb.all = \u5168\u90e8
+gb.allBranches = \u6240\u6709\u5206\u652f
+gb.allowAuthenticatedDescription = \u6279\u51c6 RW+ \u6b0a\u9650\u7d66\u4e88\u5c08\u6848\u6210\u54e1
+gb.allowForks = \u5141\u8a31\u5efa\u7acb\u5206\u652f(forks)
+gb.allowForksDescription = \u5141\u8a31\u5df2\u6388\u6b0a\u7684\u4f7f\u7528\u8005\u5f9e\u6587\u4ef6\u5eab\u5efa\u7acb\u5206\u652f(fork)
+gb.allowNamedDescription = grant fine-grained permissions to named users or teams
+gb.allProjects = \u5168\u90e8\u7fa4\u7d44
+gb.allTags = \u6240\u6709\u6a19\u7c64
+gb.anonymousCanNotPropose = \u533f\u540d\u8005\u4e0d\u80fd\u63d0\u4f9b\u88dc\u4e01
+gb.anonymousPolicy = \u533f\u540d\u72c0\u614b\u53ef\u4ee5View, Clone\u8207Push
+gb.anonymousPolicyDescription = \u4efb\u4f55\u4eba\u53ef\u6aa2\u8996,\u8907\u88fd(clone)\u8207\u63a8\u9001(push)\u6587\u4ef6\u5230\u6587\u4ef6\u5eab
+gb.anonymousUser= \u533f\u540d\u72c0\u614b
+gb.any = \u4efb\u4f55
+gb.approve = \u901a\u904e
+gb.at = at
+gb.attributes = \u5c6c\u6027
+gb.authenticatedPushPolicy = Restrict Push (Authenticated)
+gb.authenticatedPushPolicyDescription = \u4efb\u4f55\u4eba\u53ef\u4ee5\u6aa2\u8996\u8207\u8907\u88fd(clone).\u6240\u6709\u6587\u4ef6\u5eab\u6210\u54e1\u7686\u6709RW+\u8207\u63a8\u9001(push)\u529f\u80fd.
+gb.author = \u4f5c\u8005
+gb.authored = \u5df2\u6388\u6b0a
+gb.authorizationControl = \u6388\u6b0a\u7ba1\u63a7
+gb.available = \u53ef\u7528
+gb.blame = \u7a76\u67e5
+gb.blinkComparator = Blink comparator
+gb.blob = \u5340\u584a
+gb.body = body
+gb.bootDate = \u555f\u52d5\u65e5
+gb.branch = \u5206\u652f
+gb.branches = \u5206\u652f
+gb.branchStats = \u9019\u500b\u5206\u652f{2}\u6709{0}\u500b\u63d0\u4ea4\u4ee5\u53ca{1}\u500b\u6a19\u7c64
+gb.browse = \u700f\u89bd
+gb.bugTickets = \u81ed\u87f2
+gb.busyCollectingGarbage = \u62b1\u6b49,Gitblit\u6b63\u5728\u56de\u6536\u7cfb\u7d71\u8cc7\u6e90\u4e2d:{0}
+gb.byNAuthors = \u7d93\u7531{0}\u500b\u4f5c\u8005
+gb.byOneAuthor = \u7d93\u7531{0}
+gb.caCompromise = CA compromise
+gb.canAdmin = \u53ef\u7ba1\u7406
+gb.canAdminDescription = \u53ef\u7ba1\u7406Gitblit\u4f3a\u670d\u5668
+gb.cancel = \u53d6\u6d88
+gb.canCreate = \u53ef\u5efa\u7acb
+gb.canCreateDescription = \u80fd\u5920\u5efa\u7acb\u79c1\u4eba\u6587\u4ef6\u5eab
+gb.canFork = \u53ef\u5efa\u7acb\u5206\u652f(fork)
+gb.canForkDescription = \u53ef\u4ee5\u5efa\u7acb\u6587\u4ef6\u5eab\u5206\u652f(fork),\u4e26\u4e14\u8907\u88fd\u5230\u79c1\u4eba\u6587\u4ef6\u5eab\u4e2d
+gb.canNotLoadRepository = \u7121\u6cd5\u8f09\u5165\u7248\u672c\u5eab
+gb.canNotProposePatchset = \u4e0d\u80fd\u63d0\u4f9b\u88dc\u4e01
+gb.certificate = \u8b49\u66f8
+gb.certificateRevoked = \u8b49\u66f8{0,number,0} \u5df2\u7d93\u88ab\u53d6\u6d88
+gb.certificates = \u8b49\u66f8
+gb.cessationOfOperation = cessation of operation
+gb.changedFiles = \u5df2\u8b8a\u66f4\u904e\u7684\u6a94\u6848
+gb.changedStatus = changed the status
+gb.changePassword = \u4fee\u6539\u5bc6\u78bc
+gb.checkout = \u6aa2\u51fa(checkout)
+gb.checkoutStep1 = Fetch the current patchset \u2014 run this from your project directory
+gb.checkoutStep2 = \u5c07\u8a72\u88dc\u4e01\u8f49\u51fa\u5230\u65b0\u7684\u5206\u652f\u7136\u5f8c\u7528\u4f86\u6aa2\u8996
+gb.checkoutViaCommandLine = \u4f7f\u7528\u6307\u4ee4Checkout
+gb.checkoutViaCommandLineNote = \u4f60\u53ef\u4ee5\u5f9e\u4f60\u6587\u4ef6\u5eab\u4e2dcheckout\u4e00\u4efd,\u7136\u5f8c\u9032\u884c\u6e2c\u8a66
+gb.clearCache = \u6e05\u9664\u5feb\u53d6
+gb.clientCertificateBundleSent = {0}\u7684\u7528\u6236\u8b49\u66f8\u5df2\u5bc4\u767c
+gb.clientCertificateGenerated = \u6210\u529f\u7522\u751f{0}\u7684\u65b0\u8b49\u66f8
+gb.clone = \u8907\u88fd(clone)
+gb.clonePermission = {0} \u8907\u88fd(clone)
+gb.clonePolicy = Restrict Clone & Push
+gb.clonePolicyDescription = \u4efb\u4f55\u4eba\u53ef\u4ee5\u770b\u6587\u4ef6\u5eab. \u4f46\u4f60\u80fd\u5920\u8907\u88fd(clone)\u8207\u63a8\u9001(push)
+gb.cloneRestricted = authenticated clone & push
+gb.closeBrowser = \u8acb\u95dc\u9589\u700f\u89bd\u5668\u7d50\u675f\u6b64\u767b\u5165\u968e\u6bb5
+gb.closed = \u95dc\u9589
+gb.closedMilestones = \u5df2\u95dc\u9589\u7684\u91cc\u7a0b\u7891(milestones)
+gb.combinedMd5Rename = Gitblit\u4f7f\u7528md5\u65b9\u5f0f\u5c07\u5bc6\u78bc\u7de8\u78bc(\u7121\u6cd5\u9084\u539f).\u4f60\u5fc5\u9808\u8f38\u5165\u65b0\u5bc6\u78bc.
+gb.comment = \u8a3b\u89e3
+gb.commented = \u5df2\u8a3b\u89e3
+gb.comments = \u8a3b\u89e3
+gb.commit = \u63d0\u4ea4
+gb.commitActivityAuthors = \u63d0\u4ea4\u6d3b\u8e8d\u7387(\u4f7f\u7528\u8005)
+gb.commitActivityDOW = \u6bcf(\u65e5)\u9031\u63d0\u4ea4
+gb.commitActivityTrend = \u63d0\u4ea4\u8da8\u52e2\u5716
+gb.commitdiff = \u63d0\u4ea4\u5dee\u7570
+gb.commitIsNull = \u63d0\u4ea4\u5167\u5bb9\u662f\u7a7a\u7684
+gb.commitMessageRenderer = \u63d0\u4ea4\u8a0a\u606f\u5448\u73fe\u65b9\u5f0f
+gb.commitMessageRendererDescription = \u63d0\u4ea4\u8a0a\u606f\u53ef\u4ee5\u4f7f\u7528\u6587\u5b57\u6216\u662f\u6a19\u8a18\u8a9e\u8a00(markup)\u5448\u73fe
+gb.commits = \u63d0\u4ea4
+gb.commitsInPatchsetN = \u88dc\u4e01 {0} \u7684\u63d0\u4ea4
+gb.commitsTo = {0} commits to
+gb.committed = \u5df2\u63d0\u4ea4
+gb.committer = \u78ba\u8a8d\u63d0\u4ea4\u8005
+gb.compare = \u6bd4\u5c0d
+gb.compareToMergeBase = \u6bd4\u5c0d\u5f8c,\u5408\u4f75\u5230\u4e3b\u8981\u5de5\u4f5c\u5340
+gb.compareToN = \u8207{0}\u9032\u884c\u6bd4\u5c0d
+gb.completeGravatarProfile = \u5b8c\u6210Gravator.com\u4e0a\u7684\u57fa\u672c\u8cc7\u6599\u8a2d\u5b9a
+gb.confirmPassword = \u78ba\u8a8d\u5bc6\u78bc
+gb.content = \u5167\u5bb9
+gb.copyToClipboard = \u8907\u88fd\u5230\u526a\u8cbc\u677f
+gb.couldNotCreateFederationProposal = \u7121\u6cd5\u5efa\u7acb\u4e32\u9023\u7684\u5408\u4f5c\u63d0\u6848
+gb.couldNotFindFederationProposal = \u641c\u5c0b\u4e0d\u5230\u8981\u6c42\u4e32\u9023\u7684\u63d0\u6848
+gb.couldNotFindFederationRegistration = \u627e\u4e0d\u5230\u4e32\u9023\u8a3b\u518a\u55ae
+gb.couldNotFindTag = \u627e\u4e0d\u5230\u6a19\u7c64{0}
+gb.countryCode = \u570b\u5bb6\u4ee3\u78bc
+gb.create = \u5efa\u7acb
+gb.createdBy = created by
+gb.createdNewBranch = \u5efa\u7acb\u65b0\u5206\u652f
+gb.createdNewPullRequest = created pull request
+gb.createdNewTag = \u5efa\u7acb\u65b0\u6a19\u7c64
+gb.createdThisTicket = \u5df2\u958b\u7acb\u7684\u4efb\u52d9\u55ae
+gb.createFirstTicket = \u6309\u6b64\u9996\u767c\u4efb\u52d9\u55ae
+gb.createPermission = {0} (push, ref creation)
+gb.createReadme = \u5efa\u7acbREADME\u6a94\u6848
+gb.customFields = custom fields
+gb.customFieldsDescription = custom fields available to Groovy hooks
+gb.dailyActivity = \u6bcf\u65e5\u6d3b\u52d5
+gb.dashboard = \u5100\u8868\u677f
+gb.date = \u65e5\u671f
+gb.default = \u9810\u8a2d
+gb.delete = \u522a\u9664
+gb.deletedBranch = deleted branch
+gb.deletedTag = \u522a\u9664\u6a19\u7c64
+gb.deleteMilestone = \u522a\u9664\u91cc\u7a0b\u7891"{0}"?
+gb.deletePermission = {0} (push, ref creation+deletion)
+gb.deleteRepository = \u522a\u9664\u7248\u672c\u5eab"{0}"?
+gb.deleteRepositoryDescription = \u7248\u672c\u5eab\u522a\u9664\u5c07\u7121\u6cd5\u9084\u539f
+gb.deleteRepositoryHeader = \u522a\u9664\u7248\u672c\u5eab
+gb.deleteUser = \u522a\u9664\u4f7f\u7528\u8005"{0}"?
+gb.deletion = \u522a\u9664
+gb.description = \u6982\u8ff0
+gb.destinationUrl = \u50b3\u9001
+gb.destinationUrlDescription = \u50b3\u9001Gitblit\u9023\u7d50\u5230\u4f60\u7684\u5c08\u6848(proposal)
+gb.diff = \u5dee\u7570
+gb.diffCopiedFile = File was copied from {0}
+gb.diffDeletedFile = \u6a94\u6848\u5df2\u522a\u9664
+gb.diffDeletedFileSkipped = (\u522a\u9664)
+gb.diffFileDiffTooLarge = \u6a94\u6848\u592a\u5927
+gb.diffNewFile = \u6bd4\u5c0d\u65b0\u6a94\u6848
+gb.diffRenamedFile = File was renamed from {0}
+gb.diffStat = \u65b0\u589e{0}\u5217\u8207\u522a\u9664{1}\u5217
+gb.difftocurrent = \u6bd4\u5c0d\u5dee\u7570
+gb.diffTruncated = Diff truncated after the above file
+gb.disableUser = \u505c\u7528\u5e33\u6236
+gb.disableUserDescription = \u8a72\u5e33\u6236\u7121\u6cd5\u4f7f\u7528
+gb.discussion = \u8a0e\u8ad6
+gb.displayName = \u986f\u793a\u7684\u540d\u7a31
+gb.displayNameDescription = \u5e0c\u671b\u986f\u793a\u7684\u540d\u7a31
+gb.docs = \u6a94\u6848\u5340
+gb.docsWelcome1 = \u4f60\u53ef\u4ee5\u4f7f\u7528\u6a94\u6848\u5340\u5efa\u7acb\u6587\u4ef6\u5eab\u7684\u6559\u5b78\u6a94\u6848
+gb.docsWelcome2 = \u63d0\u4ea4README.md \u6216 HOME.md\u5f8c,\u518d\u958b\u59cb\u65b0\u7684\u6587\u4ef6\u5eab
+gb.doesNotExistInTree = {0}\u4e26\u6c92\u6709\u5728\u76ee\u9304{1}\u88e1\u9762
+gb.download = \u4e0b\u8f09
+gb.downloading = \u4e0b\u8f09ing
+gb.due = due
+gb.duration = \u9031\u671f
+gb.duration.days = {0}\u5929
+gb.duration.months = {0}\u6708
+gb.duration.oneDay = 1\u5929
+gb.duration.oneMonth = 1\u6708
+gb.duration.oneYear = 1\u5e74
+gb.duration.years = {0}\u5e74
+gb.edit = \u7de8\u8f2f
+gb.editMilestone = \u4fee\u6539milestone
+gb.editTicket = \u4fee\u6539\u4efb\u52d9\u55ae
+gb.editUsers = \u4fee\u6539\u5e33\u865f
+gb.effective = \u6240\u6709\u6b0a\u9650
+gb.emailAddress = \u96fb\u5b50\u90f5\u4ef6
+gb.emailAddressDescription = \u7528\u4f86\u63a5\u6536\u901a\u77e5\u7684\u4e3b\u8981\u96fb\u5b50\u90f5\u4ef6
+gb.emailCertificateBundle = \u5bc4\u767c\u7528\u6236\u7aef\u8b49\u66f8
+gb.emailMeOnMyTicketChanges = \u6211\u7684\u4efb\u52d9\u55ae\u82e5\u6709\u8b8a\u66f4,\u8acb800\u91cc\u52a0\u6025(email)\u901a\u77e5\u6211
+gb.emailMeOnMyTicketChangesDescription = \u6211\u8655\u7406\u904e\u7684\u4efb\u52d9\u55ae\u8acbemail\u901a\u77e5\u6211
+gb.empty = \u7a7a\u7684
+gb.emptyRepository = \u7a7a\u7684\u7248\u672c\u5eab
+gb.enableDocs = \u555f\u7528\u6a94\u6848\u5340
+gb.enableIncrementalPushTags = \u555f\u7528\u81ea\u52d5\u65b0\u589e\u6a19\u7c64\u529f\u80fd
+gb.enableTickets = \u555f\u7528\u4efb\u52d9\u55ae\u7cfb\u7d71
+gb.enhancementTickets = \u512a\u5316
+gb.enterKeystorePassword = \u8acb\u8f38\u5165Gitblit\u7684keystore\u5c08\u7528\u5bc6\u78bc
+gb.error = \u932f\u8aa4
+gb.errorAdministrationDisabled = \u7ba1\u7406\u6b0a\u9650\u5df2\u53d6\u6d88
+gb.errorAdminLoginRequired = \u767b\u5165\u9700\u6709\u7ba1\u7406\u6b0a\u9650
+gb.errorOnlyAdminMayCreateRepository = \u53ea\u6709\u7ba1\u7406\u8005\u80fd\u5efa\u7acb\u7248\u672c\u5eab
+gb.errorOnlyAdminOrOwnerMayEditRepository = \u53ea\u6709\u7ba1\u7406\u8005\u8207\u7248\u672c\u5eab\u64c1\u6709\u8005\u80fd\u4fee\u6539\u7248\u672c\u5eab\u5c6c\u6027
+gb.excludeFromActivity = exclude from activity page
+gb.excludeFromFederation = \u6392\u9664\u4e32\u9023
+gb.excludeFromFederationDescription = \u963b\u64cb\u5df2\u4e32\u9023\u7684Gitblit\u4f3a\u670d\u5668
+gb.excludePermission = {0} (\u6392\u9664)
+gb.exclusions = \u6392\u9664
+gb.expired = \u904e\u671f
+gb.expires = \u5230\u671f
+gb.expiring = \u5c07\u8981\u904e\u671f
+gb.export = \u532f\u51fa
+gb.extensions = \u64f4\u5145
+gb.externalPermissions = {0} access permissions are externally maintained
+gb.failedToFindAccount = \u7121\u6cd5\u641c\u5c0b\u5230\u5e33\u865f"{0}"
+gb.failedToFindCommit = Failed to find commit "{0}" in {1}\!
+gb.failedToFindGravatarProfile = \u7121\u6cd5\u627e\u5230\u5e33\u865f{0}\u7684Gravator\u8cc7\u6599
+gb.failedtoRead = \u8b80\u53d6\u5931\u6557
+gb.failedToReadMessage = Failed to read default message from {0}\!
+gb.failedToSendProposal = \u63d0\u6848\u767c\u9001\u5931\u6557\!
+gb.failedToUpdateUser = \u7121\u6cd5\u66f4\u65b0\u4f7f\u7528\u8005\u5e33\u865f
+gb.federatedRepositoryDefinitions = \u7248\u672c\u5eab\u5b9a\u7fa9
+gb.federatedSettingDefinitions = setting definitions
+gb.federatedUserDefinitions = user definitions
+gb.federateOrigin = federate the origin
+gb.federateThis = \u8207\u672c\u6587\u4ef6\u5eab\u4e32\u9023
+gb.federation = \u4e32\u9023
+gb.federationRegistration = federation registration
+gb.federationRepositoryDescription = \u8207\u5176\u4ed6gitblit\u4f3a\u670d\u5668\u5206\u4eab\u4e00\u8d77\u4f7f\u7528\u9019\u500b\u7248\u672c\u5eab
+gb.federationResults = federation pull results
+gb.federationSets = \u4e32\u9023\u7d44\u5408
+gb.federationSetsDescription = \u6b64\u6587\u4ef6\u5eab\u5c07\u5305\u542b\u65bc\u6307\u5b9a\u7684\u4e32\u9023\u7fa4\u7d44(federation sets)
+gb.federationStrategy = \u4e32\u9023\u7b56\u7565
+gb.federationStrategyDescription = \u63a7\u5236\u5982\u4f55\u5c07\u6587\u4ef6\u5eab\u8207\u5176\u4ed6Gitblit\u7248\u63a7\u4f3a\u670d\u5668\u4e32\u9023
+gb.feed = \u8cc7\u6599\u8a02\u95b1
+gb.filesAdded = \u65b0\u589e{0}\u500b\u6a94\u6848
+gb.filesCopied = \u8907\u88fd{0}\u500b\u6a94\u6848
+gb.filesDeleted = \u522a\u9664{0}\u500b\u6a94\u6848
+gb.filesModified = \u4fee\u6539{0}\u500b\u6a94\u6848
+gb.filesRenamed = \u4fee\u6539{0}\u500b\u6a94\u6848\u540d\u7a31
+gb.filter = \u689d\u4ef6\u904e\u6ffe
+gb.filters = \u67e5\u8a62\u689d\u4ef6
+gb.findSomeRepositories = \u641c\u5c0b\u6587\u4ef6\u5eab
+gb.folder = \u76ee\u9304
+gb.fork = \u5efa\u7acb\u5206\u652f(fork)
+gb.forkedFrom = forked from
+gb.forkInProgress = fork in progress
+gb.forkNotAuthorized = \u5f88\u62b1\u6b49, \u4f60\u7121\u5efa\u7acb\u6587\u4ef6\u5eab{0}\u5206\u652f(fork)\u7684\u6b0a\u9650
+gb.forkRepository = \u7248\u672c\u5eab{0}\u5efa\u7acb\u5206\u652f(fork)?
+gb.forks = \u5206\u652f(forks)
+gb.forksProhibited = \u7981\u6b62\u5efa\u7acb\u5206\u652f(forks)
+gb.forksProhibitedWarning = \u672c\u6587\u4ef6\u5eab\u7981\u6b62\u5206\u652f(fork)
+gb.free = \u91cb\u653e
+gb.frequency = \u983b\u7387
+gb.from = from
+gb.garbageCollection = \u56de\u6536\u7cfb\u7d71\u8cc7\u6e90
+gb.garbageCollectionDescription = \u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u529f\u80fd\u5c07\u6703\u6574\u9813\u9b06\u6563\u7528\u6236\u7aef\u63a8\u9001(push)\u7684\u7269\u4ef6, \u4e5f\u6703\u79fb\u9664\u6587\u4ef6\u5eab\u4e0a\u7121\u7528\u7684\u7269\u4ef6
+gb.gc = \u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u5668
+gb.gcPeriod = \u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u968e\u6bb5
+gb.gcPeriodDescription = \u56de\u6536\u9031\u671f
+gb.gcThreshold = GC \u57fa\u6578(threshold)
+gb.gcThresholdDescription = \u89f8\u767c\u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u7684\u6700\u5c0f\u7269\u4ef6\u5bb9\u91cf
+gb.general = \u4e00\u822c
+gb.generalDescription = \u4e00\u822c\u8a2d\u5b9a
+gb.hasNotReviewed = \u5c1a\u672a\u6aa2\u6838\u904e
+gb.head = HEAD
+gb.headRef = \u9810\u8a2d\u5206\u652f(HEAD)
+gb.headRefDescription = \u9810\u8a2d\u5206\u652f\u5c07\u6703\u8907\u88fd\u4ee5\u53ca\u986f\u793a\u5230\u532f\u7e3d\u9801\u9762
+gb.heapAllocated = \u5df2\u4f7f\u7528\u5806\u7a4d(Heap)
+gb.heapMaximum = \u6700\u5927\u5806\u7a4d(heap)
+gb.heapUsed = \u5df2\u4f7f\u7528\u7684\u5806\u7a4d(heap)
+gb.history = \u6b77\u7a0b
+gb.home = \u9996\u9801
+gb.hookScripts = hook\u7684\u8173\u672c
+gb.hookScriptsDescription = \u7576\u63a8\u9001(push)\u81f3\u6b64Gitblit\u7248\u63a7\u4f3a\u670d\u5668\u6642, \u57f7\u884cGroovy\u8173\u672c
+gb.hostname = \u4e3b\u6a5f\u540d\u7a31
+gb.hostnameRequired = \u8acb\u8f38\u5165\u4e3b\u6a5f\u540d\u7a31
+gb.ignore_whitespace =\u5ffd\u7565\u7a7a\u767d
+gb.illegalCharacterRepositoryName = \u7248\u672c\u5eab\u540d\u7a31\u6709\u4e0d\u5408\u6cd5\u7684\u5b57\u5143"{0}"
+gb.illegalLeadingSlash = \u7981\u6b62\u6839\u76ee\u9304(/)
+gb.illegalPersonalRepositoryLocation = \u4f60\u79c1\u4eba\u7248\u672c\u5eab\u5fc5\u9808\u653e\u5728"{0}"
+gb.illegalRelativeSlash = \u7981\u6b62\u76f8\u5c0d\u76ee\u9304(../)
+gb.imgdiffSubtract = Subtract (black = identical)
+gb.in = in
+gb.inclusions = inclusions
+gb.incrementalPushTagMessage = \u7576[{0}]\u5206\u652f\u63a8\u9001\u5f8c,\u81ea\u52d5\u7d66\u4e88\u6a19\u7c64\u865f.
+gb.indexedBranches = \u5206\u652f\u7d22\u5f15
+gb.indexedBranchesDescription = \u9078\u5b9a\u6b32\u57f7\u884cLucene\u7d22\u5f15\u529f\u80fd\u7684\u5206\u652f
+gb.inherited = \u7e7c\u627f
+gb.initialCommit = \u521d\u6b21\u63d0\u4ea4
+gb.initialCommitDescription = \u4ee5\u4e0b\u6b65\u9a5f\u5c07\u6703\u8b93\u4f60\u99ac\u4e0a\u57f7\u884c<code>git clone</code>.\u5982\u679c\u4f60\u672c\u6a5f\u5df2\u6709\u6b64\u6587\u4ef6\u5eab\u4e14\u57f7\u884c\u904e<code>git init</code>,\u8acb\u8df3\u904e\u6b64\u6b65\u9a5f.
+gb.initWithGitignore = \u5305\u542b .gitignore \u6a94\u6848
+gb.initWithGitignoreDescription = \u65b0\u589e\u4e00\u500b\u8a2d\u5b9a\u6a94\u7528\u4f86\u6307\u5b9a\u54ea\u4e9b\u6a94\u6848\u6216\u76ee\u9304\u9700\u8981\u5ffd\u7565
+gb.initWithReadme = \u5305\u542bREADME\u6587\u4ef6
+gb.initWithReadmeDescription = \u6587\u4ef6\u5eab\u5c07\u7522\u751f\u7c21\u55aeREADME\u6587\u4ef6
+gb.invalidExpirationDate = \u4e0d\u6b63\u78ba\u7684\u5230\u671f\u65e5
+gb.invalidUsernameOrPassword = \u932f\u8aa4\u7684\u4f7f\u7528\u8005\u540d\u7a31\u6216\u5bc6\u78bc!
+gb.isFederated = \u5df2\u7d93\u4e32\u9023
+gb.isFork = \u662f\u5206\u652f\u985e\u578b(fork)
+gb.isFrozen = \u51cd\u7d50\u63a5\u6536
+gb.isFrozenDescription = \u7981\u6b62\u63a8\u9001(push)
+gb.isMirror = \u8a72\u6587\u4ef6\u5eab\u70ba\u93e1\u50cf(mirror)
+gb.isNotValidFile = \u4e0d\u662f\u6b63\u5e38\u6a94\u6848
+gb.isSparkleshared = \u8a72\u6587\u4ef6\u5eab\u5df2\u70baSparkleshared (http://sparkleshare.org)
+gb.issued = \u767c\u51fa
+gb.issuer = issuer
+gb.jceWarning = Your Java Runtime Environment does not have the "JCE Unlimited Strength Jurisdiction Policy" files.\nThis will limit the length of passwords you may use to encrypt your keystores to 7 characters.\nThese policy files are an optional download from Oracle.\n\nWould you like to continue and generate the certificate infrastructure anyway?\n\nAnswering No will direct your browser to Oracle's download page so that you may download the policy files.
+gb.key = \u91d1\u9470
+gb.keyCompromise = \u91d1\u9470\u5bc6\u78bc\u5916\u6d29
+gb.labels = \u6a19\u8a18
+gb.languagePreference = \u5e38\u7528\u8a9e\u8a00
+gb.languagePreferenceDescription = \u9078\u64c7\u4f60\u60f3\u8981\u7684Gitblit\u7ffb\u8b6f
+gb.lastChange = \u6700\u8fd1\u4fee\u6539
+gb.lastLogin = \u6700\u8fd1\u767b\u5165
+gb.lastNDays = \u6700\u8fd1{0}\u5929
+gb.lastPull = \u4e0a\u6b21\u4e0b\u8f09(pull)
+gb.leaveComment = \u7559\u4e0b\u8a3b\u89e3
+gb.line = \u884c
+gb.loading = \u8f09\u5165
+gb.local = \u672c\u5730\u7aef
+gb.locality = \u4f4d\u7f6e
+gb.log = \u65e5\u8a8c
+gb.login = \u767b\u5165
+gb.logout = \u767b\u51fa
+gb.looksGood = \u770b\u8d77\u4f86\u5f88\u597d
+gb.luceneDisabled = \u505c\u7528Lucene\u7d22\u5f15\u529f\u80fd
+gb.mailingLists = \u90f5\u4ef6\u540d\u55ae
+gb.maintenanceTickets = \u7dad\u8b77
+gb.manage = \u7ba1\u7406
+gb.manual = \u81ea\u884c\u8f38\u5165
+gb.markdown = markdown
+gb.markdownFailure = \u89e3\u6790Markdown\u5931\u6557
+gb.maxActivityCommits = \u6700\u5927\u63d0\u4ea4\u6d3b\u8e8d\u7387
+gb.maxActivityCommitsDescription = \u6700\u5927\u63d0\u4ea4\u6d3b\u8e8d\u6578\u91cf
+gb.maxHits = \u6700\u5927\u9ede\u64ca
+gb.md5FingerPrint = MD5 Fingerprint
+gb.mentions = \u63d0\u5230
+gb.mentionsMeTickets = \u63d0\u5230\u4f60
+gb.merge = \u5408\u4f75
+gb.mergeBase = \u57fa\u672c\u5408\u4f75
+gb.merged = \u5df2\u5408\u4f75
+gb.mergedPatchset = \u5c07\u88dc\u4e01\u5408\u4f75
+gb.mergedPullRequest = \u5408\u4f75\u63a8\u9001\u8981\u6c42
+gb.mergeSha = mergeSha
+gb.mergeStep1 = Check out a new branch to review the changes \u2014 run this from your project directory
+gb.mergeStep2 = Bring in the proposed changes and review
+gb.mergeStep3 = \u5c07\u63d0\u6848\u4fee\u6539\u5167\u5bb9\u5408\u4f75\u5230\u4f3a\u670d\u5668\u4e0a
+gb.mergeTo = \u5408\u4f75\u5230
+gb.mergeToDescription = \u9810\u8a2d\u5c07\u6587\u4ef6\u76f8\u95dc\u88dc\u4e01\u5305\u8207\u6307\u5b9a\u5206\u652f(branch)\u5408\u4f75
+gb.mergingViaCommandLine = \u7d93\u7531\u6307\u4ee4\u57f7\u884c\u5408\u4f75
+gb.mergingViaCommandLineNote = \u5982\u679c\u4f60\u4e0d\u60f3\u8981\u4f7f\u7528\u81ea\u52d5\u5408\u4f75\u529f\u80fd,\u6216\u662f\u6309\u4e0b\u5408\u4f75\u6309\u9215, \u4f60\u53ef\u4ee5\u4e0b\u6307\u4ee4\u624b\u52d5\u5408\u4f75
+gb.message = \u8a0a\u606f
+gb.metricAuthorExclusions = \u91cf\u5316\u7d71\u8a08\u6642\u6392\u9664\u6d3b\u8e8d\u5e33\u6236
+gb.metrics = \u91cf\u5316\u7d71\u8a08
+gb.milestone = \u91cc\u7a0b\u7891
+gb.milestoneDeleteFailed = \u522a\u9664\u91cc\u7a0b\u7891"{0}"\u5931\u6557
+gb.milestoneProgress = {0}\u958b\u555f,{1}\u7d50\u675f
+gb.milestones = \u91cc\u7a0b\u7891
+gb.mirrorOf = {0}\u7684\u93e1\u50cf
+gb.mirrorWarning = \u8a72\u6587\u4ef6\u5eab\u5c6c\u65bc\u93e1\u50cf, \u4e0d\u80fd\u5920\u63a5\u6536\u63a8\u9001(push)
+gb.miscellaneous = \u5176\u4ed6
+gb.missing = \u5931\u8aa4!
+gb.missingIntegrationBranchMore = \u76ee\u6a19\u5206\u652f\u4e0d\u5728\u6b64\u7248\u672c\u5eab
+gb.missingPermission = the repository for this permission is missing\!
+gb.missingUsername = \u7f3a\u5c11\u4f7f\u7528\u8005\u540d\u7a31
+gb.modification = \u4fee\u6539
+gb.monthlyActivity = \u6708\u6d3b\u52d5
+gb.moreChanges = \u6240\u6709\u8b8a\u66f4...
+gb.moreHistory = \u66f4\u591a\u6b77\u53f2\u7d00\u9304...
+gb.moreLogs = \u66f4\u591a\u63d0\u4ea4 ...
+gb.mutable = \u52d5\u614b\u7d66\u4e88
+gb.myDashboard = \u6211\u7684\u5100\u8868\u677f
+gb.myFork = \u6aa2\u8996\u6211\u5efa\u7acb\u7684\u5206\u652f(fork)
+gb.myProfile = \u6211\u7684\u57fa\u672c\u8cc7\u6599
+gb.myRepositories = \u6211\u7684\u7248\u672c\u5eab
+gb.myTickets = \u6211\u7684\u4efb\u52d9\u55ae
+gb.myUrlDescription = \u4f60Gitblit\u4f3a\u670d\u5668\u7684\u516c\u958bURL
+gb.name = \u540d\u5b57
+gb.nameDescription = \u4f7f\u7528"/"\u505a\u70ba\u6587\u4ef6\u5eab\u7fa4\u7d44\u5206\u985e. \u5982: library/mycoolib.git
+gb.namedPushPolicy = Restrict Push (Named)
+gb.namedPushPolicyDescription = \u4efb\u4f55\u4eba\u7686\u53ef\u6aa2\u8996\u8207\u8907\u88fd(clone)\u6587\u4ef6\u5eab. \u4f60\u53ef\u53e6\u5916\u6307\u5b9a\u8ab0\u80fd\u5920\u6709\u63a8\u9001\u529f\u80fd(push)
+gb.nAttachments = {0}\u500b\u9644\u4ef6
+gb.nClosedTickets = {0}\u9805\u7d50\u675f
+gb.nComments = {0}\u500b\u8a3b\u89e3
+gb.nCommits = {0}\u4efd\u63d0\u4ea4
+gb.needsImprovement = \u9700\u8981\u512a\u5316
+gb.new = \u5efa\u7acb
+gb.newCertificate = \u5efa\u7acb\u8b49\u66f8
+gb.newCertificateDefaults = \u65b0\u8b49\u66f8\u9810\u8a2d\u503c
+gb.newClientCertificateMessage = \u6ce8\u610f:\n'password'\u5bc6\u78bc\u4e26\u4e0d\u662f\u4f7f\u7528\u8005\u5bc6\u78bc, \u800c\u662f\u7528\u4f86\u4fdd\u8b77\u4f7f\u7528\u8005\u500b\u4eba\u7684keystore.\u8a72\u5bc6\u78bc\u4e26\u4e0d\u6703\u5132\u5b58, \u56e0\u6b64\u5fc5\u9808\u8a2d\u5b9a\u63d0\u793a(hint), \u8a72\u63d0\u793a\u5c07\u6703\u5beb\u5728\u4f7f\u7528\u8005\u7684README\u6587\u4ef6\u88e1\u9762.
+gb.newMilestone = \u5efa\u7acb\u91cc\u7a0b\u7891
+gb.newRepository = \u5efa\u7acb\u7248\u672c\u5eab
+gb.newSSLCertificate = \u65b0\u7684\u4f3a\u670d\u5668SSL\u8b49\u66f8
+gb.newTeam = \u5efa\u7acb\u5718\u968a
+gb.newTicket = \u65b0\u589e\u4efb\u52d9\u55ae
+gb.newUser = \u5efa\u7acb\u4f7f\u7528\u8005
+gb.nextPull = next pull
+gb.nFederationProposalsToReview = \u7e3d\u5171\u6709{0}\u500b\u4e32\u9023\u8a08\u756b\u7b49\u5f85\u5be9\u8996
+gb.nMoreCommits = \u9084\u6709{0}\u4efd\u63d0\u4ea4 \u00bb
+gb.noActivity = \u904e\u53bb{0}\u5929\u4f86,\u4e26\u6c92\u6709\u6d3b\u52d5\u7d00\u9304
+gb.noActivityToday = \u4eca\u5929\u6c92\u6709\u6d3b\u52d5\u7d00\u9304
+gb.noComments = \u6c92\u6709\u5099\u8a3b
+gb.noDescriptionGiven = \u6c92\u6709\u7d66\u4e88\u7c21\u8ff0
+gb.noFederation = Sorry, {0} is not configured to federate with any Gitblit instances.
+gb.noForks = {0}\u6c92\u6709\u5206\u652f(fork)
+gb.noGitblitFound = Sorry, {0} could not find a Gitblit instance at {1}.
+gb.noHits = \u7121\u9ede\u64ca
+gb.noIndexedRepositoriesWarning = \u8ddf\u4f60\u76f8\u95dc\u7684\u6587\u4ef6\u5eab\u4e26\u6c92\u6709\u505aLucene\u7d22\u5f15
+gb.noMaximum = \u7121\u6700\u5927\u503c
+gb.noMilestoneSelected = \u672a\u9078\u53d6\u91cc\u7a0b\u7891
+gb.none = \u7121
+gb.nOpenTickets = {0}\u9805\u958b\u555f\u4e2d
+gb.noPermission = \u522a\u9664\u9019\u500b\u6b0a\u9650
+gb.noProposals = \u62b1\u6b49, {0}\u6b64\u6642\u4e26\u4e0d\u662f\u53ef\u63a5\u53d7\u7684\u8a08\u756b
+gb.noSelectedRepositoriesWarning = \u8acb\u81f3\u5c11\u9078\u64c7\u4e00\u500b\u6587\u4ef6\u5eab
+gb.notifyChangedOpenTickets = \u5df2\u958b\u555f\u7684\u4efb\u52d9\u55ae\u6709\u7570\u52d5\u8acb\u767c\u9001\u901a\u77e5
+gb.notRestricted = \u533f\u540d\u72c0\u614b\u53ef\u4ee5View, Clone\u8207Push
+gb.notSpecified = \u7121\u6307\u5b9a
+gb.nParticipants = {0}\u500b\u53c3\u8207
+gb.nTotalTickets = \u7e3d\u5171{0}\u9805
+gb.object = \u7269\u4ef6
+gb.of = \u7684
+gb.ok = ok
+gb.oneAttachment = {0}\u500b\u9644\u4ef6
+gb.oneComment = {0}\u500b\u8a3b\u89e3
+gb.oneCommit = 1\u500b\u63d0\u4ea4
+gb.oneCommitTo = 1\u500b\u63d0\u4ea4\u5230
+gb.oneMoreCommit = \u9084\u6709\u4e00\u500b\u63d0\u4ea4 \u00bb
+gb.oneParticipant = {0}\u53c3\u8207
+gb.OneProposalToReview = \u6709\u4e00\u500b\u4e32\u9023\u7684\u63d0\u6848\u7b49\u5f85\u5be9\u67e5
+gb.opacityAdjust = Adjust opacity
+gb.open = \u958b\u555f
+gb.openMilestones = \u6253\u958b\u91cc\u7a0b\u7891
+gb.organization = \u7d44\u7e54
+gb.organizationalUnit = \u7d44\u7e54\u55ae\u4f4d
+gb.origin = origin
+gb.originDescription = \u6b64\u6587\u4ef6\u5eabURL\u5df2\u7d93\u88ab\u8907\u88fd(cloned)\u4e86
+gb.overdue = \u904e\u671f
+gb.overview = \u6982\u89c0
+gb.owned = \u64c1\u6709\u7684
+gb.owner = \u64c1\u6709\u8005
+gb.ownerDescription = \u64c1\u6709\u8005\u53ef\u4fee\u6539\u6587\u4ef6\u5eab\u8a2d\u5b9a\u503c
+gb.ownerPermission = \u6587\u4ef6\u5eab\u6240\u6709\u8005
+gb.owners = \u6240\u6709\u8005
+gb.ownersDescription = \u6240\u6709\u8005\u53ef\u4ee5\u7ba1\u7406\u6587\u4ef6\u5eab,\u4f46\u662f\u4e0d\u5141\u8a31\u4fee\u6539\u540d\u7a31(\u79c1\u4eba\u6587\u4ef6\u5eab\u4f8b\u5916)
+gb.pageFirst = \u7b2c\u4e00\u7b46
+gb.pageNext = \u4e0b\u4e00\u9801
+gb.pagePrevious = \u4e0a\u4e00\u9801
+gb.pages = \u6587\u4ef6
+gb.parent = \u4e0a\u500b\u7248\u672c
+gb.password = \u5bc6\u78bc
+gb.passwordChangeAborted = \u53d6\u6d88\u5bc6\u78bc\u8b8a\u66f4
+gb.passwordChanged = \u5bc6\u78bc\u8b8a\u66f4\u6210\u529f
+gb.passwordHint = \u5bc6\u78bc\u63d0\u793a
+gb.passwordHintRequired = \u5bc6\u78bc\u63d0\u793a(\u5fc5\u8981)
+gb.passwordsDoNotMatch = \u5bc6\u78bc\u4e0d\u76f8\u7b26
+gb.passwordTooShort = \u5bc6\u78bc\u904e\u77ed, \u6700\u5c11{0}\u500b\u5b57\u5143
+gb.patch = \u4fee\u88dc\u6a94
+gb.patchset = \u88dc\u4e01
+gb.patchsetAlreadyMerged = \u8a72\u88dc\u4e01\u5df2\u7d93\u5408\u4f75\u5230{0}
+gb.patchsetMergeable = \u8a72\u88dc\u4e01\u53ef\u4ee5\u81ea\u52d5\u8207{0}\u5408\u4f75
+gb.patchsetMergeableMore = \u4f7f\u7528\u547d\u4ee4\u529f\u80fd,\u8b93\u6b64\u88dc\u4e01\u53ef\u4ee5\u8207{0}\u5408\u4f75
+gb.patchsetN = \u88dc\u4e01{0}
+gb.patchsetNotApproved = \u8a72\u88dc\u4e01\u7248\u672c\u4e26\u6c92\u6709\u88ab\u6279\u51c6\u8207{0}\u5408\u4f75
+gb.patchsetNotApprovedMore = \u8a72\u88dc\u4e01\u5fc5\u9808\u7531\u5be9\u67e5\u8005\u6279\u51c6
+gb.patchsetNotMergeable = \u8a72\u88dc\u4e01\u4e0d\u80fd\u81ea\u52d5\u8207{0}\u5408\u4f75
+gb.patchsetNotMergeableMore = \u5fc5\u9808\u4ee5rebased\u6216\u662f\u624b\u52d5\u8207{0}\u5408\u4f75\u7684\u65b9\u5f0f\u624d\u80fd\u89e3\u6c7a\u8a72\u88dc\u4e01\u9020\u6210\u7684\u885d\u7a81
+gb.patchsetVetoedMore = \u5be9\u8996\u8005\u5df2\u7d93\u5c0d\u6b64\u88dc\u4e01\u6295\u7968
+gb.permission = \u6b0a\u9650
+gb.permissions = \u6b0a\u9650
+gb.permittedTeams = permitted teams
+gb.permittedUsers = permitted users
+gb.personalRepositories = \u500b\u4eba\u6587\u4ef6\u5eab
+gb.pleaseGenerateClientCertificate = \u8acb\u7522\u751f\u7d66{0}\u4f7f\u7528\u7684\u7528\u6236\u7aef\u8b49\u66f8
+gb.pleaseSelectGitIgnore = \u8acb\u9078\u64c7\u4e00\u500b.gitignore\u6a94\u6848
+gb.pleaseSelectProject = \u8acb\u9078\u64c7\u5c08\u6848!
+gb.pleaseSetDestinationUrl = Please enter a destination url for your proposal\!
+gb.pleaseSetGitblitUrl = \u8acb\u8f38\u5165Gitblit URL !
+gb.pleaseSetRepositoryName = \u8acb\u8a2d\u5b9a\u7248\u672c\u5eab\u540d\u7a31
+gb.pleaseSetTeamName = \u8acb\u8f38\u5165\u5718\u968a\u540d\u7a31
+gb.pleaseSetUsername = \u8acb\u8f38\u5165\u4f7f\u7528\u8005\u540d\u7a31
+gb.plugins = \u63d2\u4ef6
+gb.postReceiveDescription = \u63a5\u5230\u63d0\u4ea4\u7533\u8acb\u5f8c,<em>\u4e26\u4e14\u5728refs\u5b8c\u7562\u5f8c</em>, \u5c07\u6703\u57f7\u884cPost-receive hook..<p>This is the appropriate hook for notifications, build triggers, etc.</p>
+gb.postReceiveScripts = post-receive\u8173\u672c
+gb.preferences = \u9810\u8a2d\u5e38\u7528\u503c
+gb.preparingFork = \u6b63\u5728\u6e96\u5099\u8907\u88fd\u4e2d(fork)...
+gb.preReceiveDescription = \u63a5\u5230\u63d0\u4ea4\u7533\u8acb\u5f8c,<em>\u4f46\u5728\u9084\u6c92\u6709\u66f4\u65b0refs\u524d</em>, \u5c07\u6703\u57f7\u884cPre-receive hook. <p>This is the appropriate hook for rejecting a push.</p>
+gb.preReceiveScripts = pre-receive \u8173\u672c
+gb.preview = \u9810\u89bd
+gb.priority = \u512a\u5148
+gb.privilegeWithdrawn = \u53d6\u6d88\u6b0a\u9650
+gb.project = \u7fa4\u7d44
+gb.projects = \u7fa4\u7d44
+gb.properties = \u5c6c\u6027
+gb.proposal = \u63d0\u6848
+gb.proposalError = \u62b1\u6b49, {0} \u4efd\u5831\u544a\u767c\u751f\u9810\u671f\u5916\u7684\u932f\u8aa4!
+gb.proposalFailed = Sorry, {0} did not receive any proposal data\!
+gb.proposalReceived = Proposal successfully received by {0}.
+gb.proposals = \u8981\u6c42\u806f\u5408\u7684\u63d0\u6848
+gb.proposalTickets = \u63d0\u6848\u4fee\u6539
+gb.proposedThisChange = proposed this change
+gb.proposeInstructions = To start, craft a patchset and upload it with Git. Gitblit will link your patchset to this ticket by the id.
+gb.proposePatchset = \u63d0\u51fa\u88dc\u4e01
+gb.proposePatchsetNote = \u6b61\u8fce\u5c0d\u6b64\u4efb\u52d9\u55ae\u63d0\u4f9b\u88dc\u4e01
+gb.proposeWith = propose a patchset with {0}
+gb.ptCheckout = Fetch & checkout the current patchset to a review branch
+gb.ptDescription = the Gitblit patchset tool
+gb.ptDescription1 = Barnum is a command-line companion for Git that simplifies the syntax for working with Gitblit Tickets and Patchsets.
+gb.ptDescription2 = Barnum requires Python 3 and native Git. It runs on Windows, Linux, and Mac OS X.
+gb.ptMerge = \u53d6\u5f97\u76ee\u524d\u88dc\u4e01,\u7136\u5f8c\u8207\u4f60\u672c\u6a5f\u7aef\u7684\u5206\u652f\u5408\u4f75
+gb.ptSimplifiedCollaboration = simplified collaboration syntax
+gb.ptSimplifiedMerge = simplified merge syntax
+gb.publicKey = \u516c\u958b\u91d1\u9470
+gb.pushedNCommitsTo = {0}\u500b\u63d0\u4ea4\u5df2\u63a8\u9001\u81f3
+gb.pushedNewBranch = \u65b0\u5206\u652f\u5df2\u63a8\u9001(pushed)
+gb.pushedNewTag = \u65b0\u6a19\u7c64\u5df2\u63a8\u9001(pushed)
+gb.pushedOneCommitTo = 1\u500b\u63d0\u4ea4\u5df2\u63a8\u9001\u81f3
+gb.pushPermission = {0}(\u63a8\u9001)
+gb.pushRestricted = authenticated push
+gb.queries = \u67e5\u8a62\u7d50\u679c
+gb.query = \u67e5\u8a62
+gb.queryHelp = \u652f\u63f4\u6a19\u6e96\u67e5\u8a62\u8a9e\u6cd5.<p/><p/>\u8a73\u60c5\u8acb\u53c3\u8003 <a target\ = "_new" href\ = "http\://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a>
+gb.queryResults = results {0} - {1} ({2} hits)
+gb.questionTickets = \u63d0\u554f
+gb.raw = \u539f\u59cb
+gb.reason = \u539f\u56e0
+gb.receive = \u63a5\u6536
+gb.received = \u5df2\u63a5\u6536
+gb.receiveSettings = \u8a2d\u5b9a\u63a5\u6536\u65b9\u5f0f
+gb.receiveSettingsDescription = \u63a7\u7ba1\u63a8\u9001\u5230\u6587\u4ef6\u5eab\u7684\u63a5\u6536\u65b9\u5f0f
+gb.recent = \u6700\u8fd1
+gb.recentActivity = \u6700\u8fd1\u6d3b\u8e8d\u72c0\u6cc1
+gb.recentActivityNone = \u904e\u53bb{0}\u5929/\u7121
+gb.recentActivityStats = \u904e\u53bb{0}\u5929,\u4e00\u5171\u6709{2}\u4eba\u57f7\u884c{1}\u4efd\u63d0\u4ea4
+gb.reflog = \u76f8\u95dc\u65e5\u8a8c
+gb.refresh = \u5237\u65b0
+gb.refs = \u5f15\u7528
+gb.regexPermission = \u5df2\u7d93\u4f7f\u7528\u6b63\u898f\u8868\u793a\u5f0f(regular expression)"{0}" \u8a2d\u5b9a\u6b0a\u9650\u5b8c\u7562
+gb.registration = \u8a3b\u518a
+gb.registrations = federation registrations
+gb.releaseDate = \u767c\u8868\u65e5
+gb.remote = \u9060\u7aef
+gb.removeVote = \u79fb\u9664\u6295\u7968
+gb.rename = \u6539\u540d\u7a31
+gb.repositories = \u6587\u4ef6\u5eab
+gb.repository = \u7248\u672c\u5eab
+gb.repositoryDeleted = \u7248\u672c\u5eab"{0}"\u5df2\u522a\u9664
+gb.repositoryDeleteFailed = \u522a\u9664\u7248\u672c\u5eab"{0}"\u5931\u6557!
+gb.repositoryDoesNotAcceptPatchsets = \u8a72\u7248\u672c\u5eab\u4e0d\u63a5\u53d7\u88dc\u4e01
+gb.repositoryForked = \u7248\u672c\u5eab{0}\u5df2\u7d93\u5efa\u7acb\u5206\u652f(fork)
+gb.repositoryForkFailed= \u5efa\u7acb\u5206\u652f(fork)\u5931\u6557
+gb.repositoryIsFrozen = \u8a72\u7248\u672c\u5eab\u5df2\u51cd\u7d50
+gb.repositoryIsMirror = \u8a72\u7248\u672c\u5eab\u70ba\u552f\u8b80\u8907\u672c
+gb.repositoryNotSpecified = \u672a\u6307\u5b9a\u7248\u672c\u5eab!
+gb.repositoryNotSpecifiedFor = \u7248\u672c\u5eab\u4e26\u6c92\u6709\u6307\u5b9a\u7d66 {0}\!
+gb.repositoryPermissions = \u7248\u672c\u5eab\u6b0a\u9650
+gb.repositoryUrl = \u7248\u672c\u5eab url
+gb.requestTickets = \u512a\u5316 & \u4efb\u52d9
+gb.requireApproval = \u9700\u6279\u51c6
+gb.requireApprovalDescription = \u5408\u4f75\u6309\u9215\u555f\u7528\u524d,\u88dc\u4e01\u5305\u5fc5\u9808\u5148\u6279\u51c6
+gb.reset = \u6e05\u9664
+gb.responsible = \u8ca0\u8cac\u4eba\u54e1
+gb.restrictedRepositories = restricted repositories
+gb.review = \u8907\u67e5(review)
+gb.reviewedPatchsetRev = reviewed patchset {0} revision {1}\: {2}
+gb.reviewers = \u5be9\u67e5\u8005
+gb.reviewPatchset = review {0} patchset {1}
+gb.reviews = reviews
+gb.revisionHistory = \u4fee\u6539\u7d00\u9304
+gb.revokeCertificate = \u64a4\u56de\u8b49\u66f8
+gb.revokeCertificateReason = \u8acb\u8f38\u5165\u64a4\u56de\u8b49\u66f8\u7406\u7531
+gb.revoked = \u5df2\u64a4\u92b7
+gb.rewind = REWIND
+gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
+gb.save = \u5132\u5b58
+gb.search = \u641c\u5c0b
+gb.searchForAuthor = Search for commits authored by
+gb.searchForCommitter = Search for commits committed by
+gb.searchTickets = \u641c\u5c0b\u4efb\u52d9\u55ae
+gb.searchTicketsTooltip = \u627e\u5230{0}\u4efd\u4efb\u52d9\u55ae
+gb.searchTooltip = \u641c\u5c0b{0}
+gb.searchTypeTooltip = \u9078\u64c7\u641c\u5c0b\u985e\u578b
+gb.selectAccessRestriction = Please select access restriction\!
+gb.selected = \u9078\u5b9a
+gb.selectFederationStrategy = Please select federation strategy\!
+gb.sendEmail = \u767cemail
+gb.sendProposal = \u63d0\u6848
+gb.serialNumber = \u5e8f\u865f
+gb.serveCertificate = \u555f\u7528\u4f7f\u7528\u6b64\u8b49\u66f8\u7684https\u529f\u80fd
+gb.serverDoesNotAcceptPatchsets = \u672c\u4f3a\u670d\u5668\u4e0d\u63a5\u53d7\u88dc\u4e01
+gb.servers = \u4f3a\u670d\u5668
+gb.servletContainer = servlet\u5bb9\u5668
+gb.sessionEnded = session\u5df2\u7d93\u53d6\u6d88
+gb.setDefault = \u8a2d\u70ba\u9810\u8a2d\u503c
+gb.settings = \u8a2d\u5b9a
+gb.severity = \u91cd\u8981
+gb.sha1FingerPrint = SHA-1 Fingerprint
+gb.show_whitespace = \u986f\u793a\u7a7a\u767d
+gb.showHideDetails = \u986f\u793a/\u96b1\u85cf \u8a73\u89e3\u5167\u5bb9
+gb.showReadme = \u986f\u793areadme\u6587\u4ef6
+gb.showReadmeDescription = \u5728\u532f\u7e3d\u9801\u9762\u4e2d\u986f\u793a"readme"(markdown\u683c\u5f0f)
+gb.showRemoteBranches = \u986f\u793a\u9060\u7aef\u5206\u652f
+gb.showRemoteBranchesDescription = \u986f\u793a\u9060\u7aef\u5206\u652f(branches)
+gb.signatureAlgorithm = \u7c3d\u7ae0\u6f14\u7b97\u6cd5
+gb.since = \u5f9e
+gb.siteName = \u7ad9\u53f0\u540d\u7a31
+gb.siteNameDescription = \u4f3a\u670d\u5668\u7c21\u7a31
+gb.size = \u5bb9\u91cf
+gb.skipSizeCalculation = \u7565\u904e\u5bb9\u91cf\u8a08\u7b97
+gb.skipSizeCalculationDescription = \u4e0d\u8a08\u7b97\u6587\u4ef6\u5eab\u5bb9\u91cf(\u52a0\u5feb\u7db2\u9801\u8f09\u5165\u901f\u5ea6)
+gb.skipSummaryMetrics = \u7565\u904e\u91cf\u5316\u532f\u7e3d
+gb.skipSummaryMetricsDescription = \u4e0d\u8981\u8a08\u7b97\u91cf\u5316\u4e26\u4e14\u986f\u793a\u5728\u532f\u7e3d\u9801\u9762\u4e0a(\u52a0\u5feb\u901f\u5ea6)
+gb.sort = \u6392\u5e8f
+gb.sortHighestPriority = \u6700\u9ad8\u512a\u5148
+gb.sortHighestSeverity = \u6700\u91cd\u8981
+gb.sortLeastComments = \u6700\u5c11\u5099\u8a3b
+gb.sortLeastPatchsetRevisions = \u6700\u5c11\u88dc\u4e01\u4fee\u6539
+gb.sortLeastRecentlyUpdated = \u6700\u8fd1\u6700\u5c11\u8b8a\u52d5
+gb.sortLeastVotes = \u6700\u5c11\u6295\u7968
+gb.sortLowestPriority = \u6700\u4f4e\u512a\u5148
+gb.sortLowestSeverity = \u6700\u4e0d\u91cd\u8981
+gb.sortMostComments = \u6700\u591a\u5099\u8a3b
+gb.sortMostPatchsetRevisions = \u6700\u591a\u88dc\u4e01\u4fee\u6b63
+gb.sortMostRecentlyUpdated = \u6700\u8fd1\u66f4\u65b0
+gb.sortMostVotes = \u6700\u591a\u6295\u7968
+gb.sortNewest = \u6700\u65b0
+gb.sortOldest = \u6700\u820a
+gb.specified = \u6307\u5b9a\u7d66\u4e88(\u542b\u7cfb\u7d71\u9810\u8a2d)
+gb.sshKeyCommentDescription = \u8acb\u8f38\u5165\u5099\u8a3b, \u82e5\u7121\u5099\u8a3b, \u5c07\u81ea\u8a02\u586b\u5165key data
+gb.sshKeyPermissionDescription = \u6307\u5b9a\u8a72SSH key\u6240\u64c1\u6709\u7684\u5b58\u53d6\u6b0a\u9650
+gb.sshKeys = SSH Keys
+gb.sshKeysDescription = SSH \u516c\u958b\u91d1\u9470\u662f\u5bc6\u78bc\u8a8d\u8b49\u5916\u66f4\u5b89\u5168\u7684\u9078\u9805
+gb.sslCertificateGenerated = \u6210\u529f\u7522\u751f\u7d66{0}\u7684\u670d\u5668SSL\u8b49\u66f8
+gb.sslCertificateGeneratedRestart = \u6210\u529f\u7522\u751f\u7d66{0}\u4f7f\u7528\u7684SSL\u8b49\u66f8\n\u4f60\u5fc5\u9808\u91cd\u65b0\u555f\u52d5Gitblit\u7248\u63a7\u4f3a\u670d\u5668\u624d\u80fd\u555f\u7528\u65b0\u7684\u8b49\u66f8\n\nf you are launching with the '--alias' parameter you will have to set that to ''--alias {0}''.
+gb.star = \u91cd\u8981
+gb.stargazers = stargazers
+gb.starred = \u91cd\u8981
+gb.starredAndOwned = \u91cd\u8981\u7684 & \u64c1\u6709\u7684
+gb.starredRepositories = \u91cd\u8981\u7684\u6587\u4ef6\u5eab
+gb.starting = \u555f\u52d5\u4e2d
+gb.stateProvince = \u5dde\u6216\u7701
+gb.stats = \u7d71\u8a08
+gb.status = \u72c0\u614b
+gb.stepN = \u6b65\u9a5f{0}
+gb.stopWatching = \u505c\u6b62\u8ffd\u8e64(watching)
+gb.subject = \u6a19\u984c
+gb.subscribe = \u8a02\u95b1
+gb.summary = \u532f\u7e3d
+gb.superseded = \u5df2\u88ab\u66ff\u4ee3
+gb.tag = \u6a19\u7c64
+gb.tagger = tagger
+gb.tags = \u6a19\u7c64
+gb.taskTickets = \u4efb\u52d9
+gb.team = \u5718\u968a
+gb.teamCreated = \u5718\u968a"{0}"\u65b0\u589e\u6210\u529f.
+gb.teamMembers = \u5718\u968a\u6210\u54e1
+gb.teamMemberships = \u5718\u968a\u6210\u54e1(memberships)
+gb.teamMustSpecifyRepository = \u5718\u968a\u6700\u5c11\u8981\u6307\u5b9a\u4e00\u500b\u7248\u672c\u5eab
+gb.teamName = \u5718\u968a\u540d\u7a31
+gb.teamNameUnavailable = \u5718\u968a"{0}"\u4e0d\u5b58\u5728.
+gb.teamPermission = "{0}" \u5718\u968a\u6210\u54e1\u7684\u6b0a\u9650
+gb.teamPermissions = \u5718\u968a\u6b0a\u9650
+gb.teamPermissionsDescription = \u4f60\u53ef\u4ee5\u6307\u5b9a\u5718\u968a\u6b0a\u9650.\u9019\u4e9b\u8a2d\u5b9a\u5c07\u6703\u53d6\u4ee3\u539f\u672c\u5718\u968a\u9810\u8a2d\u6b0a\u9650
+gb.teams = \u53c3\u8207\u7684\u5718\u968a
+gb.ticket = \u4efb\u52d9\u55ae
+gb.ticketAssigned = \u5df2\u6307\u5b9a
+gb.ticketComments = \u8a3b\u89e3
+gb.ticketId = \u4efb\u52d9\u55aeID
+gb.ticketIsClosed = \u8a72\u4efb\u52d9\u55ae\u5df2\u7d93\u7d50\u6848
+gb.ticketN = \u4efb\u52d9\u55ae\u865f#{0}
+gb.ticketOpenDate = \u767c\u884c\u65e5
+gb.ticketPatchset = {0}\u4efb\u52d9\u55ae,{1}\u88dc\u4e01
+gb.tickets = \u4efb\u52d9\u55ae
+gb.ticketSettings = \u4efb\u52d9\u55ae\u5167\u5bb9\u8a2d\u5b9a
+gb.ticketStatus = \u72c0\u614b
+gb.ticketsWelcome = \u4f60\u53ef\u4ee5\u5229\u7528\u4efb\u52d9\u55ae\u7cfb\u7d71\u5efa\u69cb\u51fa\u5f85\u8fa6\u4e8b\u9805, \u81ed\u87f2\u56de\u5831\u5340\u4ee5\u53ca\u88dc\u4e01\u5305\u7684\u5354\u540c\u5408\u4f5c
+gb.time.daysAgo = {0}\u5929\u524d
+gb.time.hoursAgo = {0}\u5c0f\u6642\u524d
+gb.time.inDays = {0}\u5929\u5167
+gb.time.inHours = {0}\u5c0f\u6642\u5167
+gb.time.inMinutes = {0}\u5206\u9418\u5167
+gb.time.justNow = \u525b\u525b
+gb.time.minsAgo = {0}\u5206\u9418\u524d
+gb.time.monthsAgo = {0}\u6708\u524d
+gb.time.oneYearAgo = 1\u5e74\u524d
+gb.time.today = \u4eca\u5929
+gb.time.weeksAgo = {0}\u5468\u524d
+gb.time.yearsAgo = {0}\u5e74\u524d
+gb.time.yesterday = \u6628\u5929
+gb.title = \u6a19\u984c
+gb.to = to
+gb.toBranch = to {0}
+gb.todaysActivityNone = \u4eca\u5929/\u7121
+gb.todaysActivityStats = \u4eca\u5929/\u6709{2}\u500b\u4f5c\u8005\u5b8c\u6210{1}\u500b\u63d0\u4ea4
+gb.token = token
+gb.tokenAllDescription = \u6240\u6709\u7248\u672c\u5eab,\u4f7f\u7528\u8005\u8207\u8a2d\u5b9a
+gb.tokenJurDescription = \u6240\u6709\u7248\u672c\u5eab
+gb.tokens = federation tokens
+gb.tokenUnrDescription = \u6240\u6709\u7248\u672c\u5eab\u8207\u4f7f\u7528\u8005
+gb.topic = \u8a71\u984c
+gb.topicsAndLabels = \u8a71\u984c\u8207\u6a19\u8a18
+gb.transportPreference = \u9810\u8a2d\u901a\u8a0a\u5354\u5b9a
+gb.transportPreferenceDescription = \u8a2d\u5b9a\u4f60\u5e38\u7528\u7684\u9023\u7dda\u901a\u8a0a\u5354\u5b9a\u4ee5\u7528\u4f86\u8907\u88fd(clone)
+gb.tree = \u76ee\u9304
+gb.type = \u985e\u578b
+gb.unauthorizedAccessForRepository = \u7248\u672c\u5eab\u672a\u6388\u6b0a\u5b58\u53d6
+gb.undefinedQueryWarning = \u672a\u8a2d\u5b9a\u67e5\u8a62\u689d\u4ef6
+gb.unspecified = \u672a\u6307\u5b9a
+gb.unstar = \u53d6\u6d88
+gb.updated = \u5df2\u66f4\u65b0
+gb.updatedBy = updated by
+gb.uploadedPatchsetN = \u88dc\u4e01{0}\u5df2\u4e0a\u50b3
+gb.uploadedPatchsetNRevisionN = \u88dc\u4e01{0}\u4fee\u6539\u7248\u672c{1}\u5df2\u4e0a\u50b3
+gb.url = URL
+gb.useDocsDescription = \u8a08\u7b97\u6587\u4ef6\u5eab\u88e1\u9762\u7684Markdown\u6a94\u6848
+gb.useIncrementalPushTagsDescription = \u63a8\u9001\u6642\u5c07\u81ea\u52d5\u65b0\u589e\u6a19\u7c64\u865f\u78bc
+gb.userCreated = \u6210\u529f\u5efa\u7acb\u65b0\u4f7f\u7528\u8005"{0}"
+gb.userDeleted = \u4f7f\u7528\u8005"{0}"\u5df2\u522a\u9664
+gb.userDeleteFailed = \u4f7f\u7528\u8005"{0}"\u522a\u9664\u5931\u6557
+gb.username = \u4f7f\u7528\u8005\u540d\u7a31
+gb.usernameUnavailable = \u4f7f\u7528\u8005\u540d\u7a31"{0}"\u4e0d\u53ef\u7528
+gb.userPermissions = \u4f7f\u7528\u8005\u6b0a\u9650
+gb.userPermissionsDescription = \u4f60\u53ef\u4ee5\u91dd\u5c0d\u5e33\u865f\u8a2d\u5b9a\u6b0a\u9650(\u9019\u4e9b\u8a2d\u5b9a\u5c07\u8986\u84cb\u5718\u968a\u6216\u5176\u4ed6\u6b0a\u9650)
+gb.users = \u4f7f\u7528\u8005
+gb.userServiceDoesNotPermitAddUser = {0}\u4e0d\u5141\u8a31\u65b0\u589e\u4f7f\u7528\u8005\u5e33\u865f
+gb.userServiceDoesNotPermitPasswordChanges = {0}\u4e0d\u5141\u8a31\u4fee\u6539\u5bc6\u78bc
+gb.useTicketsDescription = readonly, distributed Ticgit issues
+gb.validFrom = valid from
+gb.validity = validity
+gb.validUntil = valid until
+gb.verifyCommitter = \u63d0\u4ea4\u8005\u9700\u9a57\u8b49
+gb.verifyCommitterDescription = \u9700\u8981\u63d0\u4ea4\u8005\u7b26\u5408\u63a8\u9001\u5e33\u865f
+gb.verifyCommitterNote = \u6240\u6709\u5408\u4f75\u52d5\u4f5c\u7686\u9808\u5f37\u5236\u4f7f\u7528"--no-ff"\u53c3\u6578
+gb.version = \u7248\u672c
+gb.veto = veto
+gb.view = \u6aa2\u8996
+gb.viewAccess = \u4f60\u6c92\u6709Gitblit\u8b80\u53d6\u6216\u662f\u4fee\u6539\u6b0a\u9650
+gb.viewCertificate = \u6aa2\u8996\u8b49\u66f8
+gb.viewComparison = \u6bd4\u8f03\u9019{0}\u500b\u63d0\u4ea4 \u00bb
+gb.viewPermission = {0} (\u6aa2\u8996)
+gb.viewPolicy = Restrict View, Clone, & Push
+gb.viewPolicyDescription = \u9078\u64c7\u53ef\u4ee5\u5728\u6587\u4ef6\u5eab\u6aa2\u8996,\u8907\u88fd(clone)\u8207\u63a8\u9001(push)\u7684\u4f7f\u7528\u8005, \u9664\u6b64\u4e4b\u5916\u5176\u4ed6\u4eba\u7686\u7121\u6b0a\u9650
+gb.viewRestricted = authenticated view, clone, & push
+gb.vote = \u5c0d{0}\u6295\u7968
+gb.voters = votes
+gb.votes = votes
+gb.warning = \u8b66\u544a
+gb.watch = \u76e3\u770b{0}
+gb.watchers = \u76e3\u770b\u8005
+gb.watching = \u76e3\u770b\u4e2d
+gb.workingCopy = \u5de5\u4f5c\u8907\u672c
+gb.workingCopyWarning = \u8a72\u6587\u4ef6\u5eab\u4ecd\u6709\u5de5\u4f5c\u8907\u672c,\u56e0\u6b64\u7121\u6cd5\u63a5\u53d7\u63a8\u9001(push)
+gb.write = write
+gb.youDoNotHaveClonePermission = \u4f60\u4e0d\u5141\u8a31\u8907\u88fd(clone)\u6b64\u6587\u4ef6\u5eab
+gb.yourAssignedTickets = \u6307\u6d3e\u7d66\u4f60\u7684
+gb.yourCreatedTickets = \u7531\u4f60\u65b0\u589e\u7684
+gb.yourWatchedTickets = \u4f60\u60f3\u770b\u7684
+gb.zip = zip\u58d3\u7e2e\u6a94
+gb.ticketState =
+gb.repositoryForkFailed =
+gb.anonymousUser =
+gb.oneAttachment =
+gb.viewPolicy =
+gb.emailMeOnMyTicketChangesDescription =
diff --git a/src/main/java/com/gitblit/wicket/GitblitWicketApp.java b/src/main/java/com/gitblit/wicket/GitblitWicketApp.java
index 8d3d598..fefa0f4 100644
--- a/src/main/java/com/gitblit/wicket/GitblitWicketApp.java
+++ b/src/main/java/com/gitblit/wicket/GitblitWicketApp.java
@@ -8,12 +8,14 @@
import com.gitblit.IStoredSettings;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IFilestoreManager;
import com.gitblit.manager.IGitblit;
import com.gitblit.manager.INotificationManager;
import com.gitblit.manager.IPluginManager;
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IServicesManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.tickets.ITicketService;
import com.gitblit.transport.ssh.IPublicKeyManager;
@@ -68,8 +70,12 @@
public abstract IGitblit gitblit();
+ public abstract IServicesManager services();
+
public abstract ITicketService tickets();
public abstract TimeZone getTimezone();
+
+ public abstract IFilestoreManager filestore();
}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java b/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java
index 7865fb3..68ad84a 100644
--- a/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java
+++ b/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java
@@ -17,6 +17,8 @@
import java.util.Date;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import org.apache.wicket.protocol.http.IWebApplicationFactory;
@@ -28,7 +30,6 @@
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerWicketFilter;
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
@@ -37,8 +38,6 @@
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
-import dagger.ObjectGraph;
-
/**
*
* Customization of the WicketFilter to allow smart browser-side caching of
@@ -47,7 +46,8 @@
* @author James Moger
*
*/
-public class GitblitWicketFilter extends DaggerWicketFilter {
+@Singleton
+public class GitblitWicketFilter extends WicketFilter {
private IStoredSettings settings;
@@ -59,13 +59,19 @@
private GitBlitWebApp webapp;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.settings = dagger.get(IStoredSettings.class);
- this.runtimeManager = dagger.get(IRuntimeManager.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
- this.projectManager = dagger.get(IProjectManager.class);
- this.webapp = dagger.get(GitBlitWebApp.class);
+ @Inject
+ public GitblitWicketFilter(
+ IStoredSettings settings,
+ IRuntimeManager runtimeManager,
+ IRepositoryManager repositoryManager,
+ IProjectManager projectManager,
+ GitBlitWebApp webapp) {
+
+ this.settings = settings;
+ this.runtimeManager = runtimeManager;
+ this.repositoryManager = repositoryManager;
+ this.projectManager = projectManager;
+ this.webapp = webapp;
}
@Override
diff --git a/src/main/java/com/gitblit/wicket/Html5DateField.java b/src/main/java/com/gitblit/wicket/Html5DateField.java
new file mode 100644
index 0000000..927ee46
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/Html5DateField.java
@@ -0,0 +1,156 @@
+package com.gitblit.wicket;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import org.apache.wicket.Session;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.form.AbstractTextComponent.ITextFormatProvider;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.util.convert.IConverter;
+import org.apache.wicket.util.convert.converters.DateConverter;
+
+public class Html5DateField extends TextField<Date> implements ITextFormatProvider {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String DEFAULT_PATTERN = "MM/dd/yyyy";
+
+ private String datePattern = null;
+
+ private IConverter converter = null;
+
+ /**
+ * Creates a new Html5DateField, without a specified pattern. This is the same as calling
+ * <code>new Html5DateField(id, Date.class)</code>
+ *
+ * @param id
+ * The id of the date field
+ */
+ public Html5DateField(String id)
+ {
+ this(id, null, defaultDatePattern());
+ }
+
+ /**
+ * Creates a new Html5DateField, without a specified pattern. This is the same as calling
+ * <code>new Html5DateField(id, object, Date.class)</code>
+ *
+ * @param id
+ * The id of the date field
+ * @param model
+ * The model
+ */
+ public Html5DateField(String id, IModel<Date> model)
+ {
+ this(id, model, defaultDatePattern());
+ }
+
+ /**
+ * Creates a new Html5DateField bound with a specific <code>SimpleDateFormat</code> pattern.
+ *
+ * @param id
+ * The id of the date field
+ * @param datePattern
+ * A <code>SimpleDateFormat</code> pattern
+ *
+ */
+ public Html5DateField(String id, String datePattern)
+ {
+ this(id, null, datePattern);
+ }
+
+ /**
+ * Creates a new DateTextField bound with a specific <code>SimpleDateFormat</code> pattern.
+ *
+ * @param id
+ * The id of the date field
+ * @param model
+ * The model
+ * @param datePattern
+ * A <code>SimpleDateFormat</code> pattern
+ */
+ public Html5DateField(String id, IModel<Date> model, String datePattern)
+ {
+ super(id, model, Date.class);
+ this.datePattern = datePattern;
+ converter = new DateConverter()
+ {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * @see org.apache.wicket.util.convert.converters.DateConverter#getDateFormat(java.util.Locale)
+ */
+ @Override
+ public DateFormat getDateFormat(Locale locale)
+ {
+ if (locale == null)
+ {
+ locale = Locale.getDefault();
+ }
+ return new SimpleDateFormat(Html5DateField.this.datePattern, locale);
+ }
+ };
+ }
+
+ /**
+ * Returns the default converter if created without pattern; otherwise it returns a
+ * pattern-specific converter.
+ *
+ * @param type
+ * The type for which the converter should work
+ *
+ * @return A pattern-specific converter
+ */
+ @Override
+ public IConverter getConverter(Class<?> type)
+ {
+ if (converter == null)
+ {
+ return super.getConverter(type);
+ }
+ else
+ {
+ return converter;
+ }
+ }
+
+ /**
+ * Returns the date pattern.
+ *
+ * @see org.apache.wicket.markup.html.form.AbstractTextComponent.ITextFormatProvider#getTextFormat()
+ */
+ public String getTextFormat()
+ {
+ return datePattern;
+ }
+
+ /**
+ * Try to get datePattern from user session locale. If it is not possible, it will return
+ * {@link #DEFAULT_PATTERN}
+ *
+ * @return date pattern
+ */
+ private static String defaultDatePattern()
+ {
+ Locale locale = Session.get().getLocale();
+ if (locale != null)
+ {
+ DateFormat format = DateFormat.getDateInstance(DateFormat.SHORT, locale);
+ if (format instanceof SimpleDateFormat)
+ {
+ return ((SimpleDateFormat)format).toPattern();
+ }
+ }
+ return DEFAULT_PATTERN;
+ }
+
+ @Override
+ protected String getInputType()
+ {
+ return "date";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/NonTrimmedPasswordTextField.java b/src/main/java/com/gitblit/wicket/NonTrimmedPasswordTextField.java
new file mode 100644
index 0000000..1bf34e4
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/NonTrimmedPasswordTextField.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.wicket;
+
+import org.apache.wicket.markup.html.form.PasswordTextField;
+import org.apache.wicket.model.IModel;
+
+/**
+ * PasswordText field which will not trim spaces from the input field. This
+ * ensures the password trimming behaviour is everywhere (ui/ssh/git) the same
+ * (#932).
+ */
+public class NonTrimmedPasswordTextField extends PasswordTextField
+{
+ private static final long serialVersionUID = 1L;
+
+ public NonTrimmedPasswordTextField(final String id)
+ {
+ super(id);
+ }
+
+ public NonTrimmedPasswordTextField(final String id, final IModel<String> model)
+ {
+ super(id, model);
+ }
+
+ @Override
+ protected boolean shouldTrimInput()
+ {
+ return false;
+ }
+
+}
diff --git a/src/main/java/com/gitblit/wicket/TicketsUI.java b/src/main/java/com/gitblit/wicket/TicketsUI.java
index 0eff4cf..8d59952 100644
--- a/src/main/java/com/gitblit/wicket/TicketsUI.java
+++ b/src/main/java/com/gitblit/wicket/TicketsUI.java
@@ -21,6 +21,8 @@
import org.apache.wicket.markup.html.basic.Label;
import com.gitblit.models.TicketModel;
+import com.gitblit.models.TicketModel.Priority;
+import com.gitblit.models.TicketModel.Severity;
import com.gitblit.models.TicketModel.Status;
import com.gitblit.models.TicketModel.Type;
import com.gitblit.utils.StringUtils;
@@ -38,37 +40,74 @@
public static final String [] closedStatii = new String [] { "!" + Status.New.name().toLowerCase(), "!" + Status.Open.name().toLowerCase() };
public static Label getStateIcon(String wicketId, TicketModel ticket) {
- return getStateIcon(wicketId, ticket.type, ticket.status);
+ return getStateIcon(wicketId, ticket.type, ticket.status, ticket.severity);
}
- public static Label getStateIcon(String wicketId, Type type, Status state) {
+ public static Label getStateIcon(String wicketId, Type type, Status state, Severity severity) {
Label label = new Label(wicketId);
if (type == null) {
type = Type.defaultType;
}
switch (type) {
case Proposal:
- WicketUtils.setCssClass(label, "fa fa-code-fork");
+ WicketUtils.setCssClass(label, "fa fa-code-fork fa-fw");
break;
case Bug:
- WicketUtils.setCssClass(label, "fa fa-bug");
+ WicketUtils.setCssClass(label, "fa fa-bug fa-fw");
break;
case Enhancement:
- WicketUtils.setCssClass(label, "fa fa-magic");
+ WicketUtils.setCssClass(label, "fa fa-magic fa-fw");
break;
case Question:
- WicketUtils.setCssClass(label, "fa fa-question");
+ WicketUtils.setCssClass(label, "fa fa-question fa-fw");
+ break;
+ case Maintenance:
+ WicketUtils.setCssClass(label, "fa fa-cogs fa-fw");
break;
default:
// standard ticket
- WicketUtils.setCssClass(label, "fa fa-ticket");
+ WicketUtils.setCssClass(label, "fa fa-ticket fa-fw");
}
- WicketUtils.setHtmlTooltip(label, getTypeState(type, state));
+ WicketUtils.setHtmlTooltip(label, getTypeState(type, state, severity));
+
return label;
}
- public static String getTypeState(Type type, Status state) {
- return state.toString() + " " + type.toString();
+ public static Label getPriorityIcon(String wicketId, Priority priority) {
+ Label label = new Label(wicketId);
+ if (priority == null) {
+ priority = Priority.defaultPriority;
+ }
+ switch (priority) {
+ case Urgent:
+ WicketUtils.setCssClass(label, "fa fa-step-forward fa-rotate-270");
+ break;
+ case High:
+ WicketUtils.setCssClass(label, "fa fa-caret-up fa-lg");
+ break;
+ case Low:
+ WicketUtils.setCssClass(label, "fa fa-caret-down fa-lg");
+ break;
+ default:
+ }
+ WicketUtils.setHtmlTooltip(label, priority.toString());
+
+ return label;
+ }
+
+ public static String getPriorityClass(Priority priority) {
+ return String.format("priority-%s", priority);
+ }
+
+ public static String getSeverityClass(Severity severity) {
+ return String.format("severity-%s", severity);
+ }
+
+ public static String getTypeState(Type type, Status state, Severity severity) {
+ if (Severity.Unrated == severity) {
+ return state.toString() + " " + type.toString();
+ }
+ return state.toString() + " " + type.toString() + ", " + severity.toString();
}
public static String getLozengeClass(Status status, boolean subtle) {
diff --git a/src/main/java/com/gitblit/wicket/WicketUtils.java b/src/main/java/com/gitblit/wicket/WicketUtils.java
index d47390d..99c1e08 100644
--- a/src/main/java/com/gitblit/wicket/WicketUtils.java
+++ b/src/main/java/com/gitblit/wicket/WicketUtils.java
@@ -29,12 +29,14 @@
import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
import org.apache.wicket.Request;
+import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.behavior.HeaderContributor;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.IHeaderContributor;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.image.ContextImage;
+import org.apache.wicket.model.Model;
import org.apache.wicket.protocol.http.WebRequest;
import org.apache.wicket.resource.ContextRelativeResource;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
@@ -46,6 +48,7 @@
import com.gitblit.Keys;
import com.gitblit.models.FederationModel;
import com.gitblit.models.Metric;
+import com.gitblit.utils.DiffUtils.DiffComparator;
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
@@ -56,6 +59,10 @@
container.add(new SimpleAttributeModifier("class", value));
}
+ public static void addCssClass(Component container, String value) {
+ container.add(new AttributeAppender("class", new Model<String>(value), " "));
+ }
+
public static void setCssStyle(Component container, String value) {
container.add(new SimpleAttributeModifier("style", value));
}
@@ -66,8 +73,8 @@
container.add(new SimpleAttributeModifier("style", background));
}
- public static void setHtmlTooltip(Component container, String value) {
- container.add(new SimpleAttributeModifier("title", value));
+ public static Component setHtmlTooltip(Component container, String value) {
+ return container.add(new SimpleAttributeModifier("title", value));
}
public static void setInputPlaceholder(Component container, String value) {
@@ -324,6 +331,31 @@
return new PageParameters(parameterMap);
}
+ public static PageParameters newDiffParameter(String repositoryName,
+ String objectId, DiffComparator diffComparator) {
+ Map<String, String> parameterMap = new HashMap<String, String>();
+ if (StringUtils.isEmpty(objectId)) {
+ return newRepositoryParameter(repositoryName);
+ }
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("h", objectId);
+ parameterMap.put("w", "" + diffComparator.ordinal());
+ return new PageParameters(parameterMap);
+ }
+
+ public static PageParameters newDiffParameter(String repositoryName,
+ String objectId, DiffComparator diffComparator, String blobPath) {
+ Map<String, String> parameterMap = new HashMap<String, String>();
+ if (StringUtils.isEmpty(objectId)) {
+ return newRepositoryParameter(repositoryName);
+ }
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("h", objectId);
+ parameterMap.put("w", "" + diffComparator.ordinal());
+ parameterMap.put("f", blobPath);
+ return new PageParameters(parameterMap);
+ }
+
public static PageParameters newRangeParameter(String repositoryName,
String startRange, String endRange) {
Map<String, String> parameterMap = new HashMap<String, String>();
@@ -385,6 +417,19 @@
return new PageParameters(parameterMap);
}
+ public static PageParameters newFilestorePageParameter(int pageNumber, String filter) {
+ Map<String, String> parameterMap = new HashMap<String, String>();
+
+ if (pageNumber > 1) {
+ parameterMap.put("pg", String.valueOf(pageNumber));
+ }
+ if (filter != null) {
+ parameterMap.put("s", String.valueOf(filter));
+ }
+
+ return new PageParameters(parameterMap);
+ }
+
public static PageParameters newBlobDiffParameter(String repositoryName,
String baseCommitId, String commitId, String path) {
Map<String, String> parameterMap = new HashMap<String, String>();
@@ -488,6 +533,11 @@
return params.getString("st", null);
}
+ public static DiffComparator getDiffComparator(PageParameters params) {
+ int ordinal = params.getInt("w", 0);
+ return DiffComparator.values()[ordinal];
+ }
+
public static int getPage(PageParameters params) {
// index from 1
return params.getInt("pg", 1);
diff --git a/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm
index 1512ada..43816cf 100644
--- a/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm
+++ b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm
@@ -9,7 +9,13 @@
</div>
<div ng-repeat="item in ${ngList} | filter:query" style="padding: 3px;border-top: 1px solid #ddd;">
- <b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc"> </span></span></b>
+ <span style="color:{{item.c}};padding-right:2px;">
+ <span ng-show="item.y == 0" class="octicon octicon-centered octicon-repo"></span>
+ <span ng-show="item.y == 1" class="octicon octicon-centered octicon-repo-forked"></span>
+ <span ng-show="item.y == 2" class="octicon octicon-centered octicon-mirror"></span>
+ <span ng-show="item.y == 3" class="octicon octicon-centered octicon-repo-push"></span>
+ </span>
+
<a href="summary/?r={{item.r}}" title="{{item.i}}">{{item.p}}<b>{{item.n}}</b></a>
<span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>
<span ng-show="item.s" class="pull-right">
diff --git a/src/main/java/com/gitblit/wicket/pages/BasePage.html b/src/main/java/com/gitblit/wicket/pages/BasePage.html
index 89a28b8..b998428 100644
--- a/src/main/java/com/gitblit/wicket/pages/BasePage.html
+++ b/src/main/java/com/gitblit/wicket/pages/BasePage.html
@@ -15,6 +15,7 @@
<link rel="stylesheet" href="bootstrap/css/bootstrap.css"/>
<link rel="stylesheet" href="bootstrap/css/iconic.css"/>
<link rel="stylesheet" href="fontawesome/css/font-awesome.min.css"/>
+ <link rel="stylesheet" href="octicons/octicons.css"/>
<link rel="stylesheet" type="text/css" href="gitblit.css"/>
</wicket:head>
@@ -50,5 +51,6 @@
<!-- Include scripts at end for faster page loading -->
<script type="text/javascript" src="bootstrap/js/jquery.js"></script>
<script type="text/javascript" src="bootstrap/js/bootstrap.js"></script>
+ <wicket:container wicket:id="bottomScripts"></wicket:container>
</body>
</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/BasePage.java b/src/main/java/com/gitblit/wicket/pages/BasePage.java
index b696700..0d99f5e 100644
--- a/src/main/java/com/gitblit/wicket/pages/BasePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BasePage.java
@@ -42,6 +42,8 @@
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.markup.html.resources.JavascriptResourceReference;
+import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.protocol.http.RequestUtils;
import org.apache.wicket.protocol.http.WebResponse;
import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
@@ -242,7 +244,7 @@
protected void setupPage(String repositoryName, String pageName) {
add(new Label("title", getPageTitle(repositoryName)));
-
+ getBottomScriptContainer();
String rootLinkUrl = app().settings().getString(Keys.web.rootLink, urlFor(GitBlitWebApp.get().getHomePage(), null).toString());
ExternalLink rootLink = new ExternalLink("rootLink", rootLinkUrl);
WicketUtils.setHtmlTooltip(rootLink, app().settings().getString(Keys.web.siteName, Constants.NAME));
@@ -506,4 +508,40 @@
return sb.toString();
}
+ private RepeatingView getBottomScriptContainer() {
+ RepeatingView bottomScriptContainer = (RepeatingView) get("bottomScripts");
+ if (bottomScriptContainer == null) {
+ bottomScriptContainer = new RepeatingView("bottomScripts");
+ bottomScriptContainer.setRenderBodyOnly(true);
+ add(bottomScriptContainer);
+ }
+ return bottomScriptContainer;
+ }
+
+ /**
+ * Adds a HTML script element loading the javascript designated by the given path.
+ *
+ * @param scriptPath
+ * page-relative path to the Javascript resource; normally starts with "scripts/"
+ */
+ protected void addBottomScript(String scriptPath) {
+ RepeatingView bottomScripts = getBottomScriptContainer();
+ Label script = new Label(bottomScripts.newChildId(), "<script type='text/javascript' src='"
+ + urlFor(new JavascriptResourceReference(this.getClass(), scriptPath)) + "'></script>\n");
+ bottomScripts.add(script.setEscapeModelStrings(false).setRenderBodyOnly(true));
+ }
+
+ /**
+ * Adds a HTML script element containing the given code.
+ *
+ * @param code
+ * inline script code
+ */
+ protected void addBottomScriptInline(String code) {
+ RepeatingView bottomScripts = getBottomScriptContainer();
+ Label script = new Label(bottomScripts.newChildId(),
+ "<script type='text/javascript'>/*<![CDATA[*/\n" + code + "\n//]]>\n</script>\n");
+ bottomScripts.add(script.setEscapeModelStrings(false).setRenderBodyOnly(true));
+ }
+
}
diff --git a/src/main/java/com/gitblit/wicket/pages/BlamePage.java b/src/main/java/com/gitblit/wicket/pages/BlamePage.java
index 3c850f2..2fcca0a 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlamePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BlamePage.java
@@ -32,6 +32,7 @@
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
@@ -93,9 +94,34 @@
final BlameType activeBlameType = BlameType.get(blameTypeParam);
RevCommit commit = getCommit();
-
- add(new BookmarkablePageLink<Void>("blobLink", BlobPage.class,
- WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
+
+ PathModel pathModel = null;
+
+ List<PathModel> paths = JGitUtils.getFilesInPath(getRepository(), StringUtils.getRootPath(blobPath), commit);
+ for (PathModel path : paths) {
+ if (path.path.equals(blobPath)) {
+ pathModel = path;
+ break;
+ }
+ }
+
+ if (pathModel == null) {
+ final String notFound = MessageFormat.format("Blame page failed to find {0} in {1} @ {2}",
+ blobPath, repositoryName, objectId);
+ logger.error(notFound);
+ add(new Label("annotation").setVisible(false));
+ add(new Label("missingBlob", missingBlob(blobPath, commit)).setEscapeModelStrings(false));
+ return;
+ }
+
+ if (pathModel.isFilestoreItem()) {
+ String rawUrl = JGitUtils.getLfsRepositoryUrl(getContextUrl(), repositoryName, pathModel.getFilestoreOid());
+ add(new ExternalLink("blobLink", rawUrl));
+ } else {
+ add(new BookmarkablePageLink<Void>("blobLink", BlobPage.class,
+ WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
+ }
+
add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class,
WicketUtils.newObjectParameter(repositoryName, objectId)));
add(new BookmarkablePageLink<Void>("commitDiffLink", CommitDiffPage.class,
@@ -134,26 +160,13 @@
final DateFormat df = new SimpleDateFormat(format);
df.setTimeZone(getTimeZone());
- PathModel pathModel = null;
- List<PathModel> paths = JGitUtils.getFilesInPath(getRepository(), StringUtils.getRootPath(blobPath), commit);
- for (PathModel path : paths) {
- if (path.path.equals(blobPath)) {
- pathModel = path;
- break;
- }
- }
+
- if (pathModel == null) {
- final String notFound = MessageFormat.format("Blame page failed to find {0} in {1} @ {2}",
- blobPath, repositoryName, objectId);
- logger.error(notFound);
- add(new Label("annotation").setVisible(false));
- add(new Label("missingBlob", missingBlob(blobPath, commit)).setEscapeModelStrings(false));
- return;
- }
+
add(new Label("missingBlob").setVisible(false));
+ final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4);
List<AnnotatedLine> lines = DiffUtils.blame(getRepository(), blobPath, objectId);
final Map<?, String> colorMap = initializeColors(activeBlameType, lines);
ListDataProvider<AnnotatedLine> blameDp = new ListDataProvider<AnnotatedLine>(lines);
@@ -212,7 +225,7 @@
color = colorMap.get(entry.commitId);
break;
}
- Component data = new Label("data", StringUtils.escapeForHtml(entry.data, true)).setEscapeModelStrings(false);
+ Component data = new Label("data", StringUtils.escapeForHtml(entry.data, true, tabLength)).setEscapeModelStrings(false);
data.add(new SimpleAttributeModifier("style", "background-color: " + color + ";"));
item.add(data);
}
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html
index c633642..d218436 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html
@@ -9,7 +9,7 @@
<!-- blob nav links -->
<div class="page_nav2">
- <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="commitDiffLink"><wicket:message key="gb.commitdiff"></wicket:message></a>
+ <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="commitDiffLink"><wicket:message key="gb.commitdiff"></wicket:message></a> | <a wicket:id="whitespaceLink"></a>
</div>
<!-- commit header -->
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
index 9cc3eae..adf815e 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
@@ -15,13 +15,17 @@
*/
package com.gitblit.wicket.pages;
+import java.util.List;
+
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
+import com.gitblit.Keys;
import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.DiffUtils.DiffComparator;
import com.gitblit.utils.DiffUtils.DiffOutputType;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
@@ -29,6 +33,7 @@
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.CommitHeaderPanel;
+import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
@CacheControl(LastModified.BOOT)
@@ -39,20 +44,34 @@
final String blobPath = WicketUtils.getPath(params);
final String baseObjectId = WicketUtils.getBaseObjectId(params);
+ final DiffComparator diffComparator = WicketUtils.getDiffComparator(params);
Repository r = getRepository();
RevCommit commit = getCommit();
+ final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions);
+
String diff;
if (StringUtils.isEmpty(baseObjectId)) {
// use first parent
- diff = DiffUtils.getDiff(r, commit, blobPath, DiffOutputType.HTML).content;
+ RevCommit parent = commit.getParentCount() == 0 ? null : commit.getParent(0);
+ ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,
+ parent.getName(), commit.getName(), imageExtensions);
+ diff = DiffUtils.getDiff(r, commit, blobPath, diffComparator, DiffOutputType.HTML, handler, 3).content;
+ if (handler.getImgDiffCount() > 0) {
+ addBottomScript("scripts/imgdiff.js"); // Tiny support script for image diffs
+ }
add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
} else {
// base commit specified
RevCommit baseCommit = JGitUtils.getCommit(r, baseObjectId);
- diff = DiffUtils.getDiff(r, baseCommit, commit, blobPath, DiffOutputType.HTML).content;
+ ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,
+ baseCommit.getName(), commit.getName(), imageExtensions);
+ diff = DiffUtils.getDiff(r, baseCommit, commit, blobPath, diffComparator, DiffOutputType.HTML, handler, 3).content;
+ if (handler.getImgDiffCount() > 0) {
+ addBottomScript("scripts/imgdiff.js"); // Tiny support script for image diffs
+ }
add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
WicketUtils.newBlobDiffParameter(repositoryName, baseObjectId, objectId,
blobPath)));
@@ -62,6 +81,8 @@
WicketUtils.newObjectParameter(repositoryName, objectId)));
add(new BookmarkablePageLink<Void>("commitDiffLink", CommitDiffPage.class,
WicketUtils.newObjectParameter(repositoryName, objectId)));
+ add(new LinkPanel("whitespaceLink", null, getString(diffComparator.getOpposite().getTranslationKey()),
+ BlobDiffPage.class, WicketUtils.newDiffParameter(repositoryName, objectId, diffComparator.getOpposite(), blobPath)));
// diff page links
add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobPage.html b/src/main/java/com/gitblit/wicket/pages/BlobPage.html
index 3f9a959..289c149 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlobPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/BlobPage.html
@@ -40,9 +40,8 @@
</wicket:link>
</wicket:head>
+<body>
<wicket:extend>
-<!-- need to specify body.onload -->
-<body onload="prettyPrint()">
<!-- blob nav links -->
<div class="page_nav2">
@@ -61,6 +60,6 @@
<!-- blob image -->
<img wicket:id="blobImage" style="padding-top:5px;"></img>
-</body>
</wicket:extend>
+</body>
</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobPage.java b/src/main/java/com/gitblit/wicket/pages/BlobPage.java
index 3c244f9..1ef8f22 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlobPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BlobPage.java
@@ -137,6 +137,7 @@
table = missingBlob(blobPath, commit);
} else {
table = generateSourceView(source, extension, type == 1);
+ addBottomScriptInline("jQuery(prettyPrint);");
}
add(new Label("blobText", table).setEscapeModelStrings(false));
add(new Image("blobImage").setVisible(false));
@@ -150,6 +151,7 @@
table = missingBlob(blobPath, commit);
} else {
table = generateSourceView(source, null, false);
+ addBottomScriptInline("jQuery(prettyPrint);");
}
add(new Label("blobText", table).setEscapeModelStrings(false));
add(new Image("blobImage").setVisible(false));
@@ -193,7 +195,8 @@
} else {
sb.append("<pre class=\"plainprint\">");
}
- lines = StringUtils.escapeForHtml(source, true).split("\n");
+ final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4);
+ lines = StringUtils.escapeForHtml(source, true, tabLength).split("\n");
sb.append("<table width=\"100%\"><tbody>");
diff --git a/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java b/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java
index a6aca22..259a4bf 100644
--- a/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java
@@ -19,7 +19,6 @@
import org.apache.wicket.RestartResponseException;
import org.apache.wicket.markup.html.form.Button;
-import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.form.StatelessForm;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
@@ -31,6 +30,7 @@
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.NonTrimmedPasswordTextField;
public class ChangePasswordPage extends RootSubPage {
@@ -114,10 +114,10 @@
setResponsePage(RepositoriesPage.class);
}
};
- PasswordTextField passwordField = new PasswordTextField("password", password);
+ NonTrimmedPasswordTextField passwordField = new NonTrimmedPasswordTextField("password", password);
passwordField.setResetPassword(false);
form.add(passwordField);
- PasswordTextField confirmPasswordField = new PasswordTextField("confirmPassword",
+ NonTrimmedPasswordTextField confirmPasswordField = new NonTrimmedPasswordTextField("confirmPassword",
confirmPassword);
confirmPasswordField.setResetPassword(false);
form.add(confirmPasswordField);
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html
index 2c35a28..20bf19f 100644
--- a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html
@@ -9,7 +9,7 @@
<!-- commitdiff nav links -->
<div class="page_nav2">
- <wicket:message key="gb.parent"></wicket:message>: <span wicket:id="parentLink">[parent link]</span> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a>
+ <wicket:message key="gb.parent"></wicket:message>: <span wicket:id="parentLink">[parent link]</span> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="whitespaceLink"></a>
</div>
<!-- commit header -->
@@ -45,6 +45,7 @@
<td class="changeType"><span wicket:id="changeType">[change type]</span></td>
<td class="path"><span wicket:id="pathName">[commit path]</span></td>
<td class="hidden-phone rightAlign">
+ <span wicket:id="filestore" style="margin-right:20px;" class="fa fa-fw fa-external-link-square filestore-item"></span>
<span class="hidden-tablet" style="padding-right:20px;" wicket:id="diffStat"></span>
<span class="link">
<span class="hidden-tablet"><a wicket:id="patch"><wicket:message key="gb.patch"></wicket:message></a> | </span><a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a><span class="hidden-tablet"> | <a wicket:id="raw"><wicket:message key="gb.raw"></wicket:message></a></span> | <a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a>
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
index d827c44..f127517 100644
--- a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
@@ -15,6 +15,7 @@
*/
package com.gitblit.wicket.pages;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -23,29 +24,37 @@
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.apache.wicket.request.target.resource.ResourceStreamRequestTarget;
+import org.apache.wicket.util.resource.AbstractResourceStreamWriter;
+import org.apache.wicket.util.resource.IResourceStream;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import com.gitblit.Constants;
+import com.gitblit.Keys;
import com.gitblit.models.GitNote;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.models.SubmoduleModel;
+import com.gitblit.models.UserModel;
import com.gitblit.servlet.RawServlet;
import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.DiffUtils.DiffComparator;
import com.gitblit.utils.DiffUtils.DiffOutput;
import com.gitblit.utils.DiffUtils.DiffOutputType;
import com.gitblit.utils.JGitUtils;
import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.CommitHeaderPanel;
import com.gitblit.wicket.panels.CommitLegendPanel;
import com.gitblit.wicket.panels.DiffStatPanel;
-import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.AvatarImage;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.RefsPanel;
@@ -55,11 +64,9 @@
public CommitDiffPage(PageParameters params) {
super(params);
- Repository r = getRepository();
-
- RevCommit commit = getCommit();
-
- final DiffOutput diff = DiffUtils.getCommitDiff(r, commit, DiffOutputType.HTML);
+ final Repository r = getRepository();
+ final RevCommit commit = getCommit();
+ final DiffComparator diffComparator = WicketUtils.getDiffComparator(params);
List<String> parents = new ArrayList<String>();
if (commit.getParentCount() > 0) {
@@ -79,9 +86,20 @@
WicketUtils.newObjectParameter(repositoryName, objectId)));
add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class,
WicketUtils.newObjectParameter(repositoryName, objectId)));
+ add(new LinkPanel("whitespaceLink", null, getString(diffComparator.getOpposite().getTranslationKey()),
+ CommitDiffPage.class, WicketUtils.newDiffParameter(repositoryName, objectId, diffComparator.getOpposite())));
add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
+ final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions);
+ final ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,
+ parents.isEmpty() ? null : parents.get(0), commit.getName(), imageExtensions);
+ final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4);
+ final DiffOutput diff = DiffUtils.getCommitDiff(r, commit, diffComparator, DiffOutputType.HTML, handler, tabLength);
+ if (handler.getImgDiffCount() > 0) {
+ addBottomScript("scripts/imgdiff.js"); // Tiny support script for image diffs
+ }
+
// add commit diffstat
int insertions = 0;
int deletions = 0;
@@ -105,7 +123,7 @@
item.add(new RefsPanel("refName", repositoryName, Arrays.asList(entry.notesRef)));
item.add(createPersonPanel("authorName", entry.notesRef.getAuthorIdent(),
Constants.SearchType.AUTHOR));
- item.add(new GravatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent()));
+ item.add(new AvatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent()));
item.add(WicketUtils.createTimestampLabel("authorDate", entry.notesRef
.getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils()));
item.add(new Label("noteContent", bugtraqProcessor().processPlainCommitMessage(getRepository(), repositoryName,
@@ -124,14 +142,18 @@
@Override
public void populateItem(final Item<PathChangeModel> item) {
final PathChangeModel entry = item.getModelObject();
+
Label changeType = new Label("changeType", "");
WicketUtils.setChangeTypeCssClass(changeType, entry.changeType);
setChangeTypeTooltip(changeType, entry.changeType);
item.add(changeType);
item.add(new DiffStatPanel("diffStat", entry.insertions, entry.deletions, true));
+ item.add(WicketUtils.setHtmlTooltip(new Label("filestore", ""), getString("gb.filestore"))
+ .setVisible(entry.isFilestoreItem()));
boolean hasSubmodule = false;
String submodulePath = null;
+
if (entry.isTree()) {
// tree
item.add(new LinkPanel("pathName", null, entry.path, TreePage.class,
@@ -145,10 +167,37 @@
hasSubmodule = submodule.hasSubmodule;
// add relative link
- item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#" + entry.path));
+ item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#n" + entry.objectId));
} else {
// add relative link
- item.add(new LinkPanel("pathName", "list", entry.path, "#" + entry.path));
+ if (entry.isFilestoreItem()) {
+
+ item.add(new LinkPanel("pathName", "list", entry.path, new Link<Object>("link", null) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick() {
+ IResourceStream resourceStream = new AbstractResourceStreamWriter() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void write(OutputStream output) {
+ UserModel user = GitBlitWebSession.get().getUser();
+ user = user == null ? UserModel.ANONYMOUS : user;
+
+ app().filestore().downloadBlob(entry.getFilestoreOid(), user, getRepositoryModel(), output);
+ }
+ };
+
+ getRequestCycle().setRequestTarget(new ResourceStreamRequestTarget(resourceStream, entry.path));
+ }}));
+ }
+ else
+ {
+ item.add(new LinkPanel("pathName", "list", entry.path, "#n" + entry.objectId));
+ }
}
// quick links
@@ -168,12 +217,64 @@
.newPathParameter(repositoryName, entry.commitId, entry.path))
.setEnabled(!entry.changeType.equals(ChangeType.ADD)
&& !entry.changeType.equals(ChangeType.DELETE)));
- item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
- .newPathParameter(repositoryName, entry.commitId, entry.path))
- .setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
- String rawUrl = RawServlet.asLink(getContextUrl(), repositoryName, entry.commitId, entry.path);
- item.add(new ExternalLink("raw", rawUrl)
- .setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
+
+ if (entry.isFilestoreItem()) {
+ item.add(new Link<Object>("view", null) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick() {
+
+ IResourceStream resourceStream = new AbstractResourceStreamWriter() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void write(OutputStream output) {
+ UserModel user = GitBlitWebSession.get().getUser();
+ user = user == null ? UserModel.ANONYMOUS : user;
+
+ app().filestore().downloadBlob(entry.getFilestoreOid(), user, getRepositoryModel(), output);
+ }
+ };
+
+
+ getRequestCycle().setRequestTarget(new ResourceStreamRequestTarget(resourceStream, entry.path));
+ }});
+
+ item.add(new Link<Object>("raw", null) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick() {
+
+ IResourceStream resourceStream = new AbstractResourceStreamWriter() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void write(OutputStream output) {
+ UserModel user = GitBlitWebSession.get().getUser();
+ user = user == null ? UserModel.ANONYMOUS : user;
+
+ app().filestore().downloadBlob(entry.getFilestoreOid(), user, getRepositoryModel(), output);
+ }
+ };
+
+
+ getRequestCycle().setRequestTarget(new ResourceStreamRequestTarget(resourceStream, entry.path));
+ }});
+ } else {
+ item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
+ .newPathParameter(repositoryName, entry.commitId, entry.path))
+ .setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
+
+ item.add(new ExternalLink("raw", RawServlet.asLink(getContextUrl(), repositoryName, entry.commitId, entry.path))
+ .setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
+ }
+
item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
.newPathParameter(repositoryName, entry.commitId, entry.path))
.setEnabled(!entry.changeType.equals(ChangeType.ADD)
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitPage.html b/src/main/java/com/gitblit/wicket/pages/CommitPage.html
index 2aa10f2..2cfbbea 100644
--- a/src/main/java/com/gitblit/wicket/pages/CommitPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/CommitPage.html
@@ -77,8 +77,9 @@
<table class="pretty">
<tr wicket:id="changedPath">
<td class="changeType"><span wicket:id="changeType">[change type]</span></td>
- <td class="path"><span wicket:id="pathName">[commit path]</span></td>
+ <td class="path"><span wicket:id="pathName">[commit path]</span></td>
<td class="hidden-phone rightAlign">
+ <span wicket:id="filestore" style="margin-right:20px;" class="fa fa-fw fa-external-link-square filestore-item"></span>
<span class="hidden-tablet" style="padding-right:20px;" wicket:id="diffStat"></span>
<span class="link">
<a wicket:id="diff"><wicket:message key="gb.diff"></wicket:message></a> | <span class="hidden-tablet"><a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="raw"><wicket:message key="gb.raw"></wicket:message></a> | </span><a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a>
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitPage.java b/src/main/java/com/gitblit/wicket/pages/CommitPage.java
index 072bb20..3998204 100644
--- a/src/main/java/com/gitblit/wicket/pages/CommitPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/CommitPage.java
@@ -15,6 +15,7 @@
*/
package com.gitblit.wicket.pages;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -23,10 +24,14 @@
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.request.target.resource.ResourceStreamRequestTarget;
+import org.apache.wicket.util.resource.AbstractResourceStreamWriter;
+import org.apache.wicket.util.resource.IResourceStream;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -35,16 +40,18 @@
import com.gitblit.models.GitNote;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.models.SubmoduleModel;
+import com.gitblit.models.UserModel;
import com.gitblit.servlet.RawServlet;
import com.gitblit.utils.JGitUtils;
import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.CommitHeaderPanel;
import com.gitblit.wicket.panels.CommitLegendPanel;
import com.gitblit.wicket.panels.CompressedDownloadsPanel;
import com.gitblit.wicket.panels.DiffStatPanel;
-import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.AvatarImage;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.RefsPanel;
@@ -133,7 +140,7 @@
item.add(new RefsPanel("refName", repositoryName, Arrays.asList(entry.notesRef)));
item.add(createPersonPanel("authorName", entry.notesRef.getAuthorIdent(),
Constants.SearchType.AUTHOR));
- item.add(new GravatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent()));
+ item.add(new AvatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent()));
item.add(WicketUtils.createTimestampLabel("authorDate", entry.notesRef
.getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils()));
item.add(new Label("noteContent", bugtraqProcessor().processPlainCommitMessage(getRepository(), repositoryName,
@@ -163,11 +170,14 @@
@Override
public void populateItem(final Item<PathChangeModel> item) {
final PathChangeModel entry = item.getModelObject();
+
Label changeType = new Label("changeType", "");
WicketUtils.setChangeTypeCssClass(changeType, entry.changeType);
setChangeTypeTooltip(changeType, entry.changeType);
item.add(changeType);
item.add(new DiffStatPanel("diffStat", entry.insertions, entry.deletions, true));
+ item.add(WicketUtils.setHtmlTooltip(new Label("filestore", ""), getString("gb.filestore"))
+ .setVisible(entry.isFilestoreItem()));
boolean hasSubmodule = false;
String submodulePath = null;
@@ -194,9 +204,37 @@
path = JGitUtils.getStringContent(getRepository(), getCommit().getTree(), path);
displayPath = entry.path + " -> " + path;
}
- item.add(new LinkPanel("pathName", "list", displayPath, BlobPage.class,
- WicketUtils
- .newPathParameter(repositoryName, entry.commitId, path)));
+
+ if (entry.isFilestoreItem()) {
+ item.add(new LinkPanel("pathName", "list", entry.path, new Link<Object>("link", null) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick() {
+
+ IResourceStream resourceStream = new AbstractResourceStreamWriter() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void write(OutputStream output) {
+ UserModel user = GitBlitWebSession.get().getUser();
+ user = user == null ? UserModel.ANONYMOUS : user;
+
+ app().filestore().downloadBlob(entry.getFilestoreOid(), user, getRepositoryModel(), output);
+ }
+ };
+
+
+ getRequestCycle().setRequestTarget(new ResourceStreamRequestTarget(resourceStream, entry.path));
+ }}));
+
+
+ } else {
+ item.add(new LinkPanel("pathName", "list", displayPath, BlobPage.class,
+ WicketUtils.newPathParameter(repositoryName, entry.commitId, path)));
+ }
}
@@ -220,12 +258,64 @@
.newPathParameter(repositoryName, entry.commitId, entry.path))
.setEnabled(!entry.changeType.equals(ChangeType.ADD)
&& !entry.changeType.equals(ChangeType.DELETE)));
- item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
- .newPathParameter(repositoryName, entry.commitId, entry.path))
- .setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
- String rawUrl = RawServlet.asLink(getContextUrl(), repositoryName, entry.commitId, entry.path);
- item.add(new ExternalLink("raw", rawUrl)
- .setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
+
+ if (entry.isFilestoreItem()) {
+ item.add(new Link<Object>("view", null) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick() {
+
+ IResourceStream resourceStream = new AbstractResourceStreamWriter() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void write(OutputStream output) {
+ UserModel user = GitBlitWebSession.get().getUser();
+ user = user == null ? UserModel.ANONYMOUS : user;
+
+ app().filestore().downloadBlob(entry.getFilestoreOid(), user, getRepositoryModel(), output);
+ }
+ };
+
+
+ getRequestCycle().setRequestTarget(new ResourceStreamRequestTarget(resourceStream, entry.path));
+ }});
+
+ item.add(new Link<Object>("raw", null) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick() {
+
+ IResourceStream resourceStream = new AbstractResourceStreamWriter() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void write(OutputStream output) {
+ UserModel user = GitBlitWebSession.get().getUser();
+ user = user == null ? UserModel.ANONYMOUS : user;
+
+ app().filestore().downloadBlob(entry.getFilestoreOid(), user, getRepositoryModel(), output);
+ }
+ };
+
+
+ getRequestCycle().setRequestTarget(new ResourceStreamRequestTarget(resourceStream, entry.path));
+ }});
+
+ } else {
+ item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
+ .newPathParameter(repositoryName, entry.commitId, entry.path))
+ .setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
+ String rawUrl = RawServlet.asLink(getContextUrl(), repositoryName, entry.commitId, entry.path);
+ item.add(new ExternalLink("raw", rawUrl)
+ .setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
+ }
item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
.newPathParameter(repositoryName, entry.commitId, entry.path))
.setEnabled(!entry.changeType.equals(ChangeType.ADD)
diff --git a/src/main/java/com/gitblit/wicket/pages/ComparePage.html b/src/main/java/com/gitblit/wicket/pages/ComparePage.html
index d7df717..74c9831 100644
--- a/src/main/java/com/gitblit/wicket/pages/ComparePage.html
+++ b/src/main/java/com/gitblit/wicket/pages/ComparePage.html
@@ -18,6 +18,7 @@
<select wicket:id="fromRef" class="span3" />
<i class="icon-arrow-right"></i>
<select wicket:id="toRef" class="span3" />
+ <label style="padding:0px 5px;" class="checkbox"><input type="checkbox" wicket:id="ignoreWhitespaceCheckbox" /> <span wicket:id="ignoreWhitespaceLabel"></span></label>
<button class="btn" type="submit"><wicket:message key="gb.compare"></wicket:message></button>
</form>
</div>
@@ -26,6 +27,7 @@
<input wicket:id="fromId" type="text" class="span3" />
<i class="icon-arrow-right"></i>
<input wicket:id="toId" type="text" class="span3" />
+ <label style="padding:0px 5px;" class="checkbox"><input type="checkbox" wicket:id="ignoreWhitespaceCheckbox" /> <span wicket:id="ignoreWhitespaceLabel"></span></label>
<button class="btn" type="submit"><wicket:message key="gb.compare"></wicket:message></button>
</form>
</div>
diff --git a/src/main/java/com/gitblit/wicket/pages/ComparePage.java b/src/main/java/com/gitblit/wicket/pages/ComparePage.java
index 1ec6613..7e7ac2f 100644
--- a/src/main/java/com/gitblit/wicket/pages/ComparePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ComparePage.java
@@ -21,6 +21,7 @@
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
@@ -37,12 +38,14 @@
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
+import com.gitblit.Keys;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.models.RefModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.SubmoduleModel;
import com.gitblit.servlet.RawServlet;
import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.DiffUtils.DiffComparator;
import com.gitblit.utils.DiffUtils.DiffOutput;
import com.gitblit.utils.DiffUtils.DiffOutputType;
import com.gitblit.utils.JGitUtils;
@@ -68,6 +71,8 @@
IModel<String> fromRefId = new Model<String>("");
IModel<String> toRefId = new Model<String>("");
+ IModel<Boolean> ignoreWhitespace = Model.of(true);
+
public ComparePage(PageParameters params) {
super(params);
Repository r = getRepository();
@@ -111,7 +116,15 @@
fromCommitId.setObject(startId);
toCommitId.setObject(endId);
- final DiffOutput diff = DiffUtils.getDiff(r, fromCommit, toCommit, DiffOutputType.HTML);
+ final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions);
+ final ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,
+ fromCommit.getName(), toCommit.getName(), imageExtensions);
+ final DiffComparator diffComparator = WicketUtils.getDiffComparator(params);
+ final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4);
+ final DiffOutput diff = DiffUtils.getDiff(r, fromCommit, toCommit, diffComparator, DiffOutputType.HTML, handler, tabLength);
+ if (handler.getImgDiffCount() > 0) {
+ addBottomScript("scripts/imgdiff.js"); // Tiny support script for image diffs
+ }
// add compare diffstat
int insertions = 0;
@@ -160,10 +173,10 @@
hasSubmodule = submodule.hasSubmodule;
// add relative link
- item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#" + entry.path));
+ item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#n" + entry.objectId));
} else {
// add relative link
- item.add(new LinkPanel("pathName", "list", entry.path, "#" + entry.path));
+ item.add(new LinkPanel("pathName", "list", entry.path, "#n" + entry.objectId));
}
// quick links
@@ -204,6 +217,10 @@
comparison.add(new Label("diffText", diff.content).setEscapeModelStrings(false));
}
+ // set the default DiffComparator
+ DiffComparator diffComparator = WicketUtils.getDiffComparator(params);
+ ignoreWhitespace.setObject(DiffComparator.IGNORE_WHITESPACE == diffComparator);
+
//
// ref selection form
//
@@ -215,8 +232,13 @@
public void onSubmit() {
String from = ComparePage.this.fromRefId.getObject();
String to = ComparePage.this.toRefId.getObject();
+ boolean ignoreWS = ignoreWhitespace.getObject();
PageParameters params = WicketUtils.newRangeParameter(repositoryName, from, to);
+ if (ignoreWS) {
+ params.put("w", 1);
+ }
+
String relativeUrl = urlFor(ComparePage.class, params).toString();
String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
@@ -237,6 +259,8 @@
}
refsForm.add(new DropDownChoice<String>("fromRef", fromRefId, refs).setEnabled(refs.size() > 0));
refsForm.add(new DropDownChoice<String>("toRef", toRefId, refs).setEnabled(refs.size() > 0));
+ refsForm.add(new Label("ignoreWhitespaceLabel", getString(DiffComparator.IGNORE_WHITESPACE.getTranslationKey())));
+ refsForm.add(new CheckBox("ignoreWhitespaceCheckbox", ignoreWhitespace));
add(refsForm);
//
@@ -250,8 +274,12 @@
public void onSubmit() {
String from = ComparePage.this.fromCommitId.getObject();
String to = ComparePage.this.toCommitId.getObject();
+ boolean ignoreWS = ignoreWhitespace.getObject();
PageParameters params = WicketUtils.newRangeParameter(repositoryName, from, to);
+ if (ignoreWS) {
+ params.put("w", 1);
+ }
String relativeUrl = urlFor(ComparePage.class, params).toString();
String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
@@ -265,6 +293,8 @@
TextField<String> toIdField = new TextField<String>("toId", toCommitId);
WicketUtils.setInputPlaceholder(toIdField, getString("gb.to") + "...");
idsForm.add(toIdField);
+ idsForm.add(new Label("ignoreWhitespaceLabel", getString(DiffComparator.IGNORE_WHITESPACE.getTranslationKey())));
+ idsForm.add(new CheckBox("ignoreWhitespaceCheckbox", ignoreWhitespace));
add(idsForm);
r.close();
diff --git a/src/main/java/com/gitblit/wicket/pages/DocPage.html b/src/main/java/com/gitblit/wicket/pages/DocPage.html
index 2bb7e1b..6df6f3a 100644
--- a/src/main/java/com/gitblit/wicket/pages/DocPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/DocPage.html
@@ -12,7 +12,7 @@
<div class="docs" style="margin-top: -10px;">
<!-- doc nav links -->
<div style="float: right;" class="docnav">
- <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a>
+ <a wicket:id="editLink"><wicket:message key="gb.edit"></wicket:message></a> | <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a>
</div>
<!-- document content -->
@@ -24,7 +24,7 @@
<div class="docs">
<!-- doc nav links -->
<div style="float: right;" class="docnav">
- <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a>
+ <a wicket:id="editLink"><wicket:message key="gb.edit"></wicket:message></a> | <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a>
</div>
<!-- document content -->
diff --git a/src/main/java/com/gitblit/wicket/pages/DocPage.java b/src/main/java/com/gitblit/wicket/pages/DocPage.java
index 5ed9c40..5d71134 100644
--- a/src/main/java/com/gitblit/wicket/pages/DocPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/DocPage.java
@@ -25,11 +25,13 @@
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
+import com.gitblit.models.UserModel;
import com.gitblit.servlet.RawServlet;
import com.gitblit.utils.BugtraqProcessor;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.MarkupProcessor;
import com.gitblit.wicket.MarkupProcessor.MarkupDocument;
@@ -44,7 +46,9 @@
final String path = WicketUtils.getPath(params).replace("%2f", "/").replace("%2F", "/");
MarkupProcessor processor = new MarkupProcessor(app().settings(), app().xssFilter());
-
+ UserModel currentUser = (GitBlitWebSession.get().getUser() != null) ? GitBlitWebSession.get().getUser() : UserModel.ANONYMOUS;
+ final boolean userCanEdit = currentUser.canEdit(getRepositoryModel());
+
Repository r = getRepository();
RevCommit commit = JGitUtils.getCommit(r, objectId);
String [] encodings = getEncodings();
@@ -85,6 +89,9 @@
}
// document page links
+ fragment.add(new BookmarkablePageLink<Void>("editLink", EditFilePage.class,
+ WicketUtils.newPathParameter(repositoryName, objectId, documentPath))
+ .setEnabled(userCanEdit));
fragment.add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
WicketUtils.newPathParameter(repositoryName, objectId, documentPath)));
fragment.add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
diff --git a/src/main/java/com/gitblit/wicket/pages/DocsPage.html b/src/main/java/com/gitblit/wicket/pages/DocsPage.html
index 2253205..54306b1 100644
--- a/src/main/java/com/gitblit/wicket/pages/DocsPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/DocsPage.html
@@ -20,7 +20,7 @@
<div wicket:id="tabContent" class="tab-pane">
<!-- doc nav links -->
<div style="float: right;" class="docnav">
- <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a>
+ <a wicket:id="editLink"><wicket:message key="gb.edit"></wicket:message></a> | <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a>
</div>
<div class="content" wicket:id="content"></div>
</div>
@@ -43,7 +43,7 @@
<td class="size"><span wicket:id="docSize">[doc size]</span></td>
<td class="treeLinks">
<span class="hidden-phone link">
- <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="raw"><wicket:message key="gb.raw"></wicket:message></a> | <a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a>
+ <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="edit"><wicket:message key="gb.edit"></wicket:message></a> | <a wicket:id="raw"><wicket:message key="gb.raw"></wicket:message></a> | <a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a>
</span>
</td>
</tr>
diff --git a/src/main/java/com/gitblit/wicket/pages/DocsPage.java b/src/main/java/com/gitblit/wicket/pages/DocsPage.java
index 8be9a57..5244386 100644
--- a/src/main/java/com/gitblit/wicket/pages/DocsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/DocsPage.java
@@ -27,15 +27,19 @@
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.apache.wicket.model.StringResourceModel;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import com.gitblit.models.PathModel;
+import com.gitblit.models.UserModel;
import com.gitblit.servlet.RawServlet;
import com.gitblit.utils.ByteFormat;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.MarkupProcessor;
import com.gitblit.wicket.MarkupProcessor.MarkupDocument;
@@ -54,6 +58,9 @@
MarkupProcessor processor = new MarkupProcessor(app().settings(), app().xssFilter());
Repository r = getRepository();
+ UserModel currentUser = (GitBlitWebSession.get().getUser() != null) ? GitBlitWebSession.get().getUser() : UserModel.ANONYMOUS;
+ final boolean userCanEdit = currentUser.canEdit(getRepositoryModel());
+
RevCommit head = JGitUtils.getCommit(r, objectId);
final String commitId = getBestCommitId(head);
@@ -102,7 +109,12 @@
@Override
public void populateItem(final Item<MarkupDocument> item) {
MarkupDocument doc = item.getModelObject();
- // document page links
+
+ item.add(new BookmarkablePageLink<Void>("editLink", EditFilePage.class,
+ WicketUtils.newPathParameter(repositoryName, commitId, doc.documentPath))
+ .setEnabled(userCanEdit));
+
+ // document page links
item.add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
WicketUtils.newPathParameter(repositoryName, commitId, doc.documentPath)));
item.add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
@@ -148,6 +160,9 @@
// links
item.add(new BookmarkablePageLink<Void>("view", DocPage.class, WicketUtils
.newPathParameter(repositoryName, commitId, entry.path)));
+ item.add(new BookmarkablePageLink<Void>("edit", EditFilePage.class, WicketUtils
+ .newPathParameter(repositoryName, commitId, entry.path))
+ .setEnabled(userCanEdit));
String rawUrl = RawServlet.asLink(getContextUrl(), repositoryName, commitId, entry.path);
item.add(new ExternalLink("raw", rawUrl));
item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
diff --git a/src/main/java/com/gitblit/wicket/pages/EditFilePage.html b/src/main/java/com/gitblit/wicket/pages/EditFilePage.html
new file mode 100644
index 0000000..d6b6eee
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/EditFilePage.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
+ xml:lang="en"
+ lang="en">
+
+<!-- contribute editor resources to the page header -->
+<wicket:head>
+ <link rel="stylesheet" href="gitblit-editor.min.css" />
+ <script type="text/javascript" src="gitblit-editor.min.js"></script>
+</wicket:head>
+
+<body>
+<wicket:extend>
+<div wicket:id="doc"></div>
+<wicket:fragment wicket:id="markupContent">
+ <div class="docs" style="margin-top: -10px;">
+
+ <!-- doc nav links -->
+ <div style="float: right;position: relative;z-index: 100;margin-top: 1px;border-radius: 0px 3px 0px 3px;" class="docnav">
+ <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a>
+ </div>
+ <div id="visualEditor"></div>
+ <form id="documentEditor" style="padding-top:5px;" wicket:id="documentEditor">
+ <textarea id="editor" wicket:id="content">[content]</textarea>
+
+ <div id="commitDialog" class="modal hide fade">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+ <h3>Commit Document Changes</h3>
+ </div>
+ <div class="modal-body">
+ <div wicket:id="commitAuthor"></div>
+ <textarea style="width:100%; resize:none" wicket:id="commitMessage"></textarea>
+ </div>
+ <div class="modal-footer">
+ <a href="#" data-dismiss="modal" class="btn"><wicket:message key="gb.continueEditing"></wicket:message></a>
+ <a href="#" onclick="commitChanges()" class="btn btn-primary"><wicket:message key="gb.commitChanges"></wicket:message></a>
+ </div>
+ </div>
+ </form>
+
+ </div>
+</wicket:fragment>
+
+<wicket:fragment wicket:id="plainContent">
+ <div class="docs">
+ <!-- doc nav links -->
+ <div style="float: right;" class="docnav">
+ <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a>
+ </div>
+
+ <!-- document content -->
+ <div wicket:id="content">[content]</div>
+ </div>
+</wicket:fragment>
+
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/EditFilePage.java b/src/main/java/com/gitblit/wicket/pages/EditFilePage.java
new file mode 100644
index 0000000..dbf8a79
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/EditFilePage.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2016 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.wicket.pages;
+
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.TextArea;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.model.Model;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.Constants;
+import com.gitblit.models.UserModel;
+import com.gitblit.servlet.RawServlet;
+import com.gitblit.utils.BugtraqProcessor;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.MarkupProcessor;
+import com.gitblit.wicket.MarkupProcessor.MarkupDocument;
+import com.gitblit.wicket.WicketUtils;
+
+@CacheControl(LastModified.REPOSITORY)
+public class EditFilePage extends RepositoryPage {
+
+ public EditFilePage(final PageParameters params) {
+ super(params);
+
+ final UserModel currentUser = (GitBlitWebSession.get().getUser() != null) ? GitBlitWebSession.get().getUser() : UserModel.ANONYMOUS;
+
+ final String path = WicketUtils.getPath(params).replace("%2f", "/").replace("%2F", "/");
+ MarkupProcessor processor = new MarkupProcessor(app().settings(), app().xssFilter());
+
+ Repository r = getRepository();
+ RevCommit commit = JGitUtils.getCommit(r, objectId);
+ String [] encodings = getEncodings();
+
+ // Read raw markup content and transform it to html
+ String documentPath = path;
+ String markupText = JGitUtils.getStringContent(r, commit.getTree(), path, encodings);
+
+ // Hunt for document
+ if (StringUtils.isEmpty(markupText)) {
+ String name = StringUtils.stripFileExtension(path);
+
+ List<String> docExtensions = processor.getAllExtensions();
+ for (String ext : docExtensions) {
+ String checkName = name + "." + ext;
+ markupText = JGitUtils.getStringContent(r, commit.getTree(), checkName, encodings);
+ if (!StringUtils.isEmpty(markupText)) {
+ // found it
+ documentPath = path;
+ break;
+ }
+ }
+ }
+
+ if (markupText == null) {
+ markupText = "";
+ }
+
+ BugtraqProcessor bugtraq = new BugtraqProcessor(app().settings());
+ markupText = bugtraq.processText(getRepository(), repositoryName, markupText);
+
+ Fragment fragment;
+ String displayedCommitId = commit.getId().getName();
+
+ if (currentUser.canEdit(getRepositoryModel()) && JGitUtils.isTip(getRepository(), objectId.toString())) {
+
+ final Model<String> documentContent = new Model<String>(markupText);
+ final Model<String> commitMessage = new Model<String>("Document update");
+ final Model<String> commitIdAtLoad = new Model<String>(displayedCommitId);
+
+ fragment = new Fragment("doc", "markupContent", this);
+
+ Form<Void> form = new Form<Void>("documentEditor") {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void onSubmit() {
+ final Repository repository = getRepository();
+ final String document = documentContent.getObject();
+ final String message = commitMessage.getObject();
+
+ final String branchName = JGitUtils.getBranch(getRepository(), objectId).getName();
+ final String authorEmail = StringUtils.isEmpty(currentUser.emailAddress) ? (currentUser.username + "@gitblit") : currentUser.emailAddress;
+
+ boolean success = false;
+
+ try {
+ ObjectId docAtLoad = getRepository().resolve(commitIdAtLoad.getObject());
+
+ logger.trace("Commiting Edit File page: " + commitIdAtLoad.getObject());
+
+ DirCache index = DirCache.newInCore();
+ DirCacheBuilder builder = index.builder();
+ byte[] bytes = document.getBytes( Constants.ENCODING );
+
+ final DirCacheEntry fileUpdate = new DirCacheEntry(path);
+ fileUpdate.setLength(bytes.length);
+ fileUpdate.setLastModified(System.currentTimeMillis());
+ fileUpdate.setFileMode(FileMode.REGULAR_FILE);
+ fileUpdate.setObjectId(repository.newObjectInserter().insert( org.eclipse.jgit.lib.Constants.OBJ_BLOB, bytes ));
+ builder.add(fileUpdate);
+
+ Set<String> ignorePaths = new HashSet<String>();
+ ignorePaths.add(path);
+
+ for (DirCacheEntry entry : JGitUtils.getTreeEntries(repository, branchName, ignorePaths)) {
+ builder.add(entry);
+ }
+
+ builder.finish();
+
+ final boolean forceCommit = false;
+
+ success = JGitUtils.commitIndex(repository, branchName, index, docAtLoad, forceCommit, currentUser.getDisplayName(), authorEmail, message);
+
+ } catch (IOException | ConcurrentRefUpdateException e) {
+ e.printStackTrace();
+ }
+
+ if (success == false) {
+ getSession().error(MessageFormat.format(getString("gb.fileNotMergeable"),path));
+ return;
+ }
+
+ getSession().info(MessageFormat.format(getString("gb.fileCommitted"),path));
+ setResponsePage(EditFilePage.class, params);
+ }
+ };
+
+ final TextArea<String> docIO = new TextArea<String>("content", documentContent);
+ docIO.setOutputMarkupId(false);
+
+ form.add(new Label("commitAuthor", String.format("%s <%s>", currentUser.getDisplayName(), currentUser.emailAddress)));
+ form.add(new TextArea<String>("commitMessage", commitMessage));
+
+
+ form.setOutputMarkupId(false);
+ form.add(docIO);
+
+ addBottomScriptInline("attachDocumentEditor(document.querySelector('textarea#editor'), $('#commitDialog'));");
+
+ fragment.add(form);
+
+ } else {
+
+ MarkupDocument markupDoc = processor.parse(repositoryName, displayedCommitId, documentPath, markupText);
+ final Model<String> documentContent = new Model<String>(markupDoc.html);
+
+ fragment = new Fragment("doc", "plainContent", this);
+
+ fragment.add(new Label("content", documentContent).setEscapeModelStrings(false));
+ }
+
+ // document page links
+ fragment.add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
+ WicketUtils.newPathParameter(repositoryName, objectId, documentPath)));
+ fragment.add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
+ WicketUtils.newPathParameter(repositoryName, objectId, documentPath)));
+ String rawUrl = RawServlet.asLink(getContextUrl(), repositoryName, objectId, documentPath);
+ fragment.add(new ExternalLink("rawLink", rawUrl));
+
+ add(fragment);
+
+ }
+
+ @Override
+ protected String getPageName() {
+ return getString("gb.editFile");
+ }
+
+ @Override
+ protected boolean isCommitPage() {
+ return true;
+ }
+
+ @Override
+ protected Class<? extends BasePage> getRepoNavPageClass() {
+ return EditFilePage.class;
+ }
+
+
+
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/EditMilestonePage.html b/src/main/java/com/gitblit/wicket/pages/EditMilestonePage.html
index 0897ebe..13c2638 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditMilestonePage.html
+++ b/src/main/java/com/gitblit/wicket/pages/EditMilestonePage.html
@@ -19,7 +19,7 @@
<!-- Edit Milestone Table -->
<table class="ticket">
<tr><th><wicket:message key="gb.milestone"></wicket:message></th><td class="edit"><input class="input-large" type="text" wicket:id="name" id="name"></input></td></tr>
- <tr><th><wicket:message key="gb.due"></wicket:message></th><td class="edit"><input class="input-large" type="text" wicket:id="due"></input> <span class="help-inline" wicket:id="dueFormat"></span></td></tr>
+ <tr><th><wicket:message key="gb.due"></wicket:message></th><td class="edit"><input class="input-large" type="date" wicket:id="due"></input> <span class="help-inline" wicket:id="dueFormat"></span></td></tr>
<tr><th><wicket:message key="gb.status"></wicket:message><span style="color:red;">*</span></th><td class="edit"><select class="input-large" wicket:id="status"></select></td></tr>
<tr><th></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="notify" /> <span class="help-inline"><wicket:message key="gb.notifyChangedOpenTickets"></wicket:message></span></label></td></tr>
</table>
diff --git a/src/main/java/com/gitblit/wicket/pages/EditMilestonePage.java b/src/main/java/com/gitblit/wicket/pages/EditMilestonePage.java
index 6e96526..dc32665 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditMilestonePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EditMilestonePage.java
@@ -24,7 +24,6 @@
import org.apache.wicket.RestartResponseException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
-import org.apache.wicket.extensions.markup.html.form.DateTextField;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.CheckBox;
@@ -42,6 +41,7 @@
import com.gitblit.tickets.TicketMilestone;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.Html5DateField;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.BasePanel.JavascriptEventConfirmation;
@@ -106,10 +106,12 @@
notificationModel = Model.of(true);
form.add(new TextField<String>("name", nameModel));
- form.add(new DateTextField("due", dueModel, "yyyy-MM-dd"));
+ form.add(new Html5DateField("due", dueModel, "yyyy-MM-dd"));
form.add(new Label("dueFormat", "yyyy-MM-dd"));
form.add(new CheckBox("notify", notificationModel));
-
+ addBottomScriptInline("{var e=document.createElement('input');e.type='date';if(e.type=='date'){$('[name=\"due\"]~.help-inline').hide()}}");
+ addBottomScript("scripts/wicketHtml5Patch.js");
+
List<Status> statusChoices = Arrays.asList(Status.Open, Status.Closed);
form.add(new DropDownChoice<TicketModel.Status>("status", statusModel, statusChoices));
diff --git a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
index 1e683b4..7a55b9f 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -20,6 +20,7 @@
<li><a href="#search" data-toggle="tab"><wicket:message key="gb.search"></wicket:message></a></li>
<li><a href="#gc" data-toggle="tab"><wicket:message key="gb.gc"></wicket:message></a></li>
<li><a href="#misc" data-toggle="tab"><wicket:message key="gb.miscellaneous"></wicket:message></a></li>
+ <li><a href="#admin" data-toggle="tab"><wicket:message key="gb.administration"></wicket:message></a></li>
</ul>
<!-- tab content -->
@@ -178,8 +179,16 @@
<div wicket:id="mailingLists"></div>
</div>
-
- <div class="form-actions"><input class="btn btn-appmenu" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" /> <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" /> <input class="btn btn-danger" type="submit" value="Delete" wicket:message="value:gb.delete" wicket:id="delete" /></div>
+
+ <!-- administration -->
+ <div class="tab-pane" id="admin">
+
+ <h4><wicket:message key="gb.deleteRepositoryHeader"></wicket:message></h4>
+ <p><wicket:message key="gb.deleteRepositoryDescription"></wicket:message></p>
+ <input class="btn btn-danger" type="submit" value="Delete" wicket:message="value:gb.delete" wicket:id="delete" />
+ </div>
+
+ <div class="form-actions"><input class="btn btn-appmenu" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" /> <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" /></div>
</div>
</div>
diff --git a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
index 318b2b7..6bcf6f5 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -446,7 +446,7 @@
form.add(new BooleanOption("acceptNewTickets",
getString("gb.acceptNewTickets"),
getString("gb.acceptNewTicketsDescription"),
- new PropertyModel<Boolean>(repositoryModel, "acceptNewPatchsets")));
+ new PropertyModel<Boolean>(repositoryModel, "acceptNewTickets")));
form.add(new BooleanOption("requireApproval",
getString("gb.requireApproval"),
diff --git a/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java b/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java
index f537f33..a43d8db 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java
@@ -39,6 +39,7 @@
import org.apache.wicket.model.util.ListModel;
import com.gitblit.Constants.RegistrantType;
+import com.gitblit.Constants.Role;
import com.gitblit.GitBlitException;
import com.gitblit.Keys;
import com.gitblit.models.RegistrantAccessPermission;
@@ -221,14 +222,23 @@
// do not let the browser pre-populate these fields
form.add(new SimpleAttributeModifier("autocomplete", "off"));
- // not all user services support manipulating team memberships
+ // not all user providers support manipulating team memberships
boolean editMemberships = app().authentication().supportsTeamMembershipChanges(teamModel);
+ // not all user providers support manipulating the admin role
+ boolean changeAdminRole = app().authentication().supportsRoleChanges(teamModel, Role.ADMIN);
+
+ // not all user providers support manipulating the create role
+ boolean changeCreateRole = app().authentication().supportsRoleChanges(teamModel, Role.CREATE);
+
+ // not all user providers support manipulating the fork role
+ boolean changeForkRole = app().authentication().supportsRoleChanges(teamModel, Role.FORK);
+
// field names reflective match TeamModel fields
form.add(new TextField<String>("name"));
- form.add(new CheckBox("canAdmin"));
- form.add(new CheckBox("canFork").setEnabled(app().settings().getBoolean(Keys.web.allowForking, true)));
- form.add(new CheckBox("canCreate"));
+ form.add(new CheckBox("canAdmin").setEnabled(changeAdminRole));
+ form.add(new CheckBox("canFork").setEnabled(app().settings().getBoolean(Keys.web.allowForking, true) && changeForkRole));
+ form.add(new CheckBox("canCreate").setEnabled(changeCreateRole));
form.add(users.setEnabled(editMemberships));
mailingLists = new Model<String>(teamModel.mailingLists == null ? ""
: StringUtils.flattenStrings(teamModel.mailingLists, " "));
diff --git a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html
index b5fe0ae..b12d0c7 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html
@@ -39,6 +39,8 @@
</div>
</td></tr>
<tr wicket:id="status"></tr>
+ <tr><th><wicket:message key="gb.severity"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="severity"></select></td></tr>
+ <tr wicket:id="priority"></tr>
<tr wicket:id="responsible"></tr>
<tr wicket:id="milestone"></tr>
<tr wicket:id="mergeto"></tr>
@@ -71,5 +73,9 @@
<th><wicket:message key="gb.mergeTo"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="mergeto"></select></td>
</wicket:fragment>
+<wicket:fragment wicket:id="priorityFragment">
+ <th><wicket:message key="gb.priority"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="priority"></select></td>
+</wicket:fragment>
+
</wicket:extend>
</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java
index c3d405b..192b48c 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java
@@ -84,6 +84,10 @@
private Label descriptionPreview;
+ private IModel<TicketModel.Priority> priorityModel;
+
+ private IModel<TicketModel.Severity> severityModel;
+
public EditTicketPage(PageParameters params) {
super(params);
@@ -117,6 +121,8 @@
milestoneModel = Model.of();
mergeToModel = Model.of(ticket.mergeTo == null ? getRepositoryModel().mergeTo : ticket.mergeTo);
statusModel = Model.of(ticket.status);
+ priorityModel = Model.of(ticket.priority);
+ severityModel = Model.of(ticket.severity);
setStatelessHint(false);
setOutputMarkupId(true);
@@ -161,6 +167,9 @@
status.add(new DropDownChoice<TicketModel.Status>("status", statusModel, statusChoices));
form.add(status);
+ List<TicketModel.Severity> severityChoices = Arrays.asList(TicketModel.Severity.choices());
+ form.add(new DropDownChoice<TicketModel.Severity>("severity", severityModel, severityChoices));
+
if (currentUser.canAdmin(ticket, getRepositoryModel())) {
// responsible
Set<String> userlist = new TreeSet<String>(ticket.getParticipants());
@@ -214,11 +223,17 @@
milestones.add(new TicketMilestone(NIL));
}
+ // milestone
Fragment milestone = new Fragment("milestone", "milestoneFragment", this);
-
milestone.add(new DropDownChoice<TicketMilestone>("milestone", milestoneModel, milestones));
form.add(milestone.setVisible(!milestones.isEmpty()));
+ // priority
+ Fragment priority = new Fragment("priority", "priorityFragment", this);
+ List<TicketModel.Priority> priorityChoices = Arrays.asList(TicketModel.Priority.choices());
+ priority.add(new DropDownChoice<TicketModel.Priority>("priority", priorityModel, priorityChoices));
+ form.add(priority);
+
// mergeTo (integration branch)
List<String> branches = new ArrayList<String>();
for (String branch : getRepositoryModel().getLocalBranches()) {
@@ -238,6 +253,7 @@
form.add(new Label("responsible").setVisible(false));
form.add(new Label("milestone").setVisible(false));
form.add(new Label("mergeto").setVisible(false));
+ form.add(new Label("priority").setVisible(false));
}
form.add(new AjaxButton("update") {
@@ -316,6 +332,18 @@
}
}
+ TicketModel.Priority priority = priorityModel.getObject();
+ if (!ticket.priority.equals(priority))
+ {
+ change.setField(Field.priority, priority);
+ }
+
+ TicketModel.Severity severity = severityModel.getObject();
+ if (!ticket.severity.equals(severity))
+ {
+ change.setField(Field.severity, severity);
+ }
+
String mergeTo = mergeToModel.getObject();
if ((StringUtils.isEmpty(ticket.mergeTo) && !StringUtils.isEmpty(mergeTo))
|| (!StringUtils.isEmpty(mergeTo) && !mergeTo.equals(ticket.mergeTo))) {
diff --git a/src/main/java/com/gitblit/wicket/pages/EditUserPage.java b/src/main/java/com/gitblit/wicket/pages/EditUserPage.java
index 454aa61..220bee3 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditUserPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EditUserPage.java
@@ -27,7 +27,6 @@
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.Form;
-import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.Model;
@@ -35,12 +34,14 @@
import org.apache.wicket.model.util.ListModel;
import com.gitblit.Constants.RegistrantType;
+import com.gitblit.Constants.Role;
import com.gitblit.GitBlitException;
import com.gitblit.Keys;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.NonTrimmedPasswordTextField;
import com.gitblit.wicket.RequiresAdminRole;
import com.gitblit.wicket.StringChoiceRenderer;
import com.gitblit.wicket.WicketUtils;
@@ -177,7 +178,9 @@
// update user permissions
for (RegistrantAccessPermission repositoryPermission : permissions) {
- userModel.setRepositoryPermission(repositoryPermission.registrant, repositoryPermission.permission);
+ if (repositoryPermission.mutable) {
+ userModel.setRepositoryPermission(repositoryPermission.registrant, repositoryPermission.permission);
+ }
}
Iterator<String> selectedTeams = teams.getSelectedChoices();
@@ -216,24 +219,33 @@
// do not let the browser pre-populate these fields
form.add(new SimpleAttributeModifier("autocomplete", "off"));
- // not all user services support manipulating username and password
+ // not all user providers support manipulating username and password
boolean editCredentials = app().authentication().supportsCredentialChanges(userModel);
- // not all user services support manipulating display name
+ // not all user providers support manipulating display name
boolean editDisplayName = app().authentication().supportsDisplayNameChanges(userModel);
- // not all user services support manipulating email address
+ // not all user providers support manipulating email address
boolean editEmailAddress = app().authentication().supportsEmailAddressChanges(userModel);
- // not all user services support manipulating team memberships
+ // not all user providers support manipulating team memberships
boolean editTeams = app().authentication().supportsTeamMembershipChanges(userModel);
+ // not all user providers support manipulating the admin role
+ boolean changeAdminRole = app().authentication().supportsRoleChanges(userModel, Role.ADMIN);
+
+ // not all user providers support manipulating the create role
+ boolean changeCreateRole = app().authentication().supportsRoleChanges(userModel, Role.CREATE);
+
+ // not all user providers support manipulating the fork role
+ boolean changeForkRole = app().authentication().supportsRoleChanges(userModel, Role.FORK);
+
// field names reflective match UserModel fields
form.add(new TextField<String>("username").setEnabled(editCredentials));
- PasswordTextField passwordField = new PasswordTextField("password");
+ NonTrimmedPasswordTextField passwordField = new NonTrimmedPasswordTextField("password");
passwordField.setResetPassword(false);
form.add(passwordField.setEnabled(editCredentials));
- PasswordTextField confirmPasswordField = new PasswordTextField("confirmPassword",
+ NonTrimmedPasswordTextField confirmPasswordField = new NonTrimmedPasswordTextField("confirmPassword",
confirmPassword);
confirmPasswordField.setResetPassword(false);
form.add(confirmPasswordField.setEnabled(editCredentials));
@@ -245,7 +257,7 @@
// display a disabled-yet-checked checkbox
form.add(new CheckBox("canAdmin", Model.of(true)).setEnabled(false));
} else {
- form.add(new CheckBox("canAdmin"));
+ form.add(new CheckBox("canAdmin").setEnabled(changeAdminRole));
}
if (userModel.canFork() && !userModel.canFork) {
@@ -254,7 +266,7 @@
form.add(new CheckBox("canFork", Model.of(true)).setEnabled(false));
} else {
final boolean forkingAllowed = app().settings().getBoolean(Keys.web.allowForking, true);
- form.add(new CheckBox("canFork").setEnabled(forkingAllowed));
+ form.add(new CheckBox("canFork").setEnabled(forkingAllowed && changeForkRole));
}
if (userModel.canCreate() && !userModel.canCreate) {
@@ -262,7 +274,7 @@
// display a disabled-yet-checked checkbox
form.add(new CheckBox("canCreate", Model.of(true)).setEnabled(false));
} else {
- form.add(new CheckBox("canCreate"));
+ form.add(new CheckBox("canCreate").setEnabled(changeCreateRole));
}
form.add(new CheckBox("excludeFromFederation"));
diff --git a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
index b3c5243..72d1e1a 100644
--- a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
@@ -55,7 +55,7 @@
}
HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
- List<RepositoryUrl> repositoryUrls = app().gitblit().getRepositoryUrls(req, user, repository);
+ List<RepositoryUrl> repositoryUrls = app().services().getRepositoryUrls(req, user, repository);
RepositoryUrl primaryUrl = repositoryUrls.size() == 0 ? null : repositoryUrls.get(0);
String url = primaryUrl != null ? primaryUrl.url : "";
diff --git a/src/main/java/com/gitblit/wicket/pages/FilestorePage.html b/src/main/java/com/gitblit/wicket/pages/FilestorePage.html
new file mode 100644
index 0000000..8b917e8
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/FilestorePage.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
+ xml:lang="en"
+ lang="en">
+
+<body>
+<wicket:extend>
+<div class="container">
+
+ <div style="padding: 10px 0px 5px 0px;">
+ <table class="filestore-status">
+ <tr>
+ <td><a wicket:id="filterByOk" href="#"><span wicket:id="statusOkIcon"></span><span wicket:id="statusOkCount"></span></a></td>
+ <td><a wicket:id="filterByPending" href="#"><span wicket:id="statusPendingIcon"></span><span wicket:id="statusPendingCount"></span></a></td>
+ <td><a wicket:id="filterByInprogress" href="#"><span wicket:id="statusInprogressIcon"></span><span wicket:id="statusInprogressCount"></span></a></td>
+ <td><a wicket:id="filterByError" href="#"><span wicket:id="statusErrorIcon"></span><span wicket:id="statusErrorCount"></span></a></td>
+ <td><a wicket:id="filterByDeleted" href="#"><span wicket:id="statusDeletedIcon"></span><span wicket:id="statusDeletedCount"></span></a></td>
+ <td></td>
+ <td><span class="fa fa-fw fa-database"></span><span wicket:id="spaceAvailable"></span></td>
+ </tr>
+ </table>
+ <span style="float:right"><a href="#" wicket:id="filestoreHelp"><span wicket:id="helpMessage">[help message]</span></a></span>
+ </div>
+
+ <table class="repositories">
+ <tr>
+ <th><wicket:message key="gb.status">[Object status]</wicket:message></th>
+ <th><wicket:message key="gb.statusChangedOn">[changedOn]</wicket:message></th>
+ <th><wicket:message key="gb.statusChangedBy">[changedBy]</wicket:message></th>
+ <th><wicket:message key="gb.oid">[Object ID]</wicket:message></th>
+ <th><wicket:message key="gb.size">[file size]</wicket:message></th>
+ </tr>
+ <tbody>
+ <tr wicket:id="fileRow">
+ <td><center><span class="list" wicket:id="status">[Object state]</span></center></td>
+ <td><span class="list" wicket:id="on">[changedOn]</span></td>
+ <td><span class="list" wicket:id="by">[changedBy]</span></td>
+ <td class="sha256"><span class="list" wicket:id="oid">[Object ID]</span></td>
+ <td><span class="list" wicket:id="size">[file size]</span></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <!-- pager links -->
+ <div style="padding-bottom:5px;">
+ <a wicket:id="firstPageBottom"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageBottom">« <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageBottom"><wicket:message key="gb.pageNext"></wicket:message> »</a>
+ </div>
+</div>
+
+
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/FilestorePage.java b/src/main/java/com/gitblit/wicket/pages/FilestorePage.java
new file mode 100644
index 0000000..29b3d60
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/FilestorePage.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2015 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.wicket.pages;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+
+import com.gitblit.Constants;
+import com.gitblit.Keys;
+import com.gitblit.models.FilestoreModel;
+import com.gitblit.models.FilestoreModel.Status;
+import com.gitblit.models.UserModel;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.FilestoreUI;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
+
+/**
+ * Page to display the current status of the filestore.
+ * Certain errors also displayed to aid in fault finding
+ *
+ * @author Paul Martin
+ */
+@CacheControl(LastModified.ACTIVITY)
+public class FilestorePage extends RootPage {
+
+ public FilestorePage(PageParameters params) {
+ super(params);
+ setupPage("", "");
+
+ int itemsPerPage = app().settings().getInteger(Keys.web.itemsPerPage, 20);
+ if (itemsPerPage <= 1) {
+ itemsPerPage = 20;
+ }
+
+ final int pageNumber = WicketUtils.getPage(params);
+ final String filter = WicketUtils.getSearchString(params);
+
+ int prevPage = Math.max(0, pageNumber - 1);
+ int nextPage = pageNumber + 1;
+ boolean hasMore = false;
+
+ final UserModel user = (GitBlitWebSession.get().getUser() == null) ? UserModel.ANONYMOUS : GitBlitWebSession.get().getUser();
+ final long nBytesUsed = app().filestore().getFilestoreUsedByteCount();
+ final long nBytesAvailable = app().filestore().getFilestoreAvailableByteCount();
+ List<FilestoreModel> files = app().filestore().getAllObjects(user);
+
+ if (files == null) {
+ files = new ArrayList<FilestoreModel>();
+ }
+
+ long nOk = 0;
+ long nPending = 0;
+ long nInprogress = 0;
+ long nError = 0;
+ long nDeleted = 0;
+
+ for (FilestoreModel file : files) {
+ switch (file.getStatus()) {
+ case Available: { nOk++;} break;
+ case Upload_Pending: { nPending++; } break;
+ case Upload_In_Progress: { nInprogress++; } break;
+ case Deleted: { nDeleted++; } break;
+ default: { nError++; } break;
+ }
+ }
+
+
+ BookmarkablePageLink<Void> itemOk = new BookmarkablePageLink<Void>("filterByOk", FilestorePage.class,
+ WicketUtils.newFilestorePageParameter(prevPage, SortBy.ok.name()));
+
+ BookmarkablePageLink<Void> itemPending = new BookmarkablePageLink<Void>("filterByPending", FilestorePage.class,
+ WicketUtils.newFilestorePageParameter(prevPage, SortBy.pending.name()));
+
+ BookmarkablePageLink<Void> itemInprogress = new BookmarkablePageLink<Void>("filterByInprogress", FilestorePage.class,
+ WicketUtils.newFilestorePageParameter(prevPage, SortBy.inprogress.name()));
+
+ BookmarkablePageLink<Void> itemError = new BookmarkablePageLink<Void>("filterByError", FilestorePage.class,
+ WicketUtils.newFilestorePageParameter(prevPage, SortBy.error.name()));
+
+ BookmarkablePageLink<Void> itemDeleted = new BookmarkablePageLink<Void>("filterByDeleted", FilestorePage.class,
+ WicketUtils.newFilestorePageParameter(prevPage, SortBy.deleted.name()));
+
+
+ List<FilestoreModel> filteredResults = new ArrayList<FilestoreModel>(files.size());
+
+ if (filter == null) {
+ filteredResults = files;
+ } else if (filter.equals(SortBy.ok.name())) {
+ WicketUtils.setCssClass(itemOk, "filter-on");
+
+ for (FilestoreModel item : files) {
+ if (item.getStatus() == Status.Available) {
+ filteredResults.add(item);
+ }
+ }
+ } else if (filter.equals(SortBy.pending.name())) {
+ WicketUtils.setCssClass(itemPending, "filter-on");
+
+ for (FilestoreModel item : files) {
+ if (item.getStatus() == Status.Upload_Pending) {
+ filteredResults.add(item);
+ }
+ }
+ } else if (filter.equals(SortBy.inprogress.name())) {
+ WicketUtils.setCssClass(itemInprogress, "filter-on");
+
+ for (FilestoreModel item : files) {
+ if (item.getStatus() == Status.Upload_In_Progress) {
+ filteredResults.add(item);
+ }
+ }
+ } else if (filter.equals(SortBy.error.name())) {
+ WicketUtils.setCssClass(itemError, "filter-on");
+
+ for (FilestoreModel item : files) {
+ if (item.isInErrorState()) {
+ filteredResults.add(item);
+ }
+ }
+ } else if (filter.equals(SortBy.deleted.name())) {
+ WicketUtils.setCssClass(itemDeleted, "filter-on");
+
+ for (FilestoreModel item : files) {
+ if (item.getStatus() == Status.Deleted) {
+ filteredResults.add(item);
+ }
+ }
+ }
+
+ DataView<FilestoreModel> filesView = new DataView<FilestoreModel>("fileRow",
+ new SortableFilestoreProvider(filteredResults) , itemsPerPage) {
+ private static final long serialVersionUID = 1L;
+ private int counter;
+
+ @Override
+ protected void onBeforeRender() {
+ super.onBeforeRender();
+ counter = 0;
+ }
+
+ @Override
+ public void populateItem(final Item<FilestoreModel> item) {
+ final FilestoreModel entry = item.getModelObject();
+
+ DateFormat dateFormater = new SimpleDateFormat(Constants.ISO8601);
+
+ UserModel user = app().users().getUserModel(entry.getChangedBy());
+ user = user == null ? UserModel.ANONYMOUS : user;
+
+ Label icon = FilestoreUI.getStatusIcon("status", entry);
+ item.add(icon);
+ item.add(new Label("on", dateFormater.format(entry.getChangedOn())));
+ item.add(new Label("by", user.getDisplayName()));
+
+ item.add(new Label("oid", entry.oid));
+ item.add(new Label("size", FileUtils.byteCountToDisplaySize(entry.getSize())));
+
+ WicketUtils.setAlternatingBackground(item, counter);
+ counter++;
+ }
+
+ };
+
+
+ if (filteredResults.size() < itemsPerPage) {
+ filesView.setCurrentPage(0);
+ hasMore = false;
+ } else {
+ filesView.setCurrentPage(pageNumber - 1);
+ hasMore = true;
+ }
+
+
+ add(filesView);
+
+
+ add(new BookmarkablePageLink<Void>("firstPageBottom", FilestorePage.class).setEnabled(pageNumber > 1));
+ add(new BookmarkablePageLink<Void>("prevPageBottom", FilestorePage.class,
+ WicketUtils.newFilestorePageParameter(prevPage, filter)).setEnabled(pageNumber > 1));
+ add(new BookmarkablePageLink<Void>("nextPageBottom", FilestorePage.class,
+ WicketUtils.newFilestorePageParameter(nextPage, filter)).setEnabled(hasMore));
+
+
+ itemOk.add(FilestoreUI.getStatusIcon("statusOkIcon", FilestoreModel.Status.Available));
+ itemPending.add(FilestoreUI.getStatusIcon("statusPendingIcon", FilestoreModel.Status.Upload_Pending));
+ itemInprogress.add(FilestoreUI.getStatusIcon("statusInprogressIcon", FilestoreModel.Status.Upload_In_Progress));
+ itemError.add(FilestoreUI.getStatusIcon("statusErrorIcon", FilestoreModel.Status.Error_Unknown));
+ itemDeleted.add(FilestoreUI.getStatusIcon("statusDeletedIcon", FilestoreModel.Status.Deleted));
+
+ itemOk.add(new Label("statusOkCount", String.valueOf(nOk)));
+ itemPending.add(new Label("statusPendingCount", String.valueOf(nPending)));
+ itemInprogress.add(new Label("statusInprogressCount", String.valueOf(nInprogress)));
+ itemError.add(new Label("statusErrorCount", String.valueOf(nError)));
+ itemDeleted.add(new Label("statusDeletedCount", String.valueOf(nDeleted)));
+
+ add(itemOk);
+ add(itemPending);
+ add(itemInprogress);
+ add(itemError);
+ add(itemDeleted);
+
+ add(new Label("spaceAvailable", String.format("%s / %s",
+ FileUtils.byteCountToDisplaySize(nBytesUsed),
+ FileUtils.byteCountToDisplaySize(nBytesAvailable))));
+
+ BookmarkablePageLink<Void> helpLink = new BookmarkablePageLink<Void>("filestoreHelp", FilestoreUsage.class);
+ helpLink.add(new Label("helpMessage", getString("gb.filestoreHelp")));
+ add(helpLink);
+
+ }
+
+ protected enum SortBy {
+ ok, pending, inprogress, error, deleted;
+ }
+
+ private static class SortableFilestoreProvider extends SortableDataProvider<FilestoreModel> {
+
+ private static final long serialVersionUID = 1L;
+
+ private List<FilestoreModel> list;
+
+ protected SortableFilestoreProvider(List<FilestoreModel> list) {
+ this.list = list;
+ }
+
+ @Override
+ public int size() {
+ if (list == null) {
+ return 0;
+ }
+ return list.size();
+ }
+
+ @Override
+ public IModel<FilestoreModel> model(FilestoreModel header) {
+ return new Model<FilestoreModel>(header);
+ }
+
+ @Override
+ public Iterator<FilestoreModel> iterator(int first, int count) {
+ Collections.sort(list, new Comparator<FilestoreModel>() {
+ @Override
+ public int compare(FilestoreModel o1, FilestoreModel o2) {
+ return o2.getChangedOn().compareTo(o1.getChangedOn());
+ }
+ });
+ return list.subList(first, first + count).iterator();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.html b/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.html
new file mode 100644
index 0000000..89e98ca
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
+ xml:lang="en"
+ lang="en">
+
+<body>
+<wicket:extend>
+<div class="container">
+<div class="markdown">
+<div class="row">
+<div class="span10 offset1">
+
+ <div class="alert alert-danger">
+ <h3><center>Using the filestore</center></h3>
+ <p>
+ <strong>All clients intending to use the filestore must first install the <a href="https://git-lfs.github.com/">Git-LFS Client</a> and then run <code>git lfs install</code></strong><br/>
+ <p>
+ If using password authentication it is recommended that you configure the <a href="https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage">git credential storage</a> to avoid Git-LFS asking for your password on each file<br/>
+ On Windows for example: <code>git config --global credential.helper wincred</code>
+ </p>
+ </p>
+ </div>
+
+ <h3>Clone</h3>
+ <p>
+ Just <code>git clone</code> as usual, no further action is required as Gitblit is configured to use the default Git-LFS end point <code>{repository}/info/lfs/objects/</code>.<br/>
+ <i>If the repository uses a 3rd party Git-LFS server you will need to <a href="https://github.com/github/git-lfs/blob/master/docs/spec.md#the-server">manually configure the correct endpoints</a></i>.
+ </p>
+
+ <h3>Add</h3>
+ <p>After configuring the file types or paths to be tracked using <code>git lfs track "*.bin"</code> just add files as usual with <code>git add</code> command.<br/>
+ <i>Tracked files can also be configured manually using the <code>.gitattributes</code> file</i>.</p>
+
+ <h3>Remove</h3>
+ <p>When you remove a Git-LFS tracked file only the pointer file will be removed from your repository.<br/>
+ <i>All files remain on the server to allow previous versions to be checked out.</i>
+ </p>
+
+ <h3>Learn more...</h3>
+ <p><a href="https://github.com/github/git-lfs/blob/master/docs/spec.md">See the current Git-LFS specification for further details</a>.</p>
+ <br />
+
+
+</div>
+</div>
+</div>
+</div>
+</wicket:extend>
+</body>
+</html>
diff --git a/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.java b/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.java
new file mode 100644
index 0000000..9bd8e55
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.wicket.pages;
+
+public class FilestoreUsage extends RootSubPage {
+
+ public FilestoreUsage() {
+ super();
+ setupPage("", "");
+ }
+
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/ForksPage.java b/src/main/java/com/gitblit/wicket/pages/ForksPage.java
index 9fd7f4d..045f5f7 100644
--- a/src/main/java/com/gitblit/wicket/pages/ForksPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ForksPage.java
@@ -34,7 +34,7 @@
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.AvatarImage;
import com.gitblit.wicket.panels.LinkPanel;
public class ForksPage extends RepositoryPage {
@@ -63,7 +63,7 @@
user = new UserModel(repository.projectPath.substring(1));
}
PersonIdent ident = new PersonIdent(user.getDisplayName(), user.emailAddress == null ? user.getDisplayName() : user.emailAddress);
- item.add(new GravatarImage("anAvatar", ident, 20));
+ item.add(new AvatarImage("anAvatar", ident, 20));
if (pageRepository.equals(repository)) {
// do not link to self
item.add(new Label("aProject", user.getDisplayName()));
@@ -136,6 +136,9 @@
protected List<FlatFork> flatten(ForkModel node, int level) {
List<FlatFork> list = new ArrayList<FlatFork>();
+ if (node == null) {
+ return list;
+ }
list.add(new FlatFork(node.repository, level));
if (!node.isLeaf()) {
for (ForkModel fork : node.forks) {
diff --git a/src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java b/src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java
new file mode 100644
index 0000000..dc0c5ae
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2014 Tom <tw201207@gmail.com>
+ *
+ * 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.
+ */
+package com.gitblit.wicket.pages;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.protocol.http.WicketURLEncoder;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffEntry.Side;
+import org.jsoup.nodes.Element;
+
+import com.gitblit.servlet.RawServlet;
+import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.HtmlBuilder;
+
+/**
+ * A {@link DiffUtils.BinaryDiffHandler BinaryDiffHandler} for images.
+ *
+ * @author Tom <tw201207@gmail.com>
+ */
+public class ImageDiffHandler implements DiffUtils.BinaryDiffHandler {
+
+ private final String oldCommitId;
+ private final String newCommitId;
+ private final String repositoryName;
+ private final BasePage page;
+ private final List<String> imageExtensions;
+
+ private int imgDiffCount = 0;
+
+ public ImageDiffHandler(final BasePage page, final String repositoryName, final String oldCommitId, final String newCommitId,
+ final List<String> imageExtensions) {
+ this.page = page;
+ this.repositoryName = repositoryName;
+ this.oldCommitId = oldCommitId;
+ this.newCommitId = newCommitId;
+ this.imageExtensions = imageExtensions;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String renderBinaryDiff(DiffEntry diffEntry) {
+ switch (diffEntry.getChangeType()) {
+ case MODIFY:
+ case RENAME:
+ case COPY:
+ // TODO: for very small images such as icons, the slider doesn't really help. Two possible
+ // approaches: either upscale them for display (may show blurry upscaled images), or show
+ // them side by side (may still be too small to really make out the differences).
+ String oldUrl = getImageUrl(diffEntry, Side.OLD);
+ String newUrl = getImageUrl(diffEntry, Side.NEW);
+ if (oldUrl != null && newUrl != null) {
+ imgDiffCount++;
+ String id = "imgdiff" + imgDiffCount;
+ HtmlBuilder builder = new HtmlBuilder("div");
+ Element wrapper = builder.root().attr("class", "imgdiff-container").attr("id", "imgdiff-" + id);
+ Element container = wrapper.appendElement("div").attr("class", "imgdiff-ovr-slider").appendElement("div").attr("class", "imgdiff");
+ Element old = container.appendElement("div").attr("class", "imgdiff-left");
+ // style='max-width:640px;' is necessary for ensuring that the browser limits large images
+ // to some reasonable width, and to override the "img { max-width: 100%; }" from bootstrap.css,
+ // which would scale the left image to the width of its resizeable container, which isn't what
+ // we want here. Note that the max-width must be defined directly as inline style on the element,
+ // otherwise browsers ignore it if the image is larger, and we end up with an image display that
+ // is too wide.
+ // XXX: Maybe add a max-height, too, to limit portrait-oriented images to some reasonable height?
+ // (Like a 300x10000px image...)
+ old.appendElement("img").attr("class", "imgdiff-old").attr("id", id).attr("style", "max-width:640px;").attr("src", oldUrl);
+ container.appendElement("img").attr("class", "imgdiff").attr("style", "max-width:640px;").attr("src", newUrl);
+ wrapper.appendElement("br");
+ Element controls = wrapper.appendElement("div");
+ // Opacity slider
+ controls.appendElement("div").attr("class", "imgdiff-opa-container").appendElement("a").attr("class", "imgdiff-opa-slider")
+ .attr("href", "#").attr("title", page.getString("gb.opacityAdjust"));
+ // Blink comparator: find Pluto!
+ controls.appendElement("a").attr("class", "imgdiff-link imgdiff-blink").attr("href", "#")
+ .attr("title", page.getString("gb.blinkComparator"))
+ .appendElement("img").attr("src", getStaticResourceUrl("blink32.png")).attr("width", "20");
+ // Pixel subtraction, initially not displayed, will be shown by imgdiff.js depending on feature test.
+ // (Uses CSS mix-blend-mode, which isn't supported on all browsers yet).
+ controls.appendElement("a").attr("class", "imgdiff-link imgdiff-subtract").attr("href", "#")
+ .attr("title", page.getString("gb.imgdiffSubtract")).attr("style", "display:none;")
+ .appendElement("img").attr("src", getStaticResourceUrl("sub32.png")).attr("width", "20");
+ return builder.toString();
+ }
+ break;
+ case ADD:
+ String url = getImageUrl(diffEntry, Side.NEW);
+ if (url != null) {
+ return new HtmlBuilder("img").root().attr("class", "diff-img").attr("src", url).toString();
+ }
+ break;
+ default:
+ break;
+ }
+ return null;
+ }
+
+ /** Returns the number of image diffs generated so far by this {@link ImageDiffHandler}. */
+ public int getImgDiffCount() {
+ return imgDiffCount;
+ }
+
+ /**
+ * Constructs a URL that will fetch the designated resource in the git repository. The returned string will
+ * contain the URL fully URL-escaped, but note that it may still contain unescaped ampersands, so the result
+ * must still be run through HTML escaping if it is to be used in HTML.
+ *
+ * @return the URL to the image, if the given {@link DiffEntry} and {@link Side} refers to an image, or {@code null} otherwise.
+ */
+ protected String getImageUrl(DiffEntry entry, Side side) {
+ String path = entry.getPath(side);
+ int i = path.lastIndexOf('.');
+ if (i > 0) {
+ String extension = path.substring(i + 1);
+ for (String ext : imageExtensions) {
+ if (ext.equalsIgnoreCase(extension)) {
+ String commitId = Side.NEW.equals(side) ? newCommitId : oldCommitId;
+ if (commitId != null) {
+ return RawServlet.asLink(page.getContextUrl(), urlencode(repositoryName), commitId, urlencode(path));
+ } else {
+ return null;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a URL that will fetch the designated static resource from within GitBlit.
+ */
+ protected String getStaticResourceUrl(String contextRelativePath) {
+ return WebApplication.get().getRequestCycleProcessor().getRequestCodingStrategy().rewriteStaticRelativeUrl(contextRelativePath);
+ }
+
+ /**
+ * Encode a URL component of a {@link RawServlet} URL in the special way that the servlet expects it. Note that
+ * the %-encoding used does not encode '&' or '<'. Slashes are not encoded in the result.
+ *
+ * @param component
+ * to encode using %-encoding
+ * @return the encoded component
+ */
+ protected String urlencode(final String component) {
+ // RawServlet handles slashes itself. Note that only the PATH_INSTANCE fits the bill here: it encodes
+ // spaces as %20, and we just have to correct for encoded slashes. Java's standard URLEncoder would
+ // encode spaces as '+', and I don't know what effects that would have on other parts of GitBlit. It
+ // would also be wrong for path components (but fine for a query part), so we'd have to correct it, too.
+ //
+ // Actually, this should be done in RawServlet.asLink(). As it is now, this may be incorrect if that
+ // operation ever uses query parameters instead of paths, or if it is fixed to urlencode its path
+ // components. But I don't want to touch that static method in RawServlet.
+ return WicketURLEncoder.PATH_INSTANCE.encode(component, StandardCharsets.UTF_8.name()).replaceAll("%2[fF]", "/");
+ }
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/LogoutPage.html b/src/main/java/com/gitblit/wicket/pages/LogoutPage.html
index d407783..ab3c0da 100644
--- a/src/main/java/com/gitblit/wicket/pages/LogoutPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/LogoutPage.html
@@ -14,7 +14,7 @@
<span class="icon-bar"></span>
</a>
<a class="brand" wicket:id="rootLink">
- <img src="gitblt_25_white.png" width="79" height="25" alt="gitblit" class="logo"/>
+ <img src="gitblt_25_white.png" width="79" alt="gitblit" class="logo"/>
</a>
</div>
diff --git a/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html
index 91a6ef4..d62b7b2 100644
--- a/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html
@@ -24,6 +24,7 @@
<div class="span3">
<h3><wicket:message key="gb.repositories"></wicket:message></h3>
<select wicket:id="repositories" ></select>
+ <label><input type="checkbox" wicket:id="allrepos" /> <span><wicket:message key="gb.allRepositories"></wicket:message></span></label>
</div>
<div class="span9" style="margin-left:10px">
<div>
diff --git a/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java
index 4d4545a..1d81061 100644
--- a/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java
@@ -17,11 +17,14 @@
import java.text.MessageFormat;
import java.util.ArrayList;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.ListMultipleChoice;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.Fragment;
@@ -63,9 +66,20 @@
// default values
ArrayList<String> repositories = new ArrayList<String>();
String query = "";
+ boolean allRepos = false;
+
int page = 1;
int pageSize = app().settings().getInteger(Keys.web.itemsPerPage, 50);
+ // display user-accessible selections
+ UserModel user = GitBlitWebSession.get().getUser();
+ List<String> availableRepositories = new ArrayList<String>();
+ for (RepositoryModel model : app().repositories().getRepositoryModels(user)) {
+ if (model.hasCommits && !ArrayUtils.isEmpty(model.indexedBranches)) {
+ availableRepositories.add(model.name);
+ }
+ }
+
if (params != null) {
String repository = WicketUtils.getRepositoryName(params);
if (!StringUtils.isEmpty(repository)) {
@@ -80,6 +94,11 @@
repositories.addAll(list);
}
+ allRepos = params.getAsBoolean("allrepos", false);
+ if (allRepos) {
+ repositories.addAll(availableRepositories);
+ }
+
if (params.containsKey("query")) {
query = params.getString("query", "");
} else {
@@ -96,14 +115,6 @@
}
}
- // display user-accessible selections
- UserModel user = GitBlitWebSession.get().getUser();
- List<String> availableRepositories = new ArrayList<String>();
- for (RepositoryModel model : app().repositories().getRepositoryModels(user)) {
- if (model.hasCommits && !ArrayUtils.isEmpty(model.indexedBranches)) {
- availableRepositories.add(model.name);
- }
- }
boolean luceneEnabled = app().settings().getBoolean(Keys.web.allowLuceneIndexing, true);
if (luceneEnabled) {
if (availableRepositories.size() == 0) {
@@ -114,16 +125,18 @@
}
// enforce user-accessible repository selections
- ArrayList<String> searchRepositories = new ArrayList<String>();
+ Set<String> uniqueRepositories = new LinkedHashSet<String>();
for (String selectedRepository : repositories) {
if (availableRepositories.contains(selectedRepository)) {
- searchRepositories.add(selectedRepository);
+ uniqueRepositories.add(selectedRepository);
}
}
+ ArrayList<String> searchRepositories = new ArrayList<String>(uniqueRepositories);
// search form
final Model<String> queryModel = new Model<String>(query);
final Model<ArrayList<String>> repositoriesModel = new Model<ArrayList<String>>(searchRepositories);
+ final Model<Boolean> allreposModel = new Model<Boolean>(allRepos);
SessionlessForm<Void> form = new SessionlessForm<Void>("searchForm", getClass()) {
private static final long serialVersionUID = 1L;
@@ -135,13 +148,14 @@
error(getString("gb.undefinedQueryWarning"));
return;
}
- if (repositoriesModel.getObject().size() == 0) {
+ if (repositoriesModel.getObject().size() == 0 && !allreposModel.getObject()) {
error(getString("gb.noSelectedRepositoriesWarning"));
return;
}
PageParameters params = new PageParameters();
params.put("repositories", StringUtils.flattenStrings(repositoriesModel.getObject()));
params.put("query", queryModel.getObject());
+ params.put("allrepos", allreposModel.getObject());
LuceneSearchPage page = new LuceneSearchPage(params);
setResponsePage(page);
}
@@ -152,6 +166,7 @@
selections.setMaxRows(8);
form.add(selections.setEnabled(luceneEnabled));
form.add(new TextField<String>("query", queryModel).setEnabled(luceneEnabled));
+ form.add(new CheckBox("allrepos", allreposModel));
add(form.setEnabled(luceneEnabled));
// execute search
diff --git a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html
index b0bc194..70869a1 100644
--- a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html
@@ -25,18 +25,19 @@
<!-- query list -->
<ul class="nav nav-list">
<li class="nav-header"><wicket:message key="gb.queries"></wicket:message></li>
- <li><a wicket:id="changesQuery"><i class="fa fa-code-fork"></i> <wicket:message key="gb.proposalTickets"></wicket:message></a></li>
- <li><a wicket:id="bugsQuery"><i class="fa fa-bug"></i> <wicket:message key="gb.bugTickets"></wicket:message></a></li>
- <li><a wicket:id="enhancementsQuery"><i class="fa fa-magic"></i> <wicket:message key="gb.enhancementTickets"></wicket:message></a></li>
- <li><a wicket:id="tasksQuery"><i class="fa fa-ticket"></i> <wicket:message key="gb.taskTickets"></wicket:message></a></li>
- <li><a wicket:id="questionsQuery"><i class="fa fa-question"></i> <wicket:message key="gb.questionTickets"></wicket:message></a></li>
+ <li><a wicket:id="changesQuery"><i class="fa fa-code-fork fa-fw"></i> <wicket:message key="gb.proposalTickets"></wicket:message></a></li>
+ <li><a wicket:id="bugsQuery"><i class="fa fa-bug fa-fw"></i> <wicket:message key="gb.bugTickets"></wicket:message></a></li>
+ <li><a wicket:id="enhancementsQuery"><i class="fa fa-magic fa-fw"></i> <wicket:message key="gb.enhancementTickets"></wicket:message></a></li>
+ <li><a wicket:id="tasksQuery"><i class="fa fa-ticket fa-fw"></i> <wicket:message key="gb.taskTickets"></wicket:message></a></li>
+ <li><a wicket:id="questionsQuery"><i class="fa fa-question fa-fw"></i> <wicket:message key="gb.questionTickets"></wicket:message></a></li>
+ <li><a wicket:id="maintenanceQuery"><i class="fa fa-cogs fa-fw"></i> <wicket:message key="gb.maintenanceTickets"></wicket:message></a></li>
<li wicket:id="userDivider" class="divider"></li>
- <li><a wicket:id="createdQuery"><i class="fa fa-user"></i> <wicket:message key="gb.yourCreatedTickets"></wicket:message></a></li>
- <li><a wicket:id="responsibleQuery"><i class="fa fa-user"></i> <wicket:message key="gb.yourAssignedTickets"></wicket:message></a></li>
- <li><a wicket:id="watchedQuery"><i class="fa fa-eye"></i> <wicket:message key="gb.yourWatchedTickets"></wicket:message></a></li>
- <li><a wicket:id="mentionsQuery"><i class="fa fa-comment"></i> <wicket:message key="gb.mentionsMeTickets"></wicket:message></a></li>
+ <li><a wicket:id="createdQuery"><i class="fa fa-user fa-fw"></i> <wicket:message key="gb.yourCreatedTickets"></wicket:message></a></li>
+ <li><a wicket:id="responsibleQuery"><i class="fa fa-user fa-fw"></i> <wicket:message key="gb.yourAssignedTickets"></wicket:message></a></li>
+ <li><a wicket:id="watchedQuery"><i class="fa fa-eye fa-fw"></i> <wicket:message key="gb.yourWatchedTickets"></wicket:message></a></li>
+ <li><a wicket:id="mentionsQuery"><i class="fa fa-comment fa-fw"></i> <wicket:message key="gb.mentionsMeTickets"></wicket:message></a></li>
<li class="divider"></li>
- <li><a wicket:id="resetQuery"><i class="fa fa-bolt"></i> <wicket:message key="gb.reset"></wicket:message></a></li>
+ <li><a wicket:id="resetQuery"><i class="fa fa-bolt fa-fw"></i> <wicket:message key="gb.reset"></wicket:message></a></li>
</ul>
</div>
@@ -62,6 +63,13 @@
</ul>
</div>
+ <div class="btn-group">
+ <a class="btn dropdown-toggle" data-toggle="dropdown" href="#"> <wicket:message key="gb.repository"></wicket:message>: <span style="font-weight:bold;" wicket:id="currentRepository"></span> <span class="caret"></span></a>
+ <ul class="dropdown-menu">
+ <li wicket:id="repository"><span wicket:id="repositoryLink"></span></li>
+ </ul>
+ </div>
+
<div class="btn-group pull-right">
<div class="pagination pagination-right pagination-small">
<ul>
@@ -81,4 +89,4 @@
</wicket:extend>
</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java
index c207d56..bfcedf6 100644
--- a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java
@@ -17,6 +17,9 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import org.apache.wicket.PageParameters;
@@ -27,6 +30,7 @@
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import com.gitblit.Keys;
+import com.gitblit.models.RepositoryModel;
import com.gitblit.models.TicketModel;
import com.gitblit.models.TicketModel.Status;
import com.gitblit.models.UserModel;
@@ -52,341 +56,446 @@
*/
public class MyTicketsPage extends RootPage {
- public MyTicketsPage() {
- this(null);
- }
+ public MyTicketsPage() {
+ this(null);
+ }
- public MyTicketsPage(PageParameters params) {
- super(params);
- setupPage("", getString("gb.myTickets"));
+ public MyTicketsPage(PageParameters params) {
+ super(params);
+ setupPage("", getString("gb.myTickets"));
- UserModel currentUser = GitBlitWebSession.get().getUser();
- if (currentUser == null || UserModel.ANONYMOUS.equals(currentUser)) {
- setRedirect(true);
- setResponsePage(getApplication().getHomePage());
- return;
+ UserModel currentUser = GitBlitWebSession.get().getUser();
+ if (currentUser == null || UserModel.ANONYMOUS.equals(currentUser)) {
+ setRedirect(true);
+ setResponsePage(getApplication().getHomePage());
+ return;
+ }
+
+ final String username = currentUser.getName();
+
+ final String[] statiiParam = (params == null) ? TicketsUI.openStatii : params.getStringArray(Lucene.status.name());
+ final String assignedToParam = (params == null) ? "" : params.getString(Lucene.responsible.name(), null);
+ final String milestoneParam = (params == null) ? "" : params.getString(Lucene.milestone.name(), null);
+ final String queryParam = (params == null) ? null : params.getString("q", null);
+ final String searchParam = (params == null) ? "" : params.getString("s", null);
+ final String sortBy = (params == null) ? "" : Lucene.fromString(params.getString("sort", Lucene.created.name())).name();
+ final String repositoryId = (params == null) ? "" : params.getString(Lucene.rid.name(), null);
+ final boolean desc = (params == null) ? true : !"asc".equals(params.getString("direction", "desc"));
+
+
+ // add the user title panel
+ add(new UserTitlePanel("userTitlePanel", currentUser, getString("gb.myTickets")));
+
+ // add search form
+ add(new TicketSearchForm("ticketSearchForm", null, searchParam, getClass(), params));
+
+ // standard queries
+ add(new BookmarkablePageLink<Void>("changesQuery", MyTicketsPage.class,
+ queryParameters(
+ Lucene.type.matches(TicketModel.Type.Proposal.name()),
+ milestoneParam,
+ statiiParam,
+ assignedToParam,
+ sortBy,
+ desc,
+ repositoryId,
+ 1)));
+
+ add(new BookmarkablePageLink<Void>("bugsQuery", MyTicketsPage.class,
+ queryParameters(
+ Lucene.type.matches(TicketModel.Type.Bug.name()),
+ milestoneParam,
+ statiiParam,
+ assignedToParam,
+ sortBy,
+ desc,
+ repositoryId,
+ 1)));
+
+ add(new BookmarkablePageLink<Void>("enhancementsQuery", MyTicketsPage.class,
+ queryParameters(
+ Lucene.type.matches(TicketModel.Type.Enhancement.name()),
+ milestoneParam,
+ statiiParam,
+ assignedToParam,
+ sortBy,
+ desc,
+ repositoryId,
+ 1)));
+
+ add(new BookmarkablePageLink<Void>("tasksQuery", MyTicketsPage.class,
+ queryParameters(
+ Lucene.type.matches(TicketModel.Type.Task.name()),
+ milestoneParam,
+ statiiParam,
+ assignedToParam,
+ sortBy,
+ desc,
+ repositoryId,
+ 1)));
+
+ add(new BookmarkablePageLink<Void>("questionsQuery", MyTicketsPage.class,
+ queryParameters(
+ Lucene.type.matches(TicketModel.Type.Question.name()),
+ milestoneParam,
+ statiiParam,
+ assignedToParam,
+ sortBy,
+ desc,
+ repositoryId,
+ 1)));
+
+ add(new BookmarkablePageLink<Void>("maintenanceQuery", MyTicketsPage.class,
+ queryParameters(
+ Lucene.type.matches(TicketModel.Type.Maintenance.name()),
+ milestoneParam,
+ statiiParam,
+ assignedToParam,
+ sortBy,
+ desc,
+ repositoryId,
+ 1)));
+
+ add(new BookmarkablePageLink<Void>("resetQuery", MyTicketsPage.class,
+ queryParameters(
+ null,
+ milestoneParam,
+ TicketsUI.openStatii,
+ null,
+ null,
+ true,
+ null,
+ 1)));
+
+ add(new Label("userDivider"));
+ add(new BookmarkablePageLink<Void>("createdQuery", MyTicketsPage.class,
+ queryParameters(
+ Lucene.createdby.matches(username),
+ milestoneParam,
+ statiiParam,
+ assignedToParam,
+ sortBy,
+ desc,
+ repositoryId,
+ 1)));
+
+ add(new BookmarkablePageLink<Void>("watchedQuery", MyTicketsPage.class,
+ queryParameters(
+ Lucene.watchedby.matches(username),
+ milestoneParam,
+ statiiParam,
+ assignedToParam,
+ sortBy,
+ desc,
+ repositoryId,
+ 1)));
+ add(new BookmarkablePageLink<Void>("mentionsQuery", MyTicketsPage.class,
+ queryParameters(
+ Lucene.mentions.matches(username),
+ milestoneParam,
+ statiiParam,
+ assignedToParam,
+ sortBy,
+ desc,
+ repositoryId,
+ 1)));
+ add(new BookmarkablePageLink<Void>("responsibleQuery", MyTicketsPage.class,
+ queryParameters(
+ Lucene.responsible.matches(username),
+ milestoneParam,
+ statiiParam,
+ assignedToParam,
+ sortBy,
+ desc,
+ repositoryId,
+ 1)));
+
+ // states
+ if (ArrayUtils.isEmpty(statiiParam)) {
+ add(new Label("selectedStatii", getString("gb.all")));
+ } else {
+ add(new Label("selectedStatii", StringUtils.flattenStrings(Arrays.asList(statiiParam), ",")));
+ }
+ add(new BookmarkablePageLink<Void>("openTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.openStatii, assignedToParam, sortBy, desc, repositoryId, 1)));
+ add(new BookmarkablePageLink<Void>("closedTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.closedStatii, assignedToParam, sortBy, desc, repositoryId, 1)));
+ add(new BookmarkablePageLink<Void>("allTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, null, assignedToParam, sortBy, desc, repositoryId, 1)));
+
+ // by status
+ List<Status> statii = new ArrayList<Status>(Arrays.asList(Status.values()));
+ statii.remove(Status.Closed);
+ ListDataProvider<Status> resolutionsDp = new ListDataProvider<Status>(statii);
+ DataView<Status> statiiLinks = new DataView<Status>("statii", resolutionsDp) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void populateItem(final Item<Status> item) {
+ final Status status = item.getModelObject();
+ PageParameters p = queryParameters(queryParam, milestoneParam, new String [] { status.name().toLowerCase() }, assignedToParam, sortBy, desc, repositoryId, 1);
+ String css = TicketsUI.getStatusClass(status);
+ item.add(new LinkPanel("statusLink", css, status.toString(), MyTicketsPage.class, p).setRenderBodyOnly(true));
+ }
+ };
+ add(statiiLinks);
+
+ // by sort
+ List<TicketSort> sortChoices = new ArrayList<TicketSort>();
+ sortChoices.add(new TicketSort(getString("gb.sortNewest"), Lucene.created.name(), true));
+ sortChoices.add(new TicketSort(getString("gb.sortOldest"), Lucene.created.name(), false));
+ sortChoices.add(new TicketSort(getString("gb.sortMostRecentlyUpdated"), Lucene.updated.name(), true));
+ sortChoices.add(new TicketSort(getString("gb.sortLeastRecentlyUpdated"), Lucene.updated.name(), false));
+ sortChoices.add(new TicketSort(getString("gb.sortMostComments"), Lucene.comments.name(), true));
+ sortChoices.add(new TicketSort(getString("gb.sortLeastComments"), Lucene.comments.name(), false));
+ sortChoices.add(new TicketSort(getString("gb.sortMostPatchsetRevisions"), Lucene.patchsets.name(), true));
+ sortChoices.add(new TicketSort(getString("gb.sortLeastPatchsetRevisions"), Lucene.patchsets.name(), false));
+ sortChoices.add(new TicketSort(getString("gb.sortMostVotes"), Lucene.votes.name(), true));
+ sortChoices.add(new TicketSort(getString("gb.sortLeastVotes"), Lucene.votes.name(), false));
+ sortChoices.add(new TicketSort(getString("gb.sortHighestPriority"), Lucene.priority.name(), true));
+ sortChoices.add(new TicketSort(getString("gb.sortLowestPriority"), Lucene.priority.name(), false));
+ sortChoices.add(new TicketSort(getString("gb.sortHighestSeverity"), Lucene.severity.name(), true));
+ sortChoices.add(new TicketSort(getString("gb.sortLowestSeverity"), Lucene.severity.name(), false));
+
+ TicketSort currentSort = sortChoices.get(0);
+ for (TicketSort ts : sortChoices) {
+ if (ts.sortBy.equals(sortBy) && desc == ts.desc) {
+ currentSort = ts;
+ break;
+ }
+ }
+ add(new Label("currentSort", currentSort.name));
+
+ ListDataProvider<TicketSort> sortChoicesDp = new ListDataProvider<TicketSort>(sortChoices);
+ DataView<TicketSort> sortMenu = new DataView<TicketSort>("sort", sortChoicesDp) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void populateItem(final Item<TicketSort> item) {
+ final TicketSort ts = item.getModelObject();
+ PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, assignedToParam, ts.sortBy, ts.desc, repositoryId, 1);
+ item.add(new LinkPanel("sortLink", null, ts.name, MyTicketsPage.class, params).setRenderBodyOnly(true));
+ }
+ };
+ add(sortMenu);
+
+ // by repository
+ final List<QueryResult> tickets =
+ query(initializeQueryBuilder(null, username), 1, Integer.MAX_VALUE, sortBy, desc);
+ final List<RepositoryModel> repositoryChoices = correspondingRepositories(tickets);
+ Collections.sort(repositoryChoices);
+
+ final RepositoryModel noneChoice = new RepositoryModel();
+ noneChoice.name = getString("gb.all");
+ repositoryChoices.add(0, noneChoice);
+
+ RepositoryModel currentRepository = repositoryChoices.get(0);
+ for (RepositoryModel r : repositoryChoices) {
+ if (r.getRID().equals(repositoryId)) {
+ currentRepository = r;
+ break;
+ }
+ }
+ add(new Label("currentRepository", currentRepository.toString()));
+
+ ListDataProvider<RepositoryModel> repositoryChoicesDp = new ListDataProvider<RepositoryModel>(repositoryChoices);
+ DataView<RepositoryModel> repositoryMenu = new DataView<RepositoryModel>("repository", repositoryChoicesDp) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void populateItem(final Item<RepositoryModel> item) {
+ final RepositoryModel r = item.getModelObject();
+ String rid = r == noneChoice ? null : r.getRID();
+ PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, rid, 1);
+ item.add(new LinkPanel("repositoryLink", null, r.toString(), MyTicketsPage.class, params).setRenderBodyOnly(true));
+ }
+ };
+ add(repositoryMenu);
+
+ // Update query with filter criteria
+ final QueryBuilder qb = initializeQueryBuilder(queryParam, username);
+
+ if (!qb.containsField(Lucene.status.name()) && !ArrayUtils.isEmpty(statiiParam)) {
+ // specify the states
+ boolean not = false;
+ QueryBuilder q = new QueryBuilder();
+ for (String state : statiiParam) {
+ if (state.charAt(0) == '!') {
+ not = true;
+ q.and(Lucene.status.doesNotMatch(state.substring(1)));
+ } else {
+ q.or(Lucene.status.matches(state));
+ }
+ }
+
+ if (not) {
+ qb.and(q.toString());
+ } else {
+ qb.and(q.toSubquery().toString());
+ }
+ }
+
+ if (noneChoice != currentRepository && !qb.containsField(Lucene.rid.name())) {
+ QueryBuilder q1 = new QueryBuilder();
+ q1.and(Lucene.rid.matches(repositoryId));
+ qb.and(q1.toSubquery().toString());
+ }
+
+ // paging links
+ int page = (params != null) ? Math.max(1, WicketUtils.getPage(params)) : 1;
+ int pageSize = app().settings().getInteger(Keys.tickets.perPage, 25);
+
+ final List<QueryResult> allResults =
+ StringUtils.isEmpty(searchParam) ? query(qb, page, pageSize, sortBy, desc) : search(searchParam, page, pageSize);
+
+ List<QueryResult> viewableResults = new ArrayList<>(allResults.size());
+ for (QueryResult queryResult : allResults) {
+ RepositoryModel model = app().repositories().getRepositoryModel(currentUser, queryResult.repository);
+
+ if ((model != null) && (currentUser.canView(model))) {
+ viewableResults.add(queryResult);
+ }
}
+
+ int totalResults = viewableResults.size() == 0 ? 0 : viewableResults.get(0).totalResults;
+ buildPager(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, repositoryId, page, pageSize, viewableResults.size(), totalResults);
- final String username = currentUser.getName();
- final String[] statiiParam = (params == null) ? TicketsUI.openStatii : params.getStringArray(Lucene.status.name());
- final String assignedToParam = (params == null) ? "" : params.getString(Lucene.responsible.name(), null);
- final String milestoneParam = (params == null) ? "" : params.getString(Lucene.milestone.name(), null);
- final String queryParam = (params == null || StringUtils.isEmpty(params.getString("q", null))) ? "watchedby:" + username : params.getString("q", null);
- final String searchParam = (params == null) ? "" : params.getString("s", null);
- final String sortBy = (params == null) ? "" : Lucene.fromString(params.getString("sort", Lucene.created.name())).name();
- final boolean desc = (params == null) ? true : !"asc".equals(params.getString("direction", "desc"));
+ final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true);
+ add(new TicketListPanel("ticketList", viewableResults, showSwatch, true));
+ }
- // add the user title panel
- add(new UserTitlePanel("userTitlePanel", currentUser, getString("gb.myTickets")));
+ protected PageParameters queryParameters(
+ String query,
+ String milestone,
+ String[] states,
+ String assignedTo,
+ String sort,
+ boolean descending,
+ String repositoryId,
+ int page) {
- // add search form
- add(new TicketSearchForm("ticketSearchForm", null, searchParam, getClass(), params));
+ PageParameters params = WicketUtils.newRepositoryParameter("");
+ if (!StringUtils.isEmpty(query)) {
+ params.add("q", query);
+ }
+ if (!StringUtils.isEmpty(milestone)) {
+ params.add(Lucene.milestone.name(), milestone);
+ }
+ if (!ArrayUtils.isEmpty(states)) {
+ for (String state : states) {
+ params.add(Lucene.status.name(), state);
+ }
+ }
+ if (!StringUtils.isEmpty(assignedTo)) {
+ params.add(Lucene.responsible.name(), assignedTo);
+ }
+ if (!StringUtils.isEmpty(sort)) {
+ params.add("sort", sort);
+ }
+ if (!descending) {
+ params.add("direction", "asc");
+ }
+ if (!StringUtils.isEmpty(repositoryId)) {
+ params.add(Lucene.rid.name(), repositoryId);
+ }
+ if (page > 1) {
+ params.add("pg", "" + page);
+ }
+ return params;
+ }
- // standard queries
- add(new BookmarkablePageLink<Void>("changesQuery", MyTicketsPage.class,
- queryParameters(
- Lucene.type.matches(TicketModel.Type.Proposal.name()),
- milestoneParam,
- statiiParam,
- assignedToParam,
- sortBy,
- desc,
- 1)));
+ protected void buildPager(
+ final String query,
+ final String milestone,
+ final String [] states,
+ final String assignedTo,
+ final String sort,
+ final boolean desc,
+ final String repositoryId,
+ final int page,
+ int pageSize,
+ int count,
+ int total) {
- add(new BookmarkablePageLink<Void>("bugsQuery", MyTicketsPage.class,
- queryParameters(
- Lucene.type.matches(TicketModel.Type.Bug.name()),
- milestoneParam,
- statiiParam,
- assignedToParam,
- sortBy,
- desc,
- 1)));
+ boolean showNav = total > (2 * pageSize);
+ boolean allowPrev = page > 1;
+ boolean allowNext = (pageSize * (page - 1) + count) < total;
+ add(new BookmarkablePageLink<Void>("prevLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, repositoryId, page - 1)).setEnabled(allowPrev).setVisible(showNav));
+ add(new BookmarkablePageLink<Void>("nextLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, repositoryId, page + 1)).setEnabled(allowNext).setVisible(showNav));
- add(new BookmarkablePageLink<Void>("enhancementsQuery", MyTicketsPage.class,
- queryParameters(
- Lucene.type.matches(TicketModel.Type.Enhancement.name()),
- milestoneParam,
- statiiParam,
- assignedToParam,
- sortBy,
- desc,
- 1)));
+ if (total <= pageSize) {
+ add(new Label("pageLink").setVisible(false));
+ return;
+ }
- add(new BookmarkablePageLink<Void>("tasksQuery", MyTicketsPage.class,
- queryParameters(
- Lucene.type.matches(TicketModel.Type.Task.name()),
- milestoneParam,
- statiiParam,
- assignedToParam,
- sortBy,
- desc,
- 1)));
+ // determine page numbers to display
+ int pages = count == 0 ? 0 : ((total / pageSize) + (total % pageSize == 0 ? 0 : 1));
+ // preferred number of pagelinks
+ int segments = 5;
+ if (pages < segments) {
+ // not enough data for preferred number of page links
+ segments = pages;
+ }
+ int minpage = Math.min(Math.max(1, page - 2), pages - (segments - 1));
+ int maxpage = Math.min(pages, minpage + (segments - 1));
+ List<Integer> sequence = new ArrayList<Integer>();
+ for (int i = minpage; i <= maxpage; i++) {
+ sequence.add(i);
+ }
- add(new BookmarkablePageLink<Void>("questionsQuery", MyTicketsPage.class,
- queryParameters(
- Lucene.type.matches(TicketModel.Type.Question.name()),
- milestoneParam,
- statiiParam,
- assignedToParam,
- sortBy,
- desc,
- 1)));
+ ListDataProvider<Integer> pagesDp = new ListDataProvider<Integer>(sequence);
+ DataView<Integer> pagesView = new DataView<Integer>("pageLink", pagesDp) {
+ private static final long serialVersionUID = 1L;
- add(new BookmarkablePageLink<Void>("resetQuery", MyTicketsPage.class,
- queryParameters(
- null,
- milestoneParam,
- TicketsUI.openStatii,
- null,
- null,
- true,
- 1)));
+ @Override
+ public void populateItem(final Item<Integer> item) {
+ final Integer i = item.getModelObject();
+ LinkPanel link = new LinkPanel("page", null, "" + i, MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, repositoryId, i));
+ link.setRenderBodyOnly(true);
+ if (i == page) {
+ WicketUtils.setCssClass(item, "active");
+ }
+ item.add(link);
+ }
+ };
+ add(pagesView);
+ }
- add(new Label("userDivider"));
- add(new BookmarkablePageLink<Void>("createdQuery", MyTicketsPage.class,
- queryParameters(
- Lucene.createdby.matches(username),
- milestoneParam,
- statiiParam,
- assignedToParam,
- sortBy,
- desc,
- 1)));
+ private QueryBuilder initializeQueryBuilder(String queryparam, String username) {
+ final QueryBuilder qb = new QueryBuilder(queryparam);
- add(new BookmarkablePageLink<Void>("watchedQuery", MyTicketsPage.class,
- queryParameters(
- Lucene.watchedby.matches(username),
- milestoneParam,
- statiiParam,
- assignedToParam,
- sortBy,
- desc,
- 1)));
- add(new BookmarkablePageLink<Void>("mentionsQuery", MyTicketsPage.class,
- queryParameters(
- Lucene.mentions.matches(username),
- milestoneParam,
- statiiParam,
- assignedToParam,
- sortBy,
- desc,
- 1)));
- add(new BookmarkablePageLink<Void>("responsibleQuery", MyTicketsPage.class,
- queryParameters(
- Lucene.responsible.matches(username),
- milestoneParam,
- statiiParam,
- assignedToParam,
- sortBy,
- desc,
- 1)));
+ // focused "my tickets"
+ if (qb.containsField(Lucene.createdby.name())
+ || qb.containsField(Lucene.responsible.name())
+ || qb.containsField(Lucene.watchedby.name())
+ || qb.containsField(Lucene.mentions.name())) {
- // states
- if (ArrayUtils.isEmpty(statiiParam)) {
- add(new Label("selectedStatii", getString("gb.all")));
- } else {
- add(new Label("selectedStatii", StringUtils.flattenStrings(Arrays.asList(statiiParam), ",")));
- }
- add(new BookmarkablePageLink<Void>("openTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.openStatii, assignedToParam, sortBy, desc, 1)));
- add(new BookmarkablePageLink<Void>("closedTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.closedStatii, assignedToParam, sortBy, desc, 1)));
- add(new BookmarkablePageLink<Void>("allTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, null, assignedToParam, sortBy, desc, 1)));
+ return qb;
+ }
- // by status
- List<Status> statii = new ArrayList<Status>(Arrays.asList(Status.values()));
- statii.remove(Status.Closed);
- ListDataProvider<Status> resolutionsDp = new ListDataProvider<Status>(statii);
- DataView<Status> statiiLinks = new DataView<Status>("statii", resolutionsDp) {
- private static final long serialVersionUID = 1L;
+ // general "my tickets"
+ return qb.andSubquery()
+ .or(Lucene.createdby.matches(username))
+ .or(Lucene.responsible.matches(username))
+ .or(Lucene.watchedby.matches(username))
+ .or(Lucene.mentions.matches(username))
+ .endSubquery();
+ }
- @Override
- public void populateItem(final Item<Status> item) {
- final Status status = item.getModelObject();
- PageParameters p = queryParameters(queryParam, milestoneParam, new String [] { status.name().toLowerCase() }, assignedToParam, sortBy, desc, 1);
- String css = TicketsUI.getStatusClass(status);
- item.add(new LinkPanel("statusLink", css, status.toString(), MyTicketsPage.class, p).setRenderBodyOnly(true));
- }
- };
- add(statiiLinks);
+ private List<QueryResult> query(QueryBuilder qb, int page, int pageSize, String sortBy, boolean descending) {
+ return app().tickets().queryFor(qb.build(), page, pageSize, sortBy, descending);
+ }
- List<TicketSort> sortChoices = new ArrayList<TicketSort>();
- sortChoices.add(new TicketSort(getString("gb.sortNewest"), Lucene.created.name(), true));
- sortChoices.add(new TicketSort(getString("gb.sortOldest"), Lucene.created.name(), false));
- sortChoices.add(new TicketSort(getString("gb.sortMostRecentlyUpdated"), Lucene.updated.name(), true));
- sortChoices.add(new TicketSort(getString("gb.sortLeastRecentlyUpdated"), Lucene.updated.name(), false));
- sortChoices.add(new TicketSort(getString("gb.sortMostComments"), Lucene.comments.name(), true));
- sortChoices.add(new TicketSort(getString("gb.sortLeastComments"), Lucene.comments.name(), false));
- sortChoices.add(new TicketSort(getString("gb.sortMostPatchsetRevisions"), Lucene.patchsets.name(), true));
- sortChoices.add(new TicketSort(getString("gb.sortLeastPatchsetRevisions"), Lucene.patchsets.name(), false));
- sortChoices.add(new TicketSort(getString("gb.sortMostVotes"), Lucene.votes.name(), true));
- sortChoices.add(new TicketSort(getString("gb.sortLeastVotes"), Lucene.votes.name(), false));
+ private List<QueryResult> search(String searchParam, int page, int pageSize) {
+ return app().tickets().searchFor(null, searchParam, page, pageSize);
+ }
- TicketSort currentSort = sortChoices.get(0);
- for (TicketSort ts : sortChoices) {
- if (ts.sortBy.equals(sortBy) && desc == ts.desc) {
- currentSort = ts;
- break;
- }
- }
- add(new Label("currentSort", currentSort.name));
+ private List<RepositoryModel> correspondingRepositories(Collection<QueryResult> tickets) {
+ final HashMap<String, RepositoryModel> result = new HashMap<>();
+ for (QueryResult ticket : tickets) {
+ RepositoryModel repository = app().repositories().getRepositoryModel(ticket.repository);
+ if (!result.containsKey(repository.getRID())) {
+ result.put(repository.getRID(), repository);
+ }
+ }
- ListDataProvider<TicketSort> sortChoicesDp = new ListDataProvider<TicketSort>(sortChoices);
- DataView<TicketSort> sortMenu = new DataView<TicketSort>("sort", sortChoicesDp) {
- private static final long serialVersionUID = 1L;
-
- @Override
- public void populateItem(final Item<TicketSort> item) {
- final TicketSort ts = item.getModelObject();
- PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, assignedToParam, ts.sortBy, ts.desc, 1);
- item.add(new LinkPanel("sortLink", null, ts.name, MyTicketsPage.class, params).setRenderBodyOnly(true));
- }
- };
- add(sortMenu);
-
- // Build Query here
- QueryBuilder qb = new QueryBuilder(queryParam);
- if (!qb.containsField(Lucene.status.name()) && !ArrayUtils.isEmpty(statiiParam)) {
- // specify the states
- boolean not = false;
- QueryBuilder q = new QueryBuilder();
- for (String state : statiiParam) {
- if (state.charAt(0) == '!') {
- not = true;
- q.and(Lucene.status.doesNotMatch(state.substring(1)));
- } else {
- q.or(Lucene.status.matches(state));
- }
- }
- if (not) {
- qb.and(q.toString());
- } else {
- qb.and(q.toSubquery().toString());
- }
- }
-
- final String luceneQuery;
- if (qb.containsField(Lucene.createdby.name())
- || qb.containsField(Lucene.responsible.name())
- || qb.containsField(Lucene.watchedby.name())) {
- // focused "my tickets" query
- luceneQuery = qb.build();
- } else {
- // general "my tickets" query
- QueryBuilder myQuery = new QueryBuilder();
- myQuery.or(Lucene.createdby.matches(username));
- myQuery.or(Lucene.responsible.matches(username));
- myQuery.or(Lucene.watchedby.matches(username));
- myQuery.and(qb.toSubquery().toString());
- luceneQuery = myQuery.build();
- }
-
- // paging links
- int page = (params != null) ? Math.max(1, WicketUtils.getPage(params)) : 1;
- int pageSize = app().settings().getInteger(Keys.tickets.perPage, 25);
-
- List<QueryResult> results;
- if(StringUtils.isEmpty(searchParam)) {
- results = app().tickets().queryFor(luceneQuery, page, pageSize, sortBy, desc);
- } else {
- results = app().tickets().searchFor(null, searchParam, page, pageSize);
- }
-
- int totalResults = results.size() == 0 ? 0 : results.get(0).totalResults;
- buildPager(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, page, pageSize, results.size(), totalResults);
-
- final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true);
- add(new TicketListPanel("ticketList", results, showSwatch, true));
- }
-
- protected PageParameters queryParameters(
- String query,
- String milestone,
- String[] states,
- String assignedTo,
- String sort,
- boolean descending,
- int page) {
-
- PageParameters params = WicketUtils.newRepositoryParameter("");
- if (!StringUtils.isEmpty(query)) {
- params.add("q", query);
- }
- if (!StringUtils.isEmpty(milestone)) {
- params.add(Lucene.milestone.name(), milestone);
- }
- if (!ArrayUtils.isEmpty(states)) {
- for (String state : states) {
- params.add(Lucene.status.name(), state);
- }
- }
- if (!StringUtils.isEmpty(assignedTo)) {
- params.add(Lucene.responsible.name(), assignedTo);
- }
- if (!StringUtils.isEmpty(sort)) {
- params.add("sort", sort);
- }
- if (!descending) {
- params.add("direction", "asc");
- }
- if (page > 1) {
- params.add("pg", "" + page);
- }
- return params;
- }
-
- protected void buildPager(
- final String query,
- final String milestone,
- final String [] states,
- final String assignedTo,
- final String sort,
- final boolean desc,
- final int page,
- int pageSize,
- int count,
- int total) {
-
- boolean showNav = total > (2 * pageSize);
- boolean allowPrev = page > 1;
- boolean allowNext = (pageSize * (page - 1) + count) < total;
- add(new BookmarkablePageLink<Void>("prevLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, page - 1)).setEnabled(allowPrev).setVisible(showNav));
- add(new BookmarkablePageLink<Void>("nextLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, page + 1)).setEnabled(allowNext).setVisible(showNav));
-
- if (total <= pageSize) {
- add(new Label("pageLink").setVisible(false));
- return;
- }
-
- // determine page numbers to display
- int pages = count == 0 ? 0 : ((total / pageSize) + (total % pageSize == 0 ? 0 : 1));
- // preferred number of pagelinks
- int segments = 5;
- if (pages < segments) {
- // not enough data for preferred number of page links
- segments = pages;
- }
- int minpage = Math.min(Math.max(1, page - 2), pages - (segments - 1));
- int maxpage = Math.min(pages, minpage + (segments - 1));
- List<Integer> sequence = new ArrayList<Integer>();
- for (int i = minpage; i <= maxpage; i++) {
- sequence.add(i);
- }
-
- ListDataProvider<Integer> pagesDp = new ListDataProvider<Integer>(sequence);
- DataView<Integer> pagesView = new DataView<Integer>("pageLink", pagesDp) {
- private static final long serialVersionUID = 1L;
-
- @Override
- public void populateItem(final Item<Integer> item) {
- final Integer i = item.getModelObject();
- LinkPanel link = new LinkPanel("page", null, "" + i, MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, i));
- link.setRenderBodyOnly(true);
- if (i == page) {
- WicketUtils.setCssClass(item, "active");
- }
- item.add(link);
- }
- };
- add(pagesView);
- }
+ return new ArrayList<>(result.values());
+ }
}
diff --git a/src/main/java/com/gitblit/wicket/pages/NewMilestonePage.html b/src/main/java/com/gitblit/wicket/pages/NewMilestonePage.html
index 2ba5d5c..814aa53 100644
--- a/src/main/java/com/gitblit/wicket/pages/NewMilestonePage.html
+++ b/src/main/java/com/gitblit/wicket/pages/NewMilestonePage.html
@@ -5,6 +5,7 @@
lang="en">
<wicket:extend>
+
<body onload="document.getElementById('name').focus();">
<div class="container">
@@ -19,7 +20,7 @@
<!-- New Milestone Table -->
<table class="ticket">
<tr><th><wicket:message key="gb.milestone"></wicket:message></th><td class="edit"><input class="input-large" type="text" wicket:id="name" id="name"></input></td></tr>
- <tr><th><wicket:message key="gb.due"></wicket:message></th><td class="edit"><input class="input-large" type="text" wicket:id="due"></input> <span class="help-inline" wicket:id="dueFormat"></span></td></tr>
+ <tr><th><wicket:message key="gb.due"></wicket:message></th><td class="edit"><input class="input-large" type="date" wicket:id="due"></input> <span class="help-inline" wicket:id="dueFormat"></span></td></tr>
</table>
</div>
</div>
@@ -32,6 +33,5 @@
</form>
</div>
</body>
-
</wicket:extend>
</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/NewMilestonePage.java b/src/main/java/com/gitblit/wicket/pages/NewMilestonePage.java
index b452a91..2250f38 100644
--- a/src/main/java/com/gitblit/wicket/pages/NewMilestonePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/NewMilestonePage.java
@@ -21,7 +21,6 @@
import org.apache.wicket.RestartResponseException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
-import org.apache.wicket.extensions.markup.html.form.DateTextField;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
@@ -35,6 +34,7 @@
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.Html5DateField;
import com.gitblit.wicket.WicketUtils;
/**
@@ -78,9 +78,10 @@
dueModel = Model.of(new Date(System.currentTimeMillis() + TimeUtils.ONEDAY));
form.add(new TextField<String>("name", nameModel));
- form.add(new DateTextField("due", dueModel, "yyyy-MM-dd"));
+ form.add(new Html5DateField("due", dueModel, "yyyy-MM-dd"));
form.add(new Label("dueFormat", "yyyy-MM-dd"));
-
+ addBottomScriptInline("{var e=document.createElement('input');e.type='date';if(e.type=='date'){$('[name=\"due\"]~.help-inline').hide()}}");
+ addBottomScript("scripts/wicketHtml5Patch.js");
form.add(new AjaxButton("create") {
private static final long serialVersionUID = 1L;
diff --git a/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java
index b6c2359..d2589e6 100644
--- a/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java
@@ -359,14 +359,14 @@
}
}
} finally {
- revWalk.release();
+ revWalk.close();
}
} catch (UnsupportedEncodingException e) {
logger().error(null, e);
} catch (IOException e) {
logger().error(null, e);
} finally {
- odi.release();
+ odi.close();
db.close();
}
return success;
diff --git a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html
index 447c6aa..9b5af02 100644
--- a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html
@@ -39,6 +39,8 @@
</div>
</td></tr>
<tr><th><wicket:message key="gb.type"></wicket:message><span style="color:red;">*</span></th><td class="edit"><select class="input-large" wicket:id="type"></select></td></tr>
+ <tr><th><wicket:message key="gb.severity"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="severity"></select></td></tr>
+ <tr wicket:id="priority"></tr>
<tr wicket:id="responsible"></tr>
<tr wicket:id="milestone"></tr>
<tr wicket:id="mergeto"></tr>
@@ -67,5 +69,9 @@
<th><wicket:message key="gb.mergeTo"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="mergeto"></select></td>
</wicket:fragment>
+<wicket:fragment wicket:id="priorityFragment">
+ <th><wicket:message key="gb.priority"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="priority"></select></td>
+</wicket:fragment>
+
</wicket:extend>
</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java
index 8f28055..0c52505 100644
--- a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java
@@ -76,6 +76,10 @@
private Label descriptionPreview;
+ private IModel<TicketModel.Priority> priorityModel;
+
+ private IModel<TicketModel.Severity> severityModel;
+
public NewTicketPage(PageParameters params) {
super(params);
@@ -95,6 +99,8 @@
mergeToModel = Model.of(Repository.shortenRefName(getRepositoryModel().mergeTo));
responsibleModel = Model.of();
milestoneModel = Model.of();
+ severityModel = Model.of(TicketModel.Severity.defaultSeverity);
+ priorityModel = Model.of(TicketModel.Priority.defaultPriority);
setStatelessHint(false);
setOutputMarkupId(true);
@@ -105,6 +111,7 @@
form.add(new DropDownChoice<TicketModel.Type>("type", typeModel, Arrays.asList(TicketModel.Type.choices())));
form.add(new TextField<String>("title", titleModel));
form.add(new TextField<String>("topic", topicModel));
+ form.add(new DropDownChoice<TicketModel.Severity>("severity", severityModel, Arrays.asList(TicketModel.Severity.choices())));
final IModel<String> markdownPreviewModel = Model.of();
descriptionPreview = new Label("descriptionPreview", markdownPreviewModel);
@@ -152,6 +159,11 @@
milestone.add(new DropDownChoice<TicketMilestone>("milestone", milestoneModel, milestones));
form.add(milestone.setVisible(!milestones.isEmpty()));
+ // priority
+ Fragment priority = new Fragment("priority", "priorityFragment", this);
+ priority.add(new DropDownChoice<TicketModel.Priority>("priority", priorityModel, Arrays.asList(TicketModel.Priority.choices())));
+ form.add(priority);
+
// integration branch
List<String> branches = new ArrayList<String>();
for (String branch : getRepositoryModel().getLocalBranches()) {
@@ -171,6 +183,7 @@
form.add(new Label("responsible").setVisible(false));
form.add(new Label("milestone").setVisible(false));
form.add(new Label("mergeto").setVisible(false));
+ form.add(new Label("priority").setVisible(false));
}
form.add(new AjaxButton("create") {
@@ -212,6 +225,20 @@
change.setField(Field.milestone, milestone.name);
}
+ // severity
+ TicketModel.Severity severity = TicketModel.Severity.defaultSeverity;
+ if (severityModel.getObject() != null) {
+ severity = severityModel.getObject();
+ }
+ change.setField(Field.severity, severity);
+
+ // priority
+ TicketModel.Priority priority = TicketModel.Priority.defaultPriority;
+ if (priorityModel.getObject() != null) {
+ priority = priorityModel.getObject();
+ }
+ change.setField(Field.priority, priority);
+
// integration branch
String mergeTo = mergeToModel.getObject();
if (!StringUtils.isEmpty(mergeTo)) {
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html
index 2d446ec..fc28d17 100644
--- a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html
@@ -8,7 +8,7 @@
<wicket:extend>
<div class="container">
- <table class="repositories">
+ <table class="repositories projectlist">
<thead>
<tr>
<th class="left">
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java
index f04fa78..132a39d 100644
--- a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java
@@ -15,6 +15,7 @@
*/
package com.gitblit.wicket.pages;
+import java.util.Collections;
import java.util.List;
import org.apache.wicket.PageParameters;
@@ -69,6 +70,7 @@
}
List<ProjectModel> projects = getProjects(params);
+ Collections.sort(projects);
ListDataProvider<ProjectModel> dp = new ListDataProvider<ProjectModel>(projects);
diff --git a/src/main/java/com/gitblit/wicket/pages/ReflogPage.html b/src/main/java/com/gitblit/wicket/pages/ReflogPage.html
index c0ac7eb..bd59494 100644
--- a/src/main/java/com/gitblit/wicket/pages/ReflogPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/ReflogPage.html
@@ -9,7 +9,7 @@
<!-- pager links -->
<div class="page_nav2">
- <a wicket:id="firstPage"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPage">« <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPage"><wicket:message key="gb.pageNext"></wicket:message> »</a>
+ <a wicket:id="firstPageTop"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageTop">« <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageTop"><wicket:message key="gb.pageNext"></wicket:message> »</a>
</div>
<!-- ref log -->
@@ -17,7 +17,7 @@
<!-- pager links -->
<div style="padding-bottom:5px;">
- <a wicket:id="firstPage"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPage">« <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPage"><wicket:message key="gb.pageNext"></wicket:message> »</a>
+ <a wicket:id="firstPageBottom"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageBottom">« <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageBottom"><wicket:message key="gb.pageNext"></wicket:message> »</a>
</div>
</wicket:extend>
diff --git a/src/main/java/com/gitblit/wicket/pages/ReflogPage.java b/src/main/java/com/gitblit/wicket/pages/ReflogPage.java
index 29fd449..44fb222 100644
--- a/src/main/java/com/gitblit/wicket/pages/ReflogPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ReflogPage.java
@@ -40,15 +40,26 @@
boolean hasMore = reflogPanel.hasMore();
add(reflogPanel);
- add(new BookmarkablePageLink<Void>("firstPage", ReflogPage.class,
+ add(new BookmarkablePageLink<Void>("firstPageTop", ReflogPage.class,
WicketUtils.newObjectParameter(repositoryName, objectId))
.setEnabled(pageNumber > 1));
- add(new BookmarkablePageLink<Void>("prevPage", ReflogPage.class,
+ add(new BookmarkablePageLink<Void>("prevPageTop", ReflogPage.class,
WicketUtils.newLogPageParameter(repositoryName, objectId, prevPage))
.setEnabled(pageNumber > 1));
- add(new BookmarkablePageLink<Void>("nextPage", ReflogPage.class,
+ add(new BookmarkablePageLink<Void>("nextPageTop", ReflogPage.class,
WicketUtils.newLogPageParameter(repositoryName, objectId, nextPage))
.setEnabled(hasMore));
+
+ add(new BookmarkablePageLink<Void>("firstPageBottom", ReflogPage.class,
+ WicketUtils.newObjectParameter(repositoryName, objectId))
+ .setEnabled(pageNumber > 1));
+ add(new BookmarkablePageLink<Void>("prevPageBottom", ReflogPage.class,
+ WicketUtils.newLogPageParameter(repositoryName, objectId, prevPage))
+ .setEnabled(pageNumber > 1));
+ add(new BookmarkablePageLink<Void>("nextPageBottom", ReflogPage.class,
+ WicketUtils.newLogPageParameter(repositoryName, objectId, nextPage))
+ .setEnabled(hasMore));
+
}
@Override
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
index 22544bc..d413242 100644
--- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
@@ -24,13 +24,20 @@
</div>
<div class="span7">
- <div>
- <span class="project" wicket:id="projectTitle">[project title]</span>/<span class="repository" wicket:id="repositoryName">[repository name]</span>
- <a class="hidden-phone hidden-tablet" style="text-decoration: none;" wicket:id="syndication" wicket:message="title:gb.feed">
- <img style="border:0px;vertical-align:baseline;" src="feed_16x16.png"></img>
- </a>
+ <div style="display:inline-block;vertical-align:top;padding-right:5px;">
+ <span wicket:id="repoIcon"></span>
+ </div>
+ <div style="display:inline-block;">
+ <div>
+ <span class="project" wicket:id="projectTitle">[project title]</span>/<span class="repository" wicket:id="repositoryName">[repository name]</span>
+ <a class="hidden-phone hidden-tablet" style="text-decoration: none;" wicket:id="syndication" wicket:message="title:gb.feed">
+ <img style="border:0px;vertical-align:baseline;" src="feed_16x16.png"></img>
+ </a>
+ </div>
+ <div>
+ <span class="gray" wicket:id="originRepository">[origin repository]</span>
+ </div>
</div>
- <span wicket:id="originRepository">[origin repository]</span>
</div>
</div>
</div>
@@ -60,6 +67,22 @@
<i wicket:id="icon" style="padding-right:3px;"></i><span wicket:id="label"></span>
</wicket:fragment>
+ <wicket:fragment wicket:id="repoIconFragment">
+ <span class="gray mega-octicon octicon-repo"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="mirrorIconFragment">
+ <span class="gray mega-octicon octicon-mirror"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="forkIconFragment">
+ <span class="gray mega-octicon octicon-repo-forked"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="cloneIconFragment">
+ <span class="gray mega-octicon octicon-repo-push" wicket:message="title:gb.workingCopyWarning"></span>
+ </wicket:fragment>
+
<wicket:fragment wicket:id="originFragment">
<p class="originRepository"><wicket:message key="gb.forkedFrom">[forked from]</wicket:message> <span wicket:id="originRepository">[origin repository]</span></p>
</wicket:fragment>
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
index 134ee04..36c5ae1 100644
--- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
@@ -64,6 +64,7 @@
import com.gitblit.utils.BugtraqProcessor;
import com.gitblit.utils.DeepCopier;
import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.ModelUtils;
import com.gitblit.utils.RefLogUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.CacheControl;
@@ -73,6 +74,7 @@
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.NavigationPanel;
import com.gitblit.wicket.panels.RefsPanel;
+import com.google.common.base.Optional;
public abstract class RepositoryPage extends RootPage {
@@ -267,8 +269,15 @@
@Override
protected void setupPage(String repositoryName, String pageName) {
+
+ //This method should only be called once in the page lifecycle.
+ //However, it must be called after the constructor has run, hence not in onInitialize
+ //It may be attempted to be called again if an info or error message is displayed.
+ if (get("projectTitle") != null) { return; }
+
String projectName = StringUtils.getFirstPathElement(repositoryName);
ProjectModel project = app().projects().getProjectModel(projectName);
+
if (project.isUserProject()) {
// user-as-project
add(new LinkPanel("projectTitle", null, project.getDisplayName(),
@@ -295,25 +304,38 @@
RepositoryModel model = getRepositoryModel();
if (StringUtils.isEmpty(model.originRepository)) {
if (model.isMirror) {
+ add(new Fragment("repoIcon", "mirrorIconFragment", this));
Fragment mirrorFrag = new Fragment("originRepository", "mirrorFragment", this);
Label lbl = new Label("originRepository", MessageFormat.format(getString("gb.mirrorOf"), "<b>" + model.origin + "</b>"));
mirrorFrag.add(lbl.setEscapeModelStrings(false));
add(mirrorFrag);
} else {
- add(new Label("originRepository").setVisible(false));
+ if (model.isBare) {
+ add(new Fragment("repoIcon", "repoIconFragment", this));
+ } else {
+ add(new Fragment("repoIcon", "cloneIconFragment", this));
+ }
+ add(new Label("originRepository", Optional.of(model.description).or("")));
}
} else {
RepositoryModel origin = app().repositories().getRepositoryModel(model.originRepository);
if (origin == null) {
- // no origin repository
- add(new Label("originRepository").setVisible(false));
+ // no origin repository, show description if available
+ if (model.isBare) {
+ add(new Fragment("repoIcon", "repoIconFragment", this));
+ } else {
+ add(new Fragment("repoIcon", "cloneIconFragment", this));
+ }
+ add(new Label("originRepository", Optional.of(model.description).or("")));
} else if (!user.canView(origin)) {
// show origin repository without link
+ add(new Fragment("repoIcon", "forkIconFragment", this));
Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
forkFrag.add(new Label("originRepository", StringUtils.stripDotGit(model.originRepository)));
add(forkFrag);
} else {
// link to origin repository
+ add(new Fragment("repoIcon", "forkIconFragment", this));
Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
forkFrag.add(new LinkPanel("originRepository", null, StringUtils.stripDotGit(model.originRepository),
SummaryPage.class, WicketUtils.newRepositoryParameter(model.originRepository)));
@@ -356,8 +378,10 @@
add(new ExternalLink("myForkLink", "").setVisible(false));
} else {
String fork = app().repositories().getFork(user.username, model.name);
+ String userRepo = ModelUtils.getPersonalPath(user.username) + "/" + StringUtils.stripDotGit(StringUtils.getLastPathElement(model.name));
+ boolean hasUserRepo = app().repositories().hasRepository(userRepo);
boolean hasFork = fork != null;
- boolean canFork = user.canFork(model) && model.hasCommits;
+ boolean canFork = user.canFork(model) && model.hasCommits && !hasUserRepo;
if (hasFork || !canFork) {
// user not allowed to fork or fork already exists or repo forbids forking
@@ -645,6 +669,7 @@
}
}
+
@Override
protected void onBeforeRender() {
// dispose of repository object
@@ -652,8 +677,10 @@
r.close();
r = null;
}
+
// setup page header and footer
setupPage(getRepositoryName(), "/ " + getPageName());
+
super.onBeforeRender();
}
diff --git a/src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java b/src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java
index 8aec9e6..ceca1ec 100644
--- a/src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java
@@ -76,8 +76,9 @@
sb.append(asParam(p, proposal.name, "exclude", ""));
sb.append(asParam(p, proposal.name, "include", ""));
+ final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4);
add(new Label("definition", StringUtils.breakLinesForHtml(StringUtils.escapeForHtml(sb
- .toString().trim(), true))).setEscapeModelStrings(false));
+ .toString().trim(), true, tabLength))).setEscapeModelStrings(false));
List<RepositoryModel> repositories = new ArrayList<RepositoryModel>(
proposal.repositories.values());
diff --git a/src/main/java/com/gitblit/wicket/pages/RootPage.html b/src/main/java/com/gitblit/wicket/pages/RootPage.html
index 2ff305f..c51db81 100644
--- a/src/main/java/com/gitblit/wicket/pages/RootPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/RootPage.html
@@ -14,7 +14,7 @@
<span class="icon-bar"></span>
</a>
<a class="brand" wicket:id="rootLink">
- <img src="logo.png" height="45" width="120" class="logo"/>
+ <img src="logo.png" width="120" class="logo"/>
</a>
<div class="nav-collapse">
diff --git a/src/main/java/com/gitblit/wicket/pages/RootPage.java b/src/main/java/com/gitblit/wicket/pages/RootPage.java
index c4d4dd1..12779ca 100644
--- a/src/main/java/com/gitblit/wicket/pages/RootPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/RootPage.java
@@ -36,11 +36,11 @@
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.PageParameters;
+import org.apache.wicket.RequestCycle;
import org.apache.wicket.behavior.HeaderContributor;
import org.apache.wicket.markup.html.IHeaderContributor;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.panel.Fragment;
@@ -71,9 +71,10 @@
import com.gitblit.utils.ModelUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.NonTrimmedPasswordTextField;
import com.gitblit.wicket.SessionlessForm;
import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.AvatarImage;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.NavigationPanel;
@@ -151,6 +152,7 @@
boolean authenticateAdmin = app().settings().getBoolean(Keys.web.authenticateAdminPages, true);
boolean allowAdmin = app().settings().getBoolean(Keys.web.allowAdministration, true);
boolean allowLucene = app().settings().getBoolean(Keys.web.allowLuceneIndexing, true);
+ boolean displayUserPanel = app().settings().getBoolean(Keys.web.displayUserPanel, true);
boolean isLoggedIn = GitBlitWebSession.get().isLoggedIn();
if (authenticateAdmin) {
@@ -168,7 +170,7 @@
}
}
- if (authenticateView || authenticateAdmin) {
+ if (displayUserPanel && (authenticateView || authenticateAdmin)) {
if (isLoggedIn) {
UserMenu userFragment = new UserMenu("userPanel", "userMenuFragment", RootPage.this);
add(userFragment);
@@ -183,6 +185,11 @@
// navigation links
List<NavLink> navLinks = new ArrayList<NavLink>();
if (!authenticateView || (authenticateView && isLoggedIn)) {
+ UserModel user = UserModel.ANONYMOUS;
+ if (isLoggedIn) {
+ user = GitBlitWebSession.get().getUser();
+ }
+
navLinks.add(new PageNavLink(isLoggedIn ? "gb.myDashboard" : "gb.dashboard", MyDashboardPage.class,
getRootPageParameters()));
if (isLoggedIn && app().tickets().isReady()) {
@@ -190,6 +197,9 @@
}
navLinks.add(new PageNavLink("gb.repositories", RepositoriesPage.class,
getRootPageParameters()));
+
+ navLinks.add(new PageNavLink("gb.filestore", FilestorePage.class, getRootPageParameters()));
+
navLinks.add(new PageNavLink("gb.activity", ActivityPage.class, getRootPageParameters()));
if (allowLucene) {
navLinks.add(new PageNavLink("gb.search", LuceneSearchPage.class));
@@ -199,11 +209,6 @@
addDropDownMenus(navLinks);
}
- UserModel user = UserModel.ANONYMOUS;
- if (isLoggedIn) {
- user = GitBlitWebSession.get().getUser();
- }
-
// add nav link extensions
List<NavLinkExtension> extensions = app().plugins().getExtensions(NavLinkExtension.class);
for (NavLinkExtension ext : extensions) {
@@ -278,7 +283,7 @@
request = ((WebRequest) getRequest()).getHttpServletRequest();
response = ((WebResponse) getResponse()).getHttpServletResponse();
- request.getSession().setAttribute(Constants.AUTHENTICATION_TYPE, AuthenticationType.CREDENTIALS);
+ request.getSession().setAttribute(Constants.ATTRIB_AUTHTYPE, AuthenticationType.CREDENTIALS);
// Set Cookie
app().authentication().setCookie(request, response, user);
@@ -564,7 +569,9 @@
String username = RootPage.this.username.getObject();
char[] password = RootPage.this.password.getObject().toCharArray();
- UserModel user = app().authentication().authenticate(username, password);
+ HttpServletRequest request = ((WebRequest)RequestCycle.get().getRequest()).getHttpServletRequest();
+
+ UserModel user = app().authentication().authenticate(username, password, request.getRemoteAddr());
if (user == null) {
error(getString("gb.invalidUsernameOrPassword"));
} else if (user.username.equals(Constants.FEDERATION_USER)) {
@@ -580,7 +587,7 @@
TextField<String> unameField = new TextField<String>("username", username);
WicketUtils.setInputPlaceholder(unameField, markupProvider.getString("gb.username"));
loginForm.add(unameField);
- PasswordTextField pwField = new PasswordTextField("password", password);
+ NonTrimmedPasswordTextField pwField = new NonTrimmedPasswordTextField("password", password);
WicketUtils.setInputPlaceholder(pwField, markupProvider.getString("gb.password"));
loginForm.add(pwField);
add(loginForm);
@@ -607,11 +614,11 @@
UserModel user = session.getUser();
boolean editCredentials = app().authentication().supportsCredentialChanges(user);
HttpServletRequest request = ((WebRequest) getRequest()).getHttpServletRequest();
- AuthenticationType authenticationType = (AuthenticationType) request.getSession().getAttribute(Constants.AUTHENTICATION_TYPE);
- boolean standardLogin = authenticationType.isStandard();
+ AuthenticationType authenticationType = (AuthenticationType) request.getAttribute(Constants.ATTRIB_AUTHTYPE);
+ boolean standardLogin = (null != authenticationType) ? authenticationType.isStandard() : true;
if (app().settings().getBoolean(Keys.web.allowGravatar, true)) {
- add(new GravatarImage("username", user, "navbarGravatar", 20, false));
+ add(new AvatarImage("username", user, "navbarGravatar", 20, false));
} else {
add(new Label("username", user.getDisplayName()));
}
diff --git a/src/main/java/com/gitblit/wicket/pages/SessionPage.java b/src/main/java/com/gitblit/wicket/pages/SessionPage.java
index 0dda949..bcf8e97 100644
--- a/src/main/java/com/gitblit/wicket/pages/SessionPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/SessionPage.java
@@ -56,34 +56,50 @@
HttpServletRequest request = ((WebRequest) getRequest()).getHttpServletRequest();
HttpServletResponse response = ((WebResponse) getResponse()).getHttpServletResponse();
- if (session.isLoggedIn() && !session.isSessionInvalidated()) {
- // already have a session, refresh usermodel to pick up
- // any changes to permissions or roles (issue-186)
- UserModel user = app().users().getUserModel(session.getUser().username);
+ // If using container/external servlet authentication, use request attribute
+ String authedUser = (String) request.getAttribute(Constants.ATTRIB_AUTHUSER);
- if (user == null || user.disabled) {
- // user was deleted/disabled during session
- app().authentication().logout(request, response, user);
- session.setUser(null);
- session.invalidateNow();
- return;
+ // Default to trusting session authentication if not set in request by external processing
+ if (StringUtils.isEmpty(authedUser) && session.isLoggedIn()) {
+ authedUser = session.getUsername();
+ }
+
+ if (!StringUtils.isEmpty(authedUser)) {
+ // Avoid session fixation for non-session authentication
+ // If the authenticated user is different from the session user, discard
+ // the old session entirely, without trusting any session values
+ if (!authedUser.equals(session.getUsername())) {
+ session.replaceSession();
}
- // validate cookie during session (issue-361)
- if (user != null && app().settings().getBoolean(Keys.web.allowCookieAuthentication, true)) {
- String requestCookie = app().authentication().getCookie(request);
- if (!StringUtils.isEmpty(requestCookie) && !StringUtils.isEmpty(user.cookie)) {
- if (!requestCookie.equals(user.cookie)) {
- // cookie was changed during our session
- app().authentication().logout(request, response, user);
- session.setUser(null);
- session.invalidateNow();
- return;
+ if (!session.isSessionInvalidated()) {
+ // Refresh usermodel to pick up any changes to permissions or roles (issue-186)
+ UserModel user = app().users().getUserModel(authedUser);
+
+ if (user == null || user.disabled) {
+ // user was deleted/disabled during session
+ app().authentication().logout(request, response, user);
+ session.setUser(null);
+ session.invalidateNow();
+ return;
+ }
+
+ // validate cookie during session (issue-361)
+ if (app().settings().getBoolean(Keys.web.allowCookieAuthentication, true)) {
+ String requestCookie = app().authentication().getCookie(request);
+ if (!StringUtils.isEmpty(requestCookie) && !StringUtils.isEmpty(user.cookie)) {
+ if (!requestCookie.equals(user.cookie)) {
+ // cookie was changed during our session
+ app().authentication().logout(request, response, user);
+ session.setUser(null);
+ session.invalidateNow();
+ return;
+ }
}
}
+ session.setUser(user);
+ return;
}
- session.setUser(user);
- return;
}
// try to authenticate by servlet request
@@ -91,16 +107,17 @@
// Login the user
if (user != null) {
- // preserve the authentication type across session replacement
- AuthenticationType authenticationType = (AuthenticationType) request.getSession()
- .getAttribute(Constants.AUTHENTICATION_TYPE);
+ AuthenticationType authenticationType = (AuthenticationType) request.getAttribute(Constants.ATTRIB_AUTHTYPE);
// issue 62: fix session fixation vulnerability
- session.replaceSession();
+ // but only if authentication was done in the container.
+ // It avoid double change of session, that some authentication method
+ // don't like
+ if (AuthenticationType.CONTAINER != authenticationType) {
+ session.replaceSession();
+ }
session.setUser(user);
- request.getSession().setAttribute(Constants.AUTHENTICATION_TYPE, authenticationType);
-
// Set Cookie
app().authentication().setCookie(request, response, user);
diff --git a/src/main/java/com/gitblit/wicket/pages/TagPage.java b/src/main/java/com/gitblit/wicket/pages/TagPage.java
index 9eed279..ffeea6f 100644
--- a/src/main/java/com/gitblit/wicket/pages/TagPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TagPage.java
@@ -31,7 +31,7 @@
import com.gitblit.wicket.CacheControl;
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.AvatarImage;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.RefsPanel;
@@ -77,7 +77,7 @@
linkClass = CommitPage.class;
break;
}
- add(new GravatarImage("taggerAvatar", tagRef.getAuthorIdent()));
+ add(new AvatarImage("taggerAvatar", tagRef.getAuthorIdent()));
add(new RefsPanel("tagName", repositoryName, Arrays.asList(tagRef)));
add(new Label("tagId", tagRef.getObjectId().getName()));
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.html b/src/main/java/com/gitblit/wicket/pages/TicketPage.html
index f3f38ec..974dcc0 100644
--- a/src/main/java/com/gitblit/wicket/pages/TicketPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.html
@@ -67,6 +67,8 @@
<div style="border: 1px solid #ccc;padding: 10px;margin: 5px 0px;">
<table class="summary" style="width: 100%">
<tr><th><wicket:message key="gb.type"></wicket:message></th><td><span wicket:id="ticketType">[type]</span></td></tr>
+ <tr><th><wicket:message key="gb.priority"></wicket:message></th><td><span wicket:id="priority">[priority]</span></td></tr>
+ <tr><th><wicket:message key="gb.severity"></wicket:message></th><td><span wicket:id="severity">[severity]</span></td></tr>
<tr><th><wicket:message key="gb.topic"></wicket:message></th><td><span wicket:id="ticketTopic">[topic]</span></td></tr>
<tr><th><wicket:message key="gb.responsible"></wicket:message></th><td><span wicket:id="responsible">[responsible]</span></td></tr>
<tr><th><wicket:message key="gb.milestone"></wicket:message></th><td><span wicket:id="milestone">[milestone]</span></td></tr>
@@ -460,6 +462,7 @@
<span wicket:id="patchsetType">[revision type]</span>
</td>
<td><span class="hidden-phone hidden-tablet aui-lozenge aui-lozenge-subtle" wicket:id="patchsetRevision">[R1]</span>
+ <span class="fa fa-fw" style="padding-left:15px;"><a wicket:id="deleteRevision" class="fa fa-fw fa-trash delete-patchset"></a></span>
<span class="hidden-tablet hidden-phone" style="padding-left:15px;"><span wicket:id="patchsetDiffStat"></span></span>
</td>
<td style="text-align:right;"><span class="attribution-date" wicket:id="changeDate">[patch date]</span></td>
@@ -555,7 +558,6 @@
</div>
</wicket:fragment>
-
<!-- VETOED PATCHSET FRAGMENT -->
<wicket:fragment wicket:id="vetoedFragment">
<div class="alert alert-error submit-info" style="padding:4px;">
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.java b/src/main/java/com/gitblit/wicket/pages/TicketPage.java
index 19788f2..b2e63a6 100644
--- a/src/main/java/com/gitblit/wicket/pages/TicketPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.java
@@ -15,6 +15,7 @@
*/
package com.gitblit.wicket.pages;
+import java.io.IOException;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
@@ -35,6 +36,7 @@
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.PageParameters;
+import org.apache.wicket.RequestCycle;
import org.apache.wicket.RestartResponseException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.SimpleAttributeModifier;
@@ -42,12 +44,17 @@
import org.apache.wicket.markup.html.image.ContextImage;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.html.link.StatelessLink;
+import org.apache.wicket.markup.html.pages.RedirectPage;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import org.apache.wicket.model.Model;
+import org.apache.wicket.protocol.http.RequestUtils;
import org.apache.wicket.protocol.http.WebRequest;
+import org.apache.wicket.request.target.basic.RedirectRequestTarget;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
@@ -82,16 +89,19 @@
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.JGitUtils.MergeStatus;
+import com.gitblit.utils.CommitCache;
import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.RefLogUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.TicketsUI;
import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.AvatarImage;
+import com.gitblit.wicket.panels.BasePanel.JavascriptEventConfirmation;
import com.gitblit.wicket.panels.BasePanel.JavascriptTextPrompt;
import com.gitblit.wicket.panels.CommentPanel;
import com.gitblit.wicket.panels.DiffStatPanel;
-import com.gitblit.wicket.panels.GravatarImage;
import com.gitblit.wicket.panels.IconAjaxLink;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.ShockWaveComponent;
@@ -312,7 +322,7 @@
if (user == null) {
user = new UserModel(username);
}
- item.add(new GravatarImage("participant", user.getDisplayName(),
+ item.add(new AvatarImage("participant", user.getDisplayName(),
user.emailAddress, null, 25, true));
}
};
@@ -519,6 +529,10 @@
* TICKET METADATA
*/
add(new Label("ticketType", ticket.type.toString()));
+
+ add(new Label("priority", ticket.priority.toString()));
+ add(new Label("severity", ticket.severity.toString()));
+
if (StringUtils.isEmpty(ticket.topic)) {
add(new Label("ticketTopic").setVisible(false));
} else {
@@ -529,6 +543,8 @@
}
+
+
/*
* VOTERS
*/
@@ -730,7 +746,7 @@
} else {
// permit user to comment
Fragment newComment = new Fragment("newComment", "newCommentFragment", this);
- GravatarImage img = new GravatarImage("newCommentAvatar", user.username, user.emailAddress,
+ AvatarImage img = new AvatarImage("newCommentAvatar", user.username, user.emailAddress,
"gravatar-round", avatarWidth, true);
newComment.add(img);
CommentPanel commentPanel = new CommentPanel("commentPanel", user, ticket, null, TicketsPage.class);
@@ -746,7 +762,7 @@
if (currentPatchset == null) {
// no patchset available
RepositoryUrl repoUrl = getRepositoryUrl(user, repository);
- boolean canPropose = repoUrl != null && repoUrl.permission.atLeast(AccessPermission.CLONE) && !UserModel.ANONYMOUS.equals(user);
+ boolean canPropose = repoUrl != null && repoUrl.hasPermission() && repoUrl.permission.atLeast(AccessPermission.CLONE) && !UserModel.ANONYMOUS.equals(user);
if (ticket.isOpen() && app().tickets().isAcceptingNewPatchsets(repository) && canPropose) {
// ticket & repo will accept a proposal patchset
// show the instructions for proposing a patchset
@@ -810,14 +826,14 @@
public void populateItem(final Item<RevCommit> item) {
RevCommit commit = item.getModelObject();
PersonIdent author = commit.getAuthorIdent();
- item.add(new GravatarImage("authorAvatar", author.getName(), author.getEmailAddress(), null, 16, false));
+ item.add(new AvatarImage("authorAvatar", author.getName(), author.getEmailAddress(), null, 16, false));
item.add(new Label("author", commit.getAuthorIdent().getName()));
item.add(new LinkPanel("commitId", null, getShortObjectId(commit.getName()),
CommitPage.class, WicketUtils.newObjectParameter(repositoryName, commit.getName()), true));
item.add(new LinkPanel("diff", "link", getString("gb.diff"), CommitDiffPage.class,
WicketUtils.newObjectParameter(repositoryName, commit.getName()), true));
item.add(new Label("title", StringUtils.trimString(commit.getShortMessage(), Constants.LEN_SHORTLOG_REFS)));
- item.add(WicketUtils.createDateLabel("commitDate", JGitUtils.getCommitDate(commit), GitBlitWebSession
+ item.add(WicketUtils.createDateLabel("commitDate", JGitUtils.getAuthorDate(commit), GitBlitWebSession
.get().getTimezone(), getTimeUtils(), false));
item.add(new DiffStatPanel("commitDiffStat", 0, 0, true));
}
@@ -847,6 +863,9 @@
if (event.hasPatchset()) {
// patchset
Patchset patchset = event.patchset;
+ //In the case of using a cached change list
+ item.setVisible(!patchset.isDeleted());
+
String what;
if (event.isStatusChange() && (Status.New == event.getStatus())) {
what = getString("gb.proposedThisChange");
@@ -874,6 +893,14 @@
}
item.add(typeLabel);
+ Link<Void> deleteLink = createDeletePatchsetLink(repository, patchset);
+
+ if (user.canDeleteRef(repository)) {
+ item.add(deleteLink.setVisible(patchset.canDelete));
+ } else {
+ item.add(deleteLink.setVisible(false));
+ }
+
// show commit diffstat
item.add(new DiffStatPanel("patchsetDiffStat", patchset.insertions, patchset.deletions, patchset.rev > 1));
} else if (event.hasComment()) {
@@ -881,6 +908,7 @@
item.add(new Label("what", getString("gb.commented")));
item.add(new Label("patchsetRevision").setVisible(false));
item.add(new Label("patchsetType").setVisible(false));
+ item.add(new Label("deleteRevision").setVisible(false));
item.add(new Label("patchsetDiffStat").setVisible(false));
} else if (event.hasReview()) {
// review
@@ -900,11 +928,13 @@
.setEscapeModelStrings(false));
item.add(new Label("patchsetRevision").setVisible(false));
item.add(new Label("patchsetType").setVisible(false));
+ item.add(new Label("deleteRevision").setVisible(false));
item.add(new Label("patchsetDiffStat").setVisible(false));
} else {
// field change
item.add(new Label("patchsetRevision").setVisible(false));
item.add(new Label("patchsetType").setVisible(false));
+ item.add(new Label("deleteRevision").setVisible(false));
item.add(new Label("patchsetDiffStat").setVisible(false));
String what = "";
@@ -981,12 +1011,12 @@
UserModel commenter = app().users().getUserModel(entry.author);
if (commenter == null) {
// unknown user
- container.add(new GravatarImage("changeAvatar", entry.author,
+ container.add(new AvatarImage("changeAvatar", entry.author,
entry.author, null, avatarSize, false).setVisible(avatarSize > 0));
container.add(new Label("changeAuthor", entry.author.toLowerCase()));
} else {
// known user
- container.add(new GravatarImage("changeAvatar", commenter.getDisplayName(),
+ container.add(new AvatarImage("changeAvatar", commenter.getDisplayName(),
commenter.emailAddress, avatarSize > 24 ? "gravatar-round" : null,
avatarSize, true).setVisible(avatarSize > 0));
container.add(new LinkPanel("changeAuthor", null, commenter.getDisplayName(),
@@ -1425,6 +1455,12 @@
Fragment mergePanel = new Fragment("mergePanel", "alreadyMergedFragment", this);
mergePanel.add(new Label("mergeTitle", MessageFormat.format(getString("gb.patchsetAlreadyMerged"), ticket.mergeTo)));
return mergePanel;
+ } else if (MergeStatus.MISSING_INTEGRATION_BRANCH == mergeStatus) {
+ // target/integration branch is missing
+ Fragment mergePanel = new Fragment("mergePanel", "notMergeableFragment", this);
+ mergePanel.add(new Label("mergeTitle", MessageFormat.format(getString("gb.patchsetNotMergeable"), ticket.mergeTo)));
+ mergePanel.add(new Label("mergeMore", MessageFormat.format(getString("gb.missingIntegrationBranchMore"), ticket.mergeTo)));
+ return mergePanel;
} else {
// patchset can not be cleanly merged
Fragment mergePanel = new Fragment("mergePanel", "notMergeableFragment", this);
@@ -1503,7 +1539,7 @@
*/
protected RepositoryUrl getRepositoryUrl(UserModel user, RepositoryModel repository) {
HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
- List<RepositoryUrl> urls = app().gitblit().getRepositoryUrls(req, user, repository);
+ List<RepositoryUrl> urls = app().services().getRepositoryUrls(req, user, repository);
if (ArrayUtils.isEmpty(urls)) {
return null;
}
@@ -1588,4 +1624,85 @@
return copyFragment;
}
}
+
+ private Link<Void> createDeletePatchsetLink(final RepositoryModel repositoryModel, final Patchset patchset)
+ {
+ Link<Void> deleteLink = new Link<Void>("deleteRevision") {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick() {
+ Repository r = app().repositories().getRepository(repositoryModel.name);
+ UserModel user = GitBlitWebSession.get().getUser();
+
+ if (r == null) {
+ if (app().repositories().isCollectingGarbage(repositoryModel.name)) {
+ error(MessageFormat.format(getString("gb.busyCollectingGarbage"), repositoryModel.name));
+ } else {
+ error(MessageFormat.format("Failed to find repository {0}", repositoryModel.name));
+ }
+ return;
+ }
+
+ //Construct the ref name based on the patchset
+ String ticketShard = String.format("%02d", ticket.number);
+ ticketShard = ticketShard.substring(ticketShard.length() - 2);
+ final String refName = String.format("%s%s/%d/%d", Constants.R_TICKETS_PATCHSETS, ticketShard, ticket.number, patchset.number);
+
+ Ref ref = null;
+ boolean success = true;
+
+ try {
+ ref = r.getRef(refName);
+
+ if (ref != null) {
+ success = JGitUtils.deleteBranchRef(r, ref.getName());
+ } else {
+ success = false;
+ }
+
+ if (success) {
+ // clear commit cache
+ CommitCache.instance().clear(repositoryModel.name, refName);
+
+ // optionally update reflog
+ if (RefLogUtils.hasRefLogBranch(r)) {
+ RefLogUtils.deleteRef(user, r, ref);
+ }
+
+ TicketModel updatedTicket = app().tickets().deletePatchset(ticket, patchset, user.username);
+
+ if (updatedTicket == null) {
+ success = false;
+ }
+ }
+ } catch (IOException e) {
+ logger().error("failed to determine ticket from ref", e);
+ success = false;
+ } finally {
+ r.close();
+ }
+
+ if (success) {
+ getSession().info(MessageFormat.format(getString("gb.deletePatchsetSuccess"), patchset.number));
+ logger().info(MessageFormat.format("{0} deleted patchset {1} from ticket {2}",
+ user.username, patchset.number, ticket.number));
+ } else {
+ getSession().error(MessageFormat.format(getString("gb.deletePatchsetFailure"),patchset.number));
+ }
+
+ //Force reload of the page to rebuild ticket change cache
+ String relativeUrl = urlFor(TicketsPage.class, getPageParameters()).toString();
+ String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
+ setResponsePage(new RedirectPage(absoluteUrl));
+ }
+ };
+
+ WicketUtils.setHtmlTooltip(deleteLink, MessageFormat.format(getString("gb.deletePatchset"), patchset.number));
+
+ deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(getString("gb.deletePatchset"), patchset.number)));
+
+ return deleteLink;
+ }
+
}
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketsPage.html b/src/main/java/com/gitblit/wicket/pages/TicketsPage.html
index 3a3d977..c686d1f 100644
--- a/src/main/java/com/gitblit/wicket/pages/TicketsPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/TicketsPage.html
@@ -31,17 +31,18 @@
<div class="hidden-phone">
<ul class="nav nav-list">
<li class="nav-header"><wicket:message key="gb.queries"></wicket:message></li>
- <li><a wicket:id="changesQuery"><i class="fa fa-code-fork"></i> <wicket:message key="gb.proposalTickets"></wicket:message></a></li>
- <li><a wicket:id="bugsQuery"><i class="fa fa-bug"></i> <wicket:message key="gb.bugTickets"></wicket:message></a></li>
- <li><a wicket:id="enhancementsQuery"><i class="fa fa-magic"></i> <wicket:message key="gb.enhancementTickets"></wicket:message></a></li>
- <li><a wicket:id="tasksQuery"><i class="fa fa-ticket"></i> <wicket:message key="gb.taskTickets"></wicket:message></a></li>
- <li><a wicket:id="questionsQuery"><i class="fa fa-question"></i> <wicket:message key="gb.questionTickets"></wicket:message></a></li>
+ <li><a wicket:id="changesQuery"><i class="fa fa-code-fork fa-fw"></i> <wicket:message key="gb.proposalTickets"></wicket:message></a></li>
+ <li><a wicket:id="bugsQuery"><i class="fa fa-bug fa-fw"></i> <wicket:message key="gb.bugTickets"></wicket:message></a></li>
+ <li><a wicket:id="enhancementsQuery"><i class="fa fa-magic fa-fw"></i> <wicket:message key="gb.enhancementTickets"></wicket:message></a></li>
+ <li><a wicket:id="tasksQuery"><i class="fa fa-ticket fa-fw"></i> <wicket:message key="gb.taskTickets"></wicket:message></a></li>
+ <li><a wicket:id="questionsQuery"><i class="fa fa-question fa-fw"></i> <wicket:message key="gb.questionTickets"></wicket:message></a></li>
+ <li><a wicket:id="maintenanceQuery"><i class="fa fa-cogs fa-fw"></i> <wicket:message key="gb.maintenanceTickets"></wicket:message></a></li>
<li wicket:id="userDivider" class="divider"></li>
- <li><a wicket:id="createdQuery"><i class="fa fa-user"></i> <wicket:message key="gb.yourCreatedTickets"></wicket:message></a></li>
- <li><a wicket:id="watchedQuery"><i class="fa fa-eye"></i> <wicket:message key="gb.yourWatchedTickets"></wicket:message></a></li>
- <li><a wicket:id="mentionsQuery"><i class="fa fa-comment"></i> <wicket:message key="gb.mentionsMeTickets"></wicket:message></a></li>
+ <li><a wicket:id="createdQuery"><i class="fa fa-user fa-fw"></i> <wicket:message key="gb.yourCreatedTickets"></wicket:message></a></li>
+ <li><a wicket:id="watchedQuery"><i class="fa fa-eye fa-fw"></i> <wicket:message key="gb.yourWatchedTickets"></wicket:message></a></li>
+ <li><a wicket:id="mentionsQuery"><i class="fa fa-comment fa-fw"></i> <wicket:message key="gb.mentionsMeTickets"></wicket:message></a></li>
<li class="divider"></li>
- <li><a wicket:id="resetQuery"><i class="fa fa-bolt"></i> <wicket:message key="gb.reset"></wicket:message></a></li>
+ <li><a wicket:id="resetQuery"><i class="fa fa-bolt fa-fw"></i> <wicket:message key="gb.reset"></wicket:message></a></li>
</ul>
</div>
<div wicket:id="dynamicQueries" class="hidden-phone"></div>
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketsPage.java b/src/main/java/com/gitblit/wicket/pages/TicketsPage.java
index 658cdde..ecfed25 100644
--- a/src/main/java/com/gitblit/wicket/pages/TicketsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TicketsPage.java
@@ -272,6 +272,16 @@
sortBy,
desc,
1)));
+
+ add(new BookmarkablePageLink<Void>("maintenanceQuery", TicketsPage.class,
+ queryParameters(
+ Lucene.type.matches(TicketModel.Type.Maintenance.name()),
+ milestoneParam,
+ statiiParam,
+ assignedToParam,
+ sortBy,
+ desc,
+ 1)));
add(new BookmarkablePageLink<Void>("resetQuery", TicketsPage.class,
queryParameters(
@@ -454,7 +464,11 @@
sortChoices.add(new TicketSort(getString("gb.sortLeastPatchsetRevisions"), Lucene.patchsets.name(), false));
sortChoices.add(new TicketSort(getString("gb.sortMostVotes"), Lucene.votes.name(), true));
sortChoices.add(new TicketSort(getString("gb.sortLeastVotes"), Lucene.votes.name(), false));
-
+ sortChoices.add(new TicketSort(getString("gb.sortHighestPriority"), Lucene.priority.name(), true));
+ sortChoices.add(new TicketSort(getString("gb.sortLowestPriority"), Lucene.priority.name(), false));
+ sortChoices.add(new TicketSort(getString("gb.sortHighestSeverity"), Lucene.severity.name(), true));
+ sortChoices.add(new TicketSort(getString("gb.sortLowestSeverity"), Lucene.severity.name(), false));
+
TicketSort currentSort = sortChoices.get(0);
for (TicketSort ts : sortChoices) {
if (ts.sortBy.equals(sortBy) && desc == ts.desc) {
diff --git a/src/main/java/com/gitblit/wicket/pages/TreePage.html b/src/main/java/com/gitblit/wicket/pages/TreePage.html
index 51a996f..322c901 100644
--- a/src/main/java/com/gitblit/wicket/pages/TreePage.html
+++ b/src/main/java/com/gitblit/wicket/pages/TreePage.html
@@ -22,7 +22,8 @@
<table style="width:100%" class="pretty">
<tr wicket:id="changedPath">
<td class="hidden-phone icon"><img wicket:id="pathIcon" /></td>
- <td><span wicket:id="pathName"></span></td>
+ <td><span wicket:id="pathName"></span></td>
+ <td class="hidden-phone filestore"><span wicket:id="filestore" class="fa fa-fw fa-external-link-square filestore-item"></span></td>
<td class="hidden-phone size"><span wicket:id="pathSize">[path size]</span></td>
<td class="hidden-phone mode"><span wicket:id="pathPermissions">[path permissions]</span></td>
<td class="treeLinks"><span wicket:id="pathLinks">[path links]</span></td>
diff --git a/src/main/java/com/gitblit/wicket/pages/TreePage.java b/src/main/java/com/gitblit/wicket/pages/TreePage.java
index 9ddbecf..eecad26 100644
--- a/src/main/java/com/gitblit/wicket/pages/TreePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TreePage.java
@@ -15,26 +15,33 @@
*/
package com.gitblit.wicket.pages;
+import java.io.OutputStream;
import java.util.List;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.apache.wicket.request.target.resource.ResourceStreamRequestTarget;
+import org.apache.wicket.util.resource.AbstractResourceStreamWriter;
+import org.apache.wicket.util.resource.IResourceStream;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import com.gitblit.models.PathModel;
import com.gitblit.models.SubmoduleModel;
+import com.gitblit.models.UserModel;
import com.gitblit.servlet.RawServlet;
import com.gitblit.utils.ByteFormat;
import com.gitblit.utils.JGitUtils;
import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.CommitHeaderPanel;
@@ -52,7 +59,7 @@
Repository r = getRepository();
RevCommit commit = getCommit();
- List<PathModel> paths = JGitUtils.getFilesInPath(r, path, commit);
+ List<PathModel> paths = JGitUtils.getFilesInPath2(r, path, commit);
// tree page links
add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
@@ -70,12 +77,13 @@
if (path.lastIndexOf('/') > -1) {
parentPath = path.substring(0, path.lastIndexOf('/'));
}
- PathModel model = new PathModel("..", parentPath, 0, FileMode.TREE.getBits(), null, objectId);
+ PathModel model = new PathModel("..", parentPath, null, 0, FileMode.TREE.getBits(), null, objectId);
model.isParentPath = true;
paths.add(0, model);
}
final String id = getBestCommitId(commit);
+
final ByteFormat byteFormat = new ByteFormat();
final String baseUrl = WicketUtils.getGitblitURL(getRequest());
@@ -87,15 +95,18 @@
@Override
public void populateItem(final Item<PathModel> item) {
- PathModel entry = item.getModelObject();
+ final PathModel entry = item.getModelObject();
+
item.add(new Label("pathPermissions", JGitUtils.getPermissionsFromMode(entry.mode)));
+ item.add(WicketUtils.setHtmlTooltip(new Label("filestore", ""), getString("gb.filestore"))
+ .setVisible(entry.isFilestoreItem()));
+
if (entry.isParentPath) {
// parent .. path
item.add(WicketUtils.newBlankImage("pathIcon"));
item.add(new Label("pathSize", ""));
item.add(new LinkPanel("pathName", null, entry.name, TreePage.class,
- WicketUtils
- .newPathParameter(repositoryName, id, entry.path)));
+ WicketUtils.newPathParameter(repositoryName, id, entry.path)));
item.add(new Label("pathLinks", ""));
} else {
if (entry.isTree()) {
@@ -155,17 +166,95 @@
}
item.add(WicketUtils.getFileImage("pathIcon", entry.name));
item.add(new Label("pathSize", byteFormat.format(entry.size)));
- item.add(new LinkPanel("pathName", "list", displayPath, BlobPage.class,
- WicketUtils.newPathParameter(repositoryName, id,
- path)));
-
+
// links
Fragment links = new Fragment("pathLinks", "blobLinks", this);
- links.add(new BookmarkablePageLink<Void>("view", BlobPage.class,
- WicketUtils.newPathParameter(repositoryName, id,
- path)));
- String rawUrl = RawServlet.asLink(getContextUrl(), repositoryName, id, path);
- links.add(new ExternalLink("raw", rawUrl));
+
+ if (entry.isFilestoreItem()) {
+ item.add(new LinkPanel("pathName", "list", displayPath, new Link<Object>("link", null) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick() {
+
+ IResourceStream resourceStream = new AbstractResourceStreamWriter() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void write(OutputStream output) {
+ UserModel user = GitBlitWebSession.get().getUser();
+ user = user == null ? UserModel.ANONYMOUS : user;
+
+ app().filestore().downloadBlob(entry.getFilestoreOid(), user, getRepositoryModel(), output);
+ }
+ };
+
+
+ getRequestCycle().setRequestTarget(new ResourceStreamRequestTarget(resourceStream, entry.path));
+ }}));
+
+ links.add(new Link<Object>("view", null) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick() {
+
+ IResourceStream resourceStream = new AbstractResourceStreamWriter() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void write(OutputStream output) {
+ UserModel user = GitBlitWebSession.get().getUser();
+ user = user == null ? UserModel.ANONYMOUS : user;
+
+ app().filestore().downloadBlob(entry.getFilestoreOid(), user, getRepositoryModel(), output);
+ }
+ };
+
+
+ getRequestCycle().setRequestTarget(new ResourceStreamRequestTarget(resourceStream, entry.path));
+ }});
+
+ links.add(new Link<Object>("raw", null) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick() {
+
+ IResourceStream resourceStream = new AbstractResourceStreamWriter() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void write(OutputStream output) {
+ UserModel user = GitBlitWebSession.get().getUser();
+ user = user == null ? UserModel.ANONYMOUS : user;
+
+ app().filestore().downloadBlob(entry.getFilestoreOid(), user, getRepositoryModel(), output);
+ }
+ };
+
+
+ getRequestCycle().setRequestTarget(new ResourceStreamRequestTarget(resourceStream, entry.path));
+ }});
+
+ } else {
+ item.add(new LinkPanel("pathName", "list", displayPath, BlobPage.class,
+ WicketUtils.newPathParameter(repositoryName, id,
+ path)));
+
+ links.add(new BookmarkablePageLink<Void>("view", BlobPage.class,
+ WicketUtils.newPathParameter(repositoryName, id,
+ path)));
+ String rawUrl = RawServlet.asLink(getContextUrl(), repositoryName, id, path);
+ links.add(new ExternalLink("raw", rawUrl));
+ }
+
links.add(new BookmarkablePageLink<Void>("blame", BlamePage.class,
WicketUtils.newPathParameter(repositoryName, id,
path)));
diff --git a/src/main/java/com/gitblit/wicket/pages/UserPage.java b/src/main/java/com/gitblit/wicket/pages/UserPage.java
index 8931d5e..ea68f25 100644
--- a/src/main/java/com/gitblit/wicket/pages/UserPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/UserPage.java
@@ -104,7 +104,7 @@
if (isMyProfile) {
addPreferences(user);
- if (app().gitblit().isServingSSH()) {
+ if (app().services().isServingSSH()) {
// show the SSH key management tab
addSshKeys(user);
} else {
@@ -183,7 +183,8 @@
new Language("Norsk", "no"),
new Language("Język Polski", "pl"),
new Language("Português", "pt_BR"),
- new Language("中文", "zh_CN"));
+ new Language("簡體中文", "zh_CN"),
+ new Language("正體中文", "zh_TW"));
Locale locale = user.getPreferences().getLocale();
if (locale == null) {
@@ -248,14 +249,16 @@
emailMeOnMyTicketChanges).setVisible(app().notifier().isSendingMail()));
List<Transport> availableTransports = new ArrayList<>();
- if (app().gitblit().isServingSSH()) {
+ if (app().services().isServingSSH()) {
availableTransports.add(Transport.SSH);
}
- if (app().gitblit().isServingHTTP()) {
- availableTransports.add(Transport.HTTPS);
+ if (app().services().isServingHTTP()) {
availableTransports.add(Transport.HTTP);
}
- if (app().gitblit().isServingGIT()) {
+ if (app().services().isServingHTTPS()) {
+ availableTransports.add(Transport.HTTPS);
+ }
+ if (app().services().isServingGIT()) {
availableTransports.add(Transport.GIT);
}
diff --git a/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js b/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js
new file mode 100644
index 0000000..6157513
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2014 Tom <tw201207@gmail.com>
+ *
+ * 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($) {
+
+/**
+ * Sets up elem as a slider; returns an access object. Elem must be positioned!
+ * Note that the element may contain other elements; this is used for instance
+ * for the image diff overlay slider.
+ *
+ * The styling of the slider is to be done in CSS. Currently recognized options:
+ * - initial: <float> clipped to [0..1], default 0
+ * - handleClass: <string> to assign to the handle span element created.
+ * If no handleClass is specified, a very plain default style is assigned.
+ */
+function rangeSlider(elem, options) {
+ options = $.extend({ initial : 0 }, options || {});
+ options.initial = Math.min(1.0, Math.max(0, options.initial));
+
+ var $elem = $(elem);
+ var $handle = $('<span></span>').css({ position: 'absolute', left: 0, cursor: 'ew-resize' });
+ var $root = $(document.documentElement);
+ var $doc = $(document);
+ var lastRatio = options.initial;
+
+ /** Mousemove event handler to track the mouse and move the slider. Generates slider:pos events. */
+ function track(e) {
+ var pos = $elem.offset().left;
+ var width = $elem.innerWidth();
+ var handleWidth = $handle.outerWidth(false);
+ var range = width - handleWidth;
+ if (range <= 0) return;
+ var delta = Math.min(range, Math.max (0, e.pageX - pos - handleWidth / 2));
+ lastRatio = delta / range;
+ $handle.css('left', "" + (delta * 100 / width) + '%');
+ $elem.trigger('slider:pos', { ratio: lastRatio, handle: $handle[0] });
+ }
+
+ /** Mouseup event handler to stop mouse tracking. */
+ function end(e) {
+ $doc.off('mousemove', track);
+ $doc.off('mouseup', end);
+ $root.removeClass('no-select');
+ }
+
+ /** Snaps the slider to the given ratio and generates a slider:pos event with the new ratio. */
+ function setTo(ratio) {
+ var w = $elem.innerWidth();
+ if (w <= 0 || $elem.is(':hidden')) return;
+ lastRatio = Math.min( 1.0, Math.max(0, ratio));
+ $handle.css('left', "" + Math.max(0, 100 * (lastRatio * (w - $handle.outerWidth(false))) / w) + '%');
+ $elem.trigger('slider:pos', { ratio: lastRatio, handle: $handle[0] });
+ }
+
+ /**
+ * Moves the slider to the given ratio, clipped to [0..1], in duration milliseconds.
+ * Generates slider:pos events during the animation. If duration <= 30, same as setTo.
+ * Default duration is 500ms. If a callback is given, it's called once the animation
+ * has completed.
+ */
+ function moveTo(ratio, duration, callback) {
+ ratio = Math.min(1.0, Math.max(0, ratio));
+ if (ratio === lastRatio) {
+ if (typeof callback == 'function') callback();
+ return;
+ }
+ if (typeof duration == 'undefined') duration = 500;
+ if (duration <= 30) {
+ // Cinema is 24 or 48 frames/sec, so 20-40ms per frame. Makes no sense to animate for such a short duration.
+ setTo(ratio);
+ if (typeof callback == 'function') callback();
+ } else {
+ var target = ratio * ($elem.innerWidth() - $handle.outerWidth(false));
+ if (ratio > lastRatio) target--; else target++;
+ $handle.stop().animate({left: target},
+ { 'duration' : duration,
+ 'step' : function() {
+ lastRatio = Math.min(1.0, Math.max(0, $handle.position().left / ($elem.innerWidth() - $handle.outerWidth(false))));
+ $elem.trigger('slider:pos', { ratio : lastRatio, handle : $handle[0] });
+ },
+ 'complete' : function() { setTo(ratio); if (typeof callback == 'function') callback(); } // Ensure we have again a % value
+ }
+ );
+ }
+ }
+
+ /**
+ * As moveTo, but determines an appropriate duration in the range [0..maxDuration] on its own,
+ * depending on the distance the handle would move. If no maxDuration is given it defaults
+ * to 1500ms.
+ */
+ function moveAuto(ratio, maxDuration, callback) {
+ if (typeof maxDuration == 'undefined') maxDuration = 1500;
+ var delta = ratio - lastRatio;
+ if (delta < 0) delta = -delta;
+ var speed = $elem.innerWidth() * delta * 2;
+ if (speed > maxDuration) speed = maxDuration;
+ moveTo(ratio, speed, callback);
+ }
+
+ /** Returns the current ratio. */
+ function getValue() {
+ return lastRatio;
+ }
+
+ $elem.append($handle);
+ if (options.handleClass) {
+ $handle.addClass(options.handleClass);
+ } else { // Provide a default style so that it is at least visible
+ $handle.css({ width: '10px', height: '10px', background: 'white', border: '1px solid black' });
+ }
+ if (options.initial) setTo(options.initial);
+
+ /** Install mousedown handler to start mouse tracking. */
+ $handle.on('mousedown', function(e) {
+ $root.addClass('no-select');
+ $doc.on('mousemove', track);
+ $doc.on('mouseup', end);
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ return { setRatio: setTo, moveRatio: moveTo, 'moveAuto': moveAuto, getRatio: getValue, handle: $handle[0] };
+}
+
+function setup() {
+ $('.imgdiff-container').each(function() {
+ var $this = $(this);
+ var $overlaySlider = $this.find('.imgdiff-ovr-slider').first();
+ var $opacitySlider = $this.find('.imgdiff-opa-slider').first();
+ var overlayAccess = rangeSlider($overlaySlider, {handleClass: 'imgdiff-ovr-handle'});
+ var opacityAccess = rangeSlider($opacitySlider, {handleClass: 'imgdiff-opa-handle'});
+ var $img = $('#' + this.id.substr(this.id.indexOf('-')+1)); // Here we change opacity
+ var $div = $img.parent(); // This controls visibility: here we change width.
+ var blinking = false;
+
+ $overlaySlider.on('slider:pos', function(e, data) {
+ var pos = $(data.handle).offset().left;
+ var imgLeft = $img.offset().left; // Global
+ var imgW = $img.outerWidth(true);
+ var imgOff = $img.position().left; // From left edge of $div
+ if (pos <= imgLeft) {
+ $div.width(0);
+ } else if (pos <= imgLeft + imgW) {
+ $div.width(pos - imgLeft + imgOff);
+ } else if ($div.width() < imgW + imgOff) {
+ $div.width(imgW + imgOff);
+ }
+ });
+ $overlaySlider.css('cursor', 'pointer');
+ $overlaySlider.on('mousedown', function(e) {
+ var newRatio = (e.pageX - $overlaySlider.offset().left) / $overlaySlider.innerWidth();
+ var oldRatio = overlayAccess.getRatio();
+ if (newRatio !== oldRatio) {
+ overlayAccess.moveAuto(newRatio);
+ }
+ });
+
+ var autoShowing = false;
+ $opacitySlider.on('slider:pos', function(e, data) {
+ if ($div.width() <= 0 && !blinking) {
+ // Make old image visible in a nice way, *then* adjust opacity
+ autoShowing = true;
+ overlayAccess.moveAuto(1.0, 500, function() {
+ $img.stop().animate(
+ {opacity: 1.0 - opacityAccess.getRatio()},
+ {duration: 400,
+ complete: function () {
+ // In case the opacity handle was moved while we were trying to catch up
+ $img.css('opacity', 1.0 - opacityAccess.getRatio());
+ autoShowing = false;
+ }
+ }
+ );
+ });
+ } else if (!autoShowing) {
+ $img.css('opacity', 1.0 - data.ratio);
+ }
+ });
+ $opacitySlider.on('click', function(e) {
+ var newRatio = (e.pageX - $opacitySlider.offset().left) / $opacitySlider.innerWidth();
+ var oldRatio = opacityAccess.getRatio();
+ if (newRatio !== oldRatio) {
+ if ($div.width() <= 0) {
+ overlayAccess.moveRatio(1.0, 500, function() {opacityAccess.moveAuto(newRatio);}); // Make old image visible in a nice way
+ } else {
+ opacityAccess.moveAuto(newRatio)
+ }
+ }
+ e.preventDefault();
+ });
+
+ // Blinking before and after images is a good way for the human eye to catch differences.
+ var $blinker = $this.find('.imgdiff-blink');
+ var initialOpacity = null;
+ $blinker.on('click', function(e) {
+ if (blinking) {
+ window.clearTimeout(blinking);
+ $blinker.children('img').first().css('border', '1px solid transparent');
+ opacityAccess.setRatio(initialOpacity);
+ blinking = null;
+ } else {
+ $blinker.children('img').first().css('border', '1px solid #AAA');
+ initialOpacity = opacityAccess.getRatio();
+ var currentOpacity = 1.0;
+ function blink() {
+ opacityAccess.setRatio(currentOpacity);
+ currentOpacity = 1.0 - currentOpacity;
+ // Keep frequeny below 2Hz (i.e., delay above 500ms)
+ blinking = window.setTimeout(blink, 600);
+ }
+ if ($div.width() <= 0) {
+ overlayAccess.moveRatio(1.0, 500, blink);
+ } else {
+ blink();
+ }
+ }
+ e.preventDefault();
+ });
+
+ // Subtracting before and after images is another good way to detect differences. Result will be
+ // black where identical.
+ if (typeof $img[0].style.mixBlendMode != 'undefined') {
+ // Feature test: does the browser support the mix-blend-mode CSS property from the Compositing
+ // and Blending Level 1 spec (http://dev.w3.org/fxtf/compositing-1/#mix-blend-mode )?
+ // As of 2014-11, only Firefox >= 32 and Safari >= 7.1 support this. Other browsers will have to
+ // make do with the blink comparator only.
+ var $sub = $this.find('.imgdiff-subtract');
+ $sub.css('display', 'inline-block');
+ $sub.on('click', function (e) {
+ var curr = $img.css('mix-blend-mode');
+ if (curr != 'difference') {
+ curr = 'difference';
+ $sub.children('img').first().css('border', '1px solid #AAA');
+ if ($div.width() <= 0) overlayAccess.moveRatio(1.0, 500);
+ opacityAccess.setRatio(0);
+ } else {
+ curr = 'normal';
+ $sub.children('img').first().css('border', '1px solid transparent');
+
+ }
+ $img.css('mix-blend-mode', curr);
+ e.preventDefault();
+ });
+ }
+ });
+}
+
+$(setup); // Run on jQuery's dom-ready
+
+})(jQuery);
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/scripts/wicketHtml5Patch.js b/src/main/java/com/gitblit/wicket/pages/scripts/wicketHtml5Patch.js
new file mode 100644
index 0000000..49088e1
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/scripts/wicketHtml5Patch.js
@@ -0,0 +1,13 @@
+//This provides a basic patch/hack to allow Wicket 1.4 to support HTML5 input types
+
+Wicket.Form.serializeInput_original = Wicket.Form.serializeInput;
+
+Wicket.Form.serializeInput = function(input)
+{
+ if (input.type.toLowerCase() == "date")
+ {
+ return Wicket.Form.encode(input.name) + "=" + Wicket.Form.encode(input.value) + "&";
+ }
+
+ return Wicket.Form.serializeInput_original(input);
+}
diff --git a/src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java b/src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java
index d8dcdce..2c88024 100644
--- a/src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java
@@ -26,8 +26,8 @@
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Fragment;
-import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
@@ -51,8 +51,6 @@
private RadioGroup<AccessPolicy> policiesGroup;
- private IModel<Boolean> allowForks;
-
public AccessPolicyPanel(String wicketId, RepositoryModel repository) {
this(wicketId, repository, null);
}
@@ -146,13 +144,12 @@
}
add(policiesGroup);
- allowForks = Model.of(app().settings().getBoolean(Keys.web.allowForking, true));
- if (allowForks.getObject()) {
+ if (app().settings().getBoolean(Keys.web.allowForking, true)) {
Fragment fragment = new Fragment("allowForks", "allowForksFragment", this);
fragment.add(new BooleanOption("allowForks",
getString("gb.allowForks"),
getString("gb.allowForksDescription"),
- allowForks));
+ new PropertyModel<Boolean>(repository, "allowForks")));
add(fragment);
} else {
add(new Label("allowForks").setVisible(false));
@@ -165,7 +162,6 @@
AccessPolicy policy = policiesGroup.getModelObject();
repository.authorizationControl = policy.control;
repository.accessRestriction = policy.type;
- repository.allowForks = allowForks.getObject();
}
@Override
diff --git a/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java b/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java
index 35513bb..062df84 100644
--- a/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java
@@ -76,7 +76,7 @@
.getWhen(), getTimeZone(), getTimeUtils()));
// avatar
- commitItem.add(new GravatarImage("avatar", commit.getAuthorIdent(), 40));
+ commitItem.add(new AvatarImage("avatar", commit.getAuthorIdent(), 40));
// merge icon
if (commit.getParentCount() > 1) {
diff --git a/src/main/java/com/gitblit/wicket/panels/GravatarImage.html b/src/main/java/com/gitblit/wicket/panels/AvatarImage.html
similarity index 100%
rename from src/main/java/com/gitblit/wicket/panels/GravatarImage.html
rename to src/main/java/com/gitblit/wicket/panels/AvatarImage.html
diff --git a/src/main/java/com/gitblit/wicket/panels/GravatarImage.java b/src/main/java/com/gitblit/wicket/panels/AvatarImage.java
similarity index 67%
rename from src/main/java/com/gitblit/wicket/panels/GravatarImage.java
rename to src/main/java/com/gitblit/wicket/panels/AvatarImage.java
index e415757..a562042 100644
--- a/src/main/java/com/gitblit/wicket/panels/GravatarImage.java
+++ b/src/main/java/com/gitblit/wicket/panels/AvatarImage.java
@@ -17,9 +17,9 @@
import org.eclipse.jgit.lib.PersonIdent;
+import com.gitblit.AvatarGenerator;
import com.gitblit.Keys;
import com.gitblit.models.UserModel;
-import com.gitblit.utils.ActivityUtils;
import com.gitblit.wicket.ExternalImage;
import com.gitblit.wicket.WicketUtils;
@@ -29,36 +29,31 @@
* @author James Moger
*
*/
-public class GravatarImage extends BasePanel {
+public class AvatarImage extends BasePanel {
private static final long serialVersionUID = 1L;
- public GravatarImage(String id, PersonIdent person) {
+ public AvatarImage(String id, PersonIdent person) {
this(id, person, 0);
}
- public GravatarImage(String id, PersonIdent person, int width) {
+ public AvatarImage(String id, PersonIdent person, int width) {
this(id, person.getName(), person.getEmailAddress(), "gravatar", width, true);
}
- public GravatarImage(String id, PersonIdent person, String cssClass, int width, boolean identicon) {
+ public AvatarImage(String id, PersonIdent person, String cssClass, int width, boolean identicon) {
this(id, person.getName(), person.getEmailAddress(), cssClass, width, identicon);
}
- public GravatarImage(String id, UserModel user, String cssClass, int width, boolean identicon) {
+ public AvatarImage(String id, UserModel user, String cssClass, int width, boolean identicon) {
this(id, user.getDisplayName(), user.emailAddress, cssClass, width, identicon);
}
- public GravatarImage(String id, String username, String emailaddress, String cssClass, int width, boolean identicon) {
+ public AvatarImage(String id, String username, String emailaddress, String cssClass, int width, boolean identicon) {
super(id);
- String email = emailaddress == null ? username.toLowerCase() : emailaddress.toLowerCase();
- String url;
- if (identicon) {
- url = ActivityUtils.getGravatarIdenticonUrl(email, width);
- } else {
- url = ActivityUtils.getGravatarThumbnailUrl(email, width);
- }
+ AvatarGenerator avatarGenerator = app().runtime().getInjector().getInstance(AvatarGenerator.class);
+ String url = avatarGenerator.getURL(username, emailaddress, identicon, width);
ExternalImage image = new ExternalImage("image", url);
if (cssClass != null) {
WicketUtils.setCssClass(image, cssClass);
diff --git a/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java
index 4bf00f8..7a564aa 100644
--- a/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java
@@ -114,11 +114,13 @@
public void populateItem(final Item<RefModel> item) {
final RefModel entry = item.getModelObject();
+ PageParameters shortUniqRef = WicketUtils.newObjectParameter(model.name,
+ Repository.shortenRefName(entry.getName()));
+
item.add(WicketUtils.createDateLabel("branchDate", entry.getDate(), getTimeZone(), getTimeUtils()));
item.add(new LinkPanel("branchName", "list name", StringUtils.trimString(
- entry.displayName, 28), LogPage.class, WicketUtils.newObjectParameter(
- model.name, entry.getName())));
+ entry.displayName, 28), LogPage.class, shortUniqRef));
String author = entry.getAuthorIdent().getName();
LinkPanel authorLink = new LinkPanel("branchAuthor", "list", author,
@@ -131,8 +133,7 @@
String shortMessage = entry.getShortMessage();
String trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG);
LinkPanel shortlog = new LinkPanel("branchLog", "list subject", trimmedMessage,
- CommitPage.class, WicketUtils.newObjectParameter(model.name,
- entry.getName()));
+ CommitPage.class, shortUniqRef);
if (!shortMessage.equals(trimmedMessage)) {
shortlog.setTooltip(shortMessage);
}
@@ -140,27 +141,22 @@
if (maxCount <= 0) {
Fragment fragment = new Fragment("branchLinks", showDelete? "branchPageAdminLinks" : "branchPageLinks", this);
- fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils
- .newObjectParameter(model.name, entry.getName())));
- fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils
- .newObjectParameter(model.name, entry.getName())));
+ fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, shortUniqRef));
+ fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class, shortUniqRef));
String rawUrl = RawServlet.asLink(getContextUrl(), model.name, Repository.shortenRefName(entry.getName()), null);
- fragment.add(new ExternalLink("raw", rawUrl));
- fragment.add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class,
- WicketUtils.newObjectParameter(model.name, entry.getName())));
+ fragment.add(new ExternalLink("raw", rawUrl));
+ fragment.add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class, shortUniqRef));
fragment.add(new ExternalLink("syndication", SyndicationServlet.asLink(
getRequest().getRelativePathPrefixToContextRoot(), model.name,
- entry.getName(), 0)));
+ Repository.shortenRefName(entry.getName()), 0)));
if (showDelete) {
fragment.add(createDeleteBranchLink(model, entry));
}
item.add(fragment);
} else {
Fragment fragment = new Fragment("branchLinks", "branchPanelLinks", this);
- fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils
- .newObjectParameter(model.name, entry.getName())));
- fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils
- .newObjectParameter(model.name, entry.getName())));
+ fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, shortUniqRef));
+ fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class, shortUniqRef));
String rawUrl = RawServlet.asLink(getContextUrl(), model.name, Repository.shortenRefName(entry.getName()), null);
fragment.add(new ExternalLink("raw", rawUrl));
item.add(fragment);
diff --git a/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java b/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java
index eb75750..249cd4a 100644
--- a/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java
@@ -44,6 +44,6 @@
add(new Label("commitid", c.getName()));
add(new Label("author", c.getAuthorIdent().getName()));
add(WicketUtils.createDateLabel("date", c.getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils()));
- add(new GravatarImage("authorAvatar", c.getAuthorIdent()));
+ add(new AvatarImage("authorAvatar", c.getAuthorIdent()));
}
}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java b/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java
index decfda5..0c80f99 100644
--- a/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java
@@ -166,7 +166,7 @@
} else if (isTag) {
// link to tag
logItem.add(new LinkPanel("refChanged", null, shortRefName,
- TagPage.class, WicketUtils.newObjectParameter(change.repository, fullRefName)));
+ TagPage.class, WicketUtils.newObjectParameter(change.repository, shortRefName)));
} else if (isTicket) {
// link to ticket
logItem.add(new LinkPanel("refChanged", null, shortRefName,
@@ -174,7 +174,7 @@
} else {
// link to tree
logItem.add(new LinkPanel("refChanged", null, shortRefName,
- TreePage.class, WicketUtils.newObjectParameter(change.repository, fullRefName)));
+ TreePage.class, WicketUtils.newObjectParameter(change.repository, shortRefName)));
}
// to/from/etc
@@ -216,7 +216,7 @@
final RepositoryCommit commit = commitItem.getModelObject();
// author gravatar
- commitItem.add(new GravatarImage("commitAuthor", commit.getAuthorIdent(), null, 16, false));
+ commitItem.add(new AvatarImage("commitAuthor", commit.getAuthorIdent(), null, 16, false));
// merge icon
if (commit.getParentCount() > 1) {
diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java
index 4433b04..e4ce5ce 100644
--- a/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java
+++ b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java
@@ -125,7 +125,15 @@
item.t = getTimeUtils().timeAgo(repo.lastChange);
item.d = df.format(repo.lastChange);
item.c = StringUtils.getColor(StringUtils.stripDotGit(repo.name));
- item.wc = repo.isBare ? 0 : 1;
+ if (!repo.isBare) {
+ item.y = 3;
+ } else if (repo.isMirror) {
+ item.y = 2;
+ } else if (repo.isFork()) {
+ item.y = 1;
+ } else {
+ item.y = 0;
+ }
list.add(item);
}
@@ -147,6 +155,6 @@
String i; // information/description
long s; // stars
String c; // html color
- int wc; // working copy: 1 = true, 0 = false
+ int y; // type: 0 = normal, 1 = fork, 2 = mirror, 3 = clone
}
}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java b/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java
index e1706a0..75fd70e 100644
--- a/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java
@@ -109,14 +109,14 @@
tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
while (tw.next()) {
if (tw.getPathString().equals(path)) {
- matchingPath = new PathChangeModel(tw.getPathString(), tw.getPathString(), 0, tw
+ matchingPath = new PathChangeModel(tw.getPathString(), tw.getPathString(), null, 0, tw
.getRawMode(0), tw.getObjectId(0).getName(), commit.getId().getName(),
ChangeType.MODIFY);
}
}
} catch (Exception e) {
} finally {
- tw.release();
+ tw.close();
}
}
}
@@ -160,7 +160,7 @@
@Override
public void populateItem(final Item<RevCommit> item) {
final RevCommit entry = item.getModelObject();
- final Date date = JGitUtils.getCommitDate(entry);
+ final Date date = JGitUtils.getAuthorDate(entry);
item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
diff --git a/src/main/java/com/gitblit/wicket/panels/LinkPanel.java b/src/main/java/com/gitblit/wicket/panels/LinkPanel.java
index 06159ac..aa09be0 100644
--- a/src/main/java/com/gitblit/wicket/panels/LinkPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/LinkPanel.java
@@ -15,6 +15,9 @@
*/
package com.gitblit.wicket.panels;
+import java.io.OutputStream;
+import java.util.concurrent.Callable;
+
import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
import org.apache.wicket.behavior.SimpleAttributeModifier;
@@ -26,8 +29,13 @@
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
+import org.apache.wicket.request.target.resource.ResourceStreamRequestTarget;
+import org.apache.wicket.util.resource.AbstractResourceStreamWriter;
+import org.apache.wicket.util.resource.IResourceStream;
+import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.WicketUtils;
public class LinkPanel extends Panel {
@@ -108,6 +116,20 @@
add(link);
}
+ public LinkPanel(String wicketId, String linkCssClass, String label, Link<?> link) {
+ super(wicketId);
+
+ this.labelModel = new Model<String>(label);
+
+ if (linkCssClass != null) {
+ link.add(new SimpleAttributeModifier("class", linkCssClass));
+ }
+
+ link.add(new Label("icon").setVisible(false));
+ link.add(new Label("label", labelModel));
+ add(link);
+ }
+
public void setNoFollow() {
Component c = get("link");
c.add(new SimpleAttributeModifier("rel", "nofollow"));
diff --git a/src/main/java/com/gitblit/wicket/panels/LogPanel.java b/src/main/java/com/gitblit/wicket/panels/LogPanel.java
index 16fc746..e9d240d 100644
--- a/src/main/java/com/gitblit/wicket/panels/LogPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/LogPanel.java
@@ -109,7 +109,7 @@
@Override
public void populateItem(final Item<RevCommit> item) {
final RevCommit entry = item.getModelObject();
- final Date date = JGitUtils.getCommitDate(entry);
+ final Date date = JGitUtils.getAuthorDate(entry);
final boolean isMerge = entry.getParentCount() > 1;
item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
diff --git a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
index 33345a0..88de3b4 100644
--- a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
+++ b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
@@ -24,13 +24,28 @@
<p class="originRepository" style="margin-left:20px;" ><wicket:message key="gb.forkedFrom">[forked from]</wicket:message> <span wicket:id="originRepository">[origin repository]</span></p>
</wicket:fragment>
+ <wicket:fragment wicket:id="repoIconFragment">
+ <span class="mega-octicon octicon-repo"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="mirrorIconFragment">
+ <span class="mega-octicon octicon-mirror"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="forkIconFragment">
+ <span class="mega-octicon octicon-repo-forked"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="cloneIconFragment">
+ <span class="mega-octicon octicon-repo-push" wicket:message="title:gb.workingCopyWarning"></span>
+ </wicket:fragment>
+
<div>
<div style="padding-top:15px;padding-bottom:15px;margin-right:15px;">
<div class="pull-right" style="text-align:right;padding-right:15px;">
<span wicket:id="repositoryLinks"></span>
<div>
<img class="inlineIcon" wicket:id="sparkleshareIcon" />
- <img class="inlineIcon" wicket:id="mirrorIcon" />
<img class="inlineIcon" wicket:id="frozenIcon" />
<img class="inlineIcon" wicket:id="federatedIcon" />
@@ -42,11 +57,15 @@
</div>
<div class="pageTitle" style="border:0px;">
- <div>
- <span class="repositorySwatch" wicket:id="repositorySwatch"></span>
- <span class="repository" style="padding-left:3px;color:black;" wicket:id="repositoryName">[repository name]</span>
- </div>
- <span wicket:id="originRepository">[origin repository]</span>
+ <div style="display:inline-block;vertical-align:top;padding-right:5px;">
+ <span wicket:id="repoIcon"></span>
+ </div>
+ <div style="display:inline-block;">
+ <div>
+ <span class="repository" style="padding-left:3px;color:black;" wicket:id="repositoryName">[repository name]</span>
+ </div>
+ <span wicket:id="originRepository">[origin repository]</span>
+ </div>
</div>
<div style="padding-left:20px;">
diff --git a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
index 8630d20..efcb1cb 100644
--- a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
@@ -51,24 +51,27 @@
final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true);
final boolean showSize = app().settings().getBoolean(Keys.web.showRepositorySizes, true);
- // repository swatch
- Component swatch;
- if (entry.isBare) {
- swatch = new Label("repositorySwatch", " ").setEscapeModelStrings(false);
- } else {
- swatch = new Label("repositorySwatch", "!");
- WicketUtils.setHtmlTooltip(swatch, localizer.getString("gb.workingCopyWarning", parent));
- }
- WicketUtils.setCssBackground(swatch, entry.toString());
- add(swatch);
- swatch.setVisible(showSwatch);
-
PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
add(new LinkPanel("repositoryName", "list", StringUtils.getRelativePath(entry.projectPath,
StringUtils.stripDotGit(entry.name)), SummaryPage.class, pp));
add(new Label("repositoryDescription", entry.description).setVisible(!StringUtils
.isEmpty(entry.description)));
+ Fragment iconFragment;
+ if (entry.isMirror) {
+ iconFragment = new Fragment("repoIcon", "mirrorIconFragment", this);
+ } else if (entry.isFork()) {
+ iconFragment = new Fragment("repoIcon", "forkIconFragment", this);
+ } else if (entry.isBare) {
+ iconFragment = new Fragment("repoIcon", "repoIconFragment", this);
+ } else {
+ iconFragment = new Fragment("repoIcon", "cloneIconFragment", this);
+ }
+ if (showSwatch) {
+ WicketUtils.setCssStyle(iconFragment, "color:" + StringUtils.getColor(entry.toString()));
+ }
+ add(iconFragment);
+
if (StringUtils.isEmpty(entry.originRepository)) {
add(new Label("originRepository").setVisible(false));
} else {
@@ -84,13 +87,7 @@
add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
}
- if (entry.isMirror) {
- add(WicketUtils.newImage("mirrorIcon", "mirror_16x16.png", localizer.getString("gb.isMirror", parent)));
- } else {
- add(WicketUtils.newClearPixel("mirrorIcon").setVisible(false));
- }
-
- if (entry.isFrozen) {
+ if (!entry.isMirror && entry.isFrozen) {
add(WicketUtils.newImage("frozenIcon", "cold_16x16.png", localizer.getString("gb.isFrozen", parent)));
} else {
add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
@@ -161,6 +158,7 @@
add(new Label("repositorySize", localizer.getString("gb.empty", parent)).setEscapeModelStrings(false));
}
- add(new ExternalLink("syndication", SyndicationServlet.asLink("", entry.name, null, 0)));
+ add(new ExternalLink("syndication", SyndicationServlet.asLink(getRequest()
+ .getRelativePathPrefixToContextRoot(), entry.name, null, 0)));
}
}
diff --git a/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java b/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java
index baefc6b..2235fd3 100644
--- a/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java
@@ -271,7 +271,7 @@
final RepositoryCommit commit = commitItem.getModelObject();
// author gravatar
- commitItem.add(new GravatarImage("commitAuthor", commit.getAuthorIdent(), null, 16, false));
+ commitItem.add(new AvatarImage("commitAuthor", commit.getAuthorIdent(), null, 16, false));
// merge icon
if (commit.getParentCount() > 1) {
diff --git a/src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java b/src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
index f37cc2a..447e178 100644
--- a/src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
@@ -145,7 +145,7 @@
}
Fragment userFragment = new Fragment("registrant", "userRegistrant", RegistrantPermissionsPanel.this);
- userFragment.add(new GravatarImage("userAvatar", ident, 20));
+ userFragment.add(new AvatarImage("userAvatar", ident, 20));
userFragment.add(new Label("userName", entry.registrant));
item.add(userFragment);
} else {
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html
index e2e7b72..2de52b0 100644
--- a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html
+++ b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html
@@ -17,6 +17,22 @@
</tr>
</tbody>
</table>
+
+ <wicket:fragment wicket:id="repoIconFragment">
+ <span class="octicon octicon-centered octicon-repo"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="mirrorIconFragment">
+ <span class="octicon octicon-centered octicon-mirror"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="forkIconFragment">
+ <span class="octicon octicon-centered octicon-repo-forked"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="cloneIconFragment">
+ <span class="octicon octicon-centered octicon-repo-push" wicket:message="title:gb.workingCopyWarning"></span>
+ </wicket:fragment>
<wicket:fragment wicket:id="adminLinks">
<!-- page nav links -->
@@ -76,10 +92,10 @@
</wicket:fragment>
<wicket:fragment wicket:id="repositoryRow">
- <td class="left" style="padding-left:3px;" ><b><span class="repositorySwatch" wicket:id="repositorySwatch"></span></b> <span style="padding-left:3px;" wicket:id="repositoryName">[repository name]</span></td>
+ <td class="left" style="padding-left:3px;" ><span wicket:id="repoIcon"></span><span style="padding-left:3px;" wicket:id="repositoryName">[repository name]</span></td>
<td class="hidden-phone"><span class="list" wicket:id="repositoryDescription">[repository description]</span></td>
<td class="hidden-tablet hidden-phone author"><span wicket:id="repositoryOwner">[repository owner]</span></td>
- <td class="hidden-phone" style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="sparkleshareIcon" /><img class="inlineIcon" wicket:id="mirrorIcon" /><img class="inlineIcon" wicket:id="forkIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="federatedIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
+ <td class="hidden-phone" style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="sparkleshareIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="federatedIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
<td><span wicket:id="repositoryLastChange">[last change]</span></td>
<td class="rightAlign hidden-phone" style="text-align: right;padding-right:15px;"><span style="font-size:0.8em;" wicket:id="repositorySize">[repository size]</span></td>
</wicket:fragment>
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java
index 8573e1a..c3f0709 100644
--- a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java
@@ -24,7 +24,6 @@
import java.util.List;
import java.util.Map;
-import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
import org.apache.wicket.extensions.markup.html.repeater.data.sort.OrderByBorder;
import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
@@ -183,24 +182,28 @@
Fragment row = new Fragment("rowContent", "repositoryRow", this);
item.add(row);
+ // show colored repository type icon
+ Fragment iconFragment;
+ if (entry.isMirror) {
+ iconFragment = new Fragment("repoIcon", "mirrorIconFragment", this);
+ } else if (entry.isFork()) {
+ iconFragment = new Fragment("repoIcon", "forkIconFragment", this);
+ } else if (entry.isBare) {
+ iconFragment = new Fragment("repoIcon", "repoIconFragment", this);
+ } else {
+ iconFragment = new Fragment("repoIcon", "cloneIconFragment", this);
+ }
+ if (showSwatch) {
+ WicketUtils.setCssStyle(iconFragment, "color:" + StringUtils.getColor(entry.toString()));
+ }
+ row.add(iconFragment);
+
// try to strip group name for less cluttered list
String repoName = entry.toString();
if (!StringUtils.isEmpty(currGroupName) && (repoName.indexOf('/') > -1)) {
repoName = repoName.substring(currGroupName.length() + 1);
}
- // repository swatch
- Component swatch;
- if (entry.isBare){
- swatch = new Label("repositorySwatch", " ").setEscapeModelStrings(false);
- } else {
- swatch = new Label("repositorySwatch", "!");
- WicketUtils.setHtmlTooltip(swatch, getString("gb.workingCopyWarning"));
- }
- WicketUtils.setCssBackground(swatch, entry.toString());
- row.add(swatch);
- swatch.setVisible(showSwatch);
-
if (linksActive) {
Class<? extends BasePage> linkPage = SummaryPage.class;
PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
@@ -228,21 +231,7 @@
row.add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
}
- if (entry.isMirror) {
- row.add(WicketUtils.newImage("mirrorIcon", "mirror_16x16.png",
- getString("gb.isMirror")));
- } else {
- row.add(WicketUtils.newClearPixel("mirrorIcon").setVisible(false));
- }
-
- if (entry.isFork()) {
- row.add(WicketUtils.newImage("forkIcon", "commit_divide_16x16.png",
- getString("gb.isFork")));
- } else {
- row.add(WicketUtils.newClearPixel("forkIcon").setVisible(false));
- }
-
- if (entry.isFrozen) {
+ if (!entry.isMirror && entry.isFrozen) {
row.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png",
getString("gb.isFrozen")));
} else {
@@ -255,24 +244,30 @@
} else {
row.add(WicketUtils.newClearPixel("federatedIcon").setVisible(false));
}
- switch (entry.accessRestriction) {
- case NONE:
- row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
- break;
- case PUSH:
- row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
- accessRestrictionTranslations.get(entry.accessRestriction)));
- break;
- case CLONE:
- row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
- accessRestrictionTranslations.get(entry.accessRestriction)));
- break;
- case VIEW:
- row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
- accessRestrictionTranslations.get(entry.accessRestriction)));
- break;
- default:
- row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+
+ if (entry.isMirror) {
+ row.add(WicketUtils.newImage("accessRestrictionIcon", "mirror_16x16.png",
+ getString("gb.isMirror")));
+ } else {
+ switch (entry.accessRestriction) {
+ case NONE:
+ row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+ break;
+ case PUSH:
+ row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
+ accessRestrictionTranslations.get(entry.accessRestriction)));
+ break;
+ case CLONE:
+ row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
+ accessRestrictionTranslations.get(entry.accessRestriction)));
+ break;
+ case VIEW:
+ row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
+ accessRestrictionTranslations.get(entry.accessRestriction)));
+ break;
+ default:
+ row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+ }
}
String owner = "";
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
index 938226a..207f125 100644
--- a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
@@ -80,11 +80,11 @@
HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
- List<RepositoryUrl> repositoryUrls = app().gitblit().getRepositoryUrls(req, user, repository);
+ List<RepositoryUrl> repositoryUrls = app().services().getRepositoryUrls(req, user, repository);
// grab primary url from the top of the list
primaryUrl = repositoryUrls.size() == 0 ? null : repositoryUrls.get(0);
- boolean canClone = primaryUrl != null && ((primaryUrl.permission == null) || primaryUrl.permission.atLeast(AccessPermission.CLONE));
+ boolean canClone = primaryUrl != null && (!primaryUrl.hasPermission() || primaryUrl.permission.atLeast(AccessPermission.CLONE));
if (repositoryUrls.size() == 0 || !canClone) {
// no urls, nothing to show.
@@ -145,7 +145,7 @@
fragment.add(content);
item.add(fragment);
- Label permissionLabel = new Label("permission", repoUrl.isExternal() ? externalPermission : repoUrl.permission.toString());
+ Label permissionLabel = new Label("permission", repoUrl.hasPermission() ? repoUrl.permission.toString() : externalPermission);
WicketUtils.setPermissionClass(permissionLabel, repoUrl.permission);
String tooltip = getProtocolPermissionDescription(repository, repoUrl);
WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
@@ -165,7 +165,7 @@
if (repository.isMirror) {
urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "mirror_16x16.png",
getString("gb.isMirror")));
- } else if (app().gitblit().isServingRepositories()) {
+ } else if (app().services().isServingRepositories()) {
switch (repository.accessRestriction) {
case NONE:
urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
@@ -201,7 +201,7 @@
urlPanel.add(new Label("primaryUrl", primaryUrl.url).setRenderBodyOnly(true));
- Label permissionLabel = new Label("primaryUrlPermission", primaryUrl.isExternal() ? externalPermission : primaryUrl.permission.toString());
+ Label permissionLabel = new Label("primaryUrlPermission", primaryUrl.hasPermission() ? primaryUrl.permission.toString() : externalPermission);
String tooltip = getProtocolPermissionDescription(repository, primaryUrl);
WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
urlPanel.add(permissionLabel);
@@ -234,8 +234,8 @@
// filter the urls for the client app
List<RepositoryUrl> urls = new ArrayList<RepositoryUrl>();
for (RepositoryUrl repoUrl : repositoryUrls) {
- if (clientApp.minimumPermission == null || repoUrl.permission == null) {
- // no minimum permission or external permissions, assume it is satisfactory
+ if (clientApp.minimumPermission == null || !repoUrl.hasPermission()) {
+ // no minimum permission or untracked permissions, assume it is satisfactory
if (clientApp.supportsTransport(repoUrl.url)) {
urls.add(repoUrl);
}
@@ -339,7 +339,7 @@
}
protected Label createPermissionBadge(String wicketId, RepositoryUrl repoUrl) {
- Label permissionLabel = new Label(wicketId, repoUrl.isExternal() ? externalPermission : repoUrl.permission.toString());
+ Label permissionLabel = new Label(wicketId, repoUrl.hasPermission() ? repoUrl.permission.toString() : externalPermission);
WicketUtils.setPermissionClass(permissionLabel, repoUrl.permission);
String tooltip = getProtocolPermissionDescription(repository, repoUrl);
WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
@@ -369,18 +369,7 @@
RepositoryUrl repoUrl) {
if (!urlPermissionsMap.containsKey(repoUrl.url)) {
String note;
- if (repoUrl.isExternal()) {
- String protocol;
- int protocolIndex = repoUrl.url.indexOf("://");
- if (protocolIndex > -1) {
- // explicit protocol specified
- protocol = repoUrl.url.substring(0, protocolIndex);
- } else {
- // implicit SSH url
- protocol = "ssh";
- }
- note = MessageFormat.format(getString("gb.externalPermissions"), protocol);
- } else {
+ if (repoUrl.hasPermission()) {
note = null;
String key;
switch (repoUrl.permission) {
@@ -411,6 +400,17 @@
String description = MessageFormat.format(pattern, repoUrl.permission.toString());
note = description;
}
+ } else {
+ String protocol;
+ int protocolIndex = repoUrl.url.indexOf("://");
+ if (protocolIndex > -1) {
+ // explicit protocol specified
+ protocol = repoUrl.url.substring(0, protocolIndex);
+ } else {
+ // implicit SSH url
+ protocol = "ssh";
+ }
+ note = MessageFormat.format(getString("gb.externalPermissions"), protocol);
}
urlPermissionsMap.put(repoUrl.url, note);
}
diff --git a/src/main/java/com/gitblit/wicket/panels/SearchPanel.java b/src/main/java/com/gitblit/wicket/panels/SearchPanel.java
index 5d0b2de..09322bc 100644
--- a/src/main/java/com/gitblit/wicket/panels/SearchPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/SearchPanel.java
@@ -86,7 +86,7 @@
@Override
public void populateItem(final Item<RevCommit> item) {
final RevCommit entry = item.getModelObject();
- final Date date = JGitUtils.getCommitDate(entry);
+ final Date date = JGitUtils.getAuthorDate(entry);
item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
diff --git a/src/main/java/com/gitblit/wicket/panels/TeamsPanel.html b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.html
index ff68929..2cce7b1 100644
--- a/src/main/java/com/gitblit/wicket/panels/TeamsPanel.html
+++ b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.html
@@ -15,16 +15,18 @@
<img style="vertical-align: middle; border: 1px solid #888; background-color: white;" src="users_16x16.png"/>
<wicket:message key="gb.teams">[teams]</wicket:message>
</th>
+ <th class="hidden-phone" style="width:140px;"><wicket:message key="gb.type">[type]</wicket:message></th>
<th class="hidden-phone" style="width:140px;"><wicket:message key="gb.teamMembers">[team members]</wicket:message></th>
<th class="hidden-phone" style="width:100px;"><wicket:message key="gb.repositories">[repositories]</wicket:message></th>
<th style="width:80px;" class="right"></th>
</tr>
- <tbody>
+ <tbody>
<tr wicket:id="teamRow">
<td class="left" ><div class="list" wicket:id="teamname">[teamname]</div></td>
+ <td class="hidden-phone left" ><span style="font-size: 0.8em;" wicket:id="accountType">[account type]</span></td>
<td class="hidden-phone left" ><div class="list" wicket:id="members">[members]</div></td>
<td class="hidden-phone left" ><div class="list" wicket:id="repositories">[repositories]</div></td>
- <td class="rightAlign"><span wicket:id="teamLinks"></span></td>
+ <td class="rightAlign"><span wicket:id="teamLinks"></span></td>
</tr>
</tbody>
</table>
diff --git a/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java
index c1e1a43..7f3fd9a 100644
--- a/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java
@@ -60,6 +60,7 @@
EditTeamPage.class, WicketUtils.newTeamnameParameter(entry.name));
WicketUtils.setHtmlTooltip(editLink, getString("gb.edit") + " " + entry.name);
item.add(editLink);
+ item.add(new Label("accountType", entry.accountType.name()));
item.add(new Label("members", entry.users.size() > 0 ? ("" + entry.users.size())
: ""));
item.add(new Label("repositories",
diff --git a/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html
index 30f5036..659baea 100644
--- a/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html
+++ b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html
@@ -30,6 +30,9 @@
<td class="hidden-phone ticket-list-state">
<i wicket:message="title:gb.watching" style="color:#888;" class="fa fa-eye" wicket:id="watching"></i>
</td>
+ <td class="ticket-list-priority">
+ <div wicket:id="priority"></div>
+ </td>
<td class="ticket-list-state">
<div wicket:id="status"></div>
</td>
diff --git a/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java
index cc0b57a..1fbe87c 100644
--- a/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java
@@ -83,7 +83,10 @@
item.add(new Label("ticketsLink").setVisible(false));
}
- item.add(TicketsUI.getStateIcon("state", ticket.type, ticket.status));
+ Label icon = TicketsUI.getStateIcon("state", ticket.type, ticket.status, ticket.severity);
+ WicketUtils.addCssClass(icon, TicketsUI.getSeverityClass(ticket.severity));
+ item.add(icon);
+
item.add(new Label("id", "" + ticket.number));
UserModel creator = app().users().getUserModel(ticket.createdBy);
if (creator != null) {
@@ -153,7 +156,7 @@
if (responsible == null) {
responsible = new UserModel(ticket.responsible);
}
- GravatarImage avatar = new GravatarImage("responsible", responsible.getDisplayName(),
+ AvatarImage avatar = new AvatarImage("responsible", responsible.getDisplayName(),
responsible.emailAddress, null, 16, true);
avatar.setTooltip(getString("gb.responsible") + ": " + responsible.getDisplayName());
item.add(avatar);
@@ -167,6 +170,11 @@
// watching indicator
item.add(new Label("watching").setVisible(ticket.isWatching(GitBlitWebSession.get().getUsername())));
+ // priority indicator
+ Label priorityIcon = TicketsUI.getPriorityIcon("priority", ticket.priority);
+ WicketUtils.addCssClass(priorityIcon, TicketsUI.getPriorityClass(ticket.priority));
+ item.add(priorityIcon.setVisible(true));
+
// status indicator
String css = TicketsUI.getLozengeClass(ticket.status, true);
Label l = new Label("status", ticket.status.toString());
diff --git a/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java b/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java
index 2bf5ee7..063889b 100644
--- a/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java
@@ -25,7 +25,7 @@
public UserTitlePanel(String wicketId, UserModel user, String title) {
super(wicketId);
- add(new GravatarImage("userGravatar", user, "gravatar", 36, false));
+ add(new AvatarImage("userGravatar", user, "gravatar", 36, false));
add(new Label("userDisplayName", user.getDisplayName()));
add(new Label("userTitle", title));
}
diff --git a/src/main/java/login_zh_TW.mkd b/src/main/java/login_zh_TW.mkd
new file mode 100644
index 0000000..68cfedf
--- /dev/null
+++ b/src/main/java/login_zh_TW.mkd
@@ -0,0 +1,3 @@
+## 請登入
+
+請輸入密碼,以便登入此Gitblit版控伺服器
diff --git a/src/main/java/welcome_zh_TW.mkd b/src/main/java/welcome_zh_TW.mkd
new file mode 100644
index 0000000..eaaee65
--- /dev/null
+++ b/src/main/java/welcome_zh_TW.mkd
@@ -0,0 +1,3 @@
+## 歡迎來到Gitblit版本控管伺服器
+
+一個快速讓您能存放自己Git文件庫的解決方案 [Git](http://www.git-scm.com)
diff --git a/src/main/js/.gitignore b/src/main/js/.gitignore
new file mode 100644
index 0000000..b512c09
--- /dev/null
+++ b/src/main/js/.gitignore
@@ -0,0 +1 @@
+node_modules
\ No newline at end of file
diff --git a/src/main/js/editor.dev.css b/src/main/js/editor.dev.css
new file mode 100644
index 0000000..d1a0ebe
--- /dev/null
+++ b/src/main/js/editor.dev.css
@@ -0,0 +1,70 @@
+.ProseMirror-menubar {
+
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+ color: #666;
+ padding: 1px 6px;
+ border-bottom: 1px solid silver;
+ background: white;
+ z-index: 10;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ overflow: visible;
+
+}
+
+.ProseMirror-menubar.scrolling {
+ top: 47px;
+}
+
+.ProseMirror-menuitem {
+ display: inline-block;
+ font-size: 1.2em;
+ text-align: center;
+ min-width: 30px;
+ line-height: 30px;
+ box-sizing: border-box;
+ margin: 1px;
+ border-width: 1px;
+ border-style: solid;
+ border-color: white;
+}
+
+.ProseMirror-menuitem:hover {
+ border-radius: 5px;
+ background: #fcfcfc;
+ border-color: #95a5a6;
+ border-width: 1px;
+ border-style: solid;
+ box-sizing: border-box;
+}
+
+.ProseMirror-icon {
+ line-height: inherit;
+ vertical-align: middle;
+}
+
+
+.ProseMirror-menubar .fa-header-x:after {
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 65%;
+ vertical-align: text-bottom;
+ position: relative;
+ top: 2px;
+}
+
+.fa-header-1:after {
+ content: "1";
+}
+
+.fa-header-2:after {
+ content: "2";
+}
+
+.fa-header-3:after {
+ content: "3";
+}
+
+.forceHide {
+ display:none !important;
+}
\ No newline at end of file
diff --git a/src/main/js/editor.dev.js b/src/main/js/editor.dev.js
new file mode 100644
index 0000000..bbe67d3
--- /dev/null
+++ b/src/main/js/editor.dev.js
@@ -0,0 +1,279 @@
+attachDocumentEditor = function (editorElement, commitDialogElement)
+{
+ var edit = require("./prosemirror/dist/edit")
+ require("./prosemirror/dist/inputrules/autoinput")
+ require("./prosemirror/dist/menu/menubar")
+ require("./prosemirror/dist/markdown")
+ var _menu = require("./prosemirror/dist/menu/menu")
+
+
+ var content = document.querySelector('#editor');
+ content.style.display = "none";
+
+ var gitblitCommands = new _menu.MenuCommandGroup("gitblitCommands");
+ var viewCommands = new _menu.MenuCommandGroup("viewCommands");
+ var textCommands = new _menu.MenuCommandGroup("textCommands");
+ var insertCommands = new _menu.MenuCommandGroup("insertCommands");
+
+ var menuItems = [gitblitCommands, viewCommands, textCommands, _menu.inlineGroup, _menu.blockGroup, _menu.historyGroup, insertCommands];
+
+ const updateCmd = Object.create(null);
+
+ updateCmd["GitblitCommit"] = {
+ label: "GitblitCommit",
+ run: function() {
+ commitDialogElement.modal({show:true});
+ editorElement.value = pm.getContent('markdown');
+ },
+ menu: {
+ group: "gitblitCommands", rank: 10,
+ display: {
+ render: function(cmd, pm) { return renderFontAwesomeIcon(cmd, pm, "fa-save"); }
+ }
+ }
+ };
+
+ updateCmd["FullScreen"] = {
+ label: "Toggle Fullscreen",
+ derive: "toggle",
+ run: function(pm) {
+ //Maintain the scroll context
+ var initialScroll = window.scrollY;
+ var navs = [document.querySelector("div.repositorynavbar"), document.querySelector("div.navbar"), document.querySelector("div.docnav")];
+ var offset = navs.reduce(function(p, c) { return p + c.offsetHeight; }, 0);
+ navs.forEach(function(e) { e.classList.toggle("forceHide"); });
+
+ if (!toggleFullScreen(document.documentElement)) {
+ offset = 60;
+ } else {
+ offset -= 60;
+ }
+
+ pm.signal("commandsChanged");
+
+ //Browsers don't seem to accept a scrollTo straight after a full screen
+ setTimeout(function(){window.scrollTo(0, Math.max(0,initialScroll - offset));}, 100);
+
+ },
+ menu: {
+ group: "viewCommands", rank: 11,
+ display: {
+ render: function(cmd, pm) { return renderFontAwesomeIcon(cmd, pm, "fa-arrows-alt"); }
+ }
+ },
+ active: function active(pm) { return getFullScreenElement() ? true : false; }
+ };
+
+ updateCmd["heading1"] = {
+ derive: "toggle",
+ run: function(pm) {
+ var selection = pm.selection;
+ var from = selection.from;
+ var to = selection.to;
+ var attr = {name:"make", level:"1"};
+
+ var node = pm.doc.resolve(from).parent;
+ if (node && node.hasMarkup(pm.schema.nodes.heading, attr)) {
+ return pm.tr.setBlockType(from, to, pm.schema.defaultTextblockType(), {}).apply(pm.apply.scroll);
+ } else {
+ return pm.tr.setBlockType(from, to, pm.schema.nodes.heading, attr).apply(pm.apply.scroll);
+ }
+
+ },
+ active: function active(pm) {
+ var node = pm.doc.resolve(pm.selection.from).parent;
+ if (node && node.hasMarkup(pm.schema.nodes.heading, {name:"make", level:"1"})) {
+ return true;
+ }
+ return false;
+ },
+ menu: {
+ group: "textCommands", rank: 1,
+ display: {
+ render: function(cmd, pm) { return renderFontAwesomeIcon(cmd, pm, "fa-header fa-header-x fa-header-1"); }
+ },
+ },
+ select: function(){return true;}
+ };
+
+ updateCmd["heading2"] = {
+ derive: "toggle",
+ run: function(pm) {
+ var selection = pm.selection;
+ var from = selection.from;
+ var to = selection.to;
+ var attr = {name:"make", level:"2"};
+
+ var node = pm.doc.resolve(from).parent;
+ if (node && node.hasMarkup(pm.schema.nodes.heading, attr)) {
+ return pm.tr.setBlockType(from, to, pm.schema.defaultTextblockType(), {}).apply(pm.apply.scroll);
+ } else {
+ return pm.tr.setBlockType(from, to, pm.schema.nodes.heading, attr).apply(pm.apply.scroll);
+ }
+
+ },
+ active: function active(pm) {
+ var node = pm.doc.resolve(pm.selection.from).parent;
+ if (node && node.hasMarkup(pm.schema.nodes.heading, {name:"make", level:"2"})) {
+ return true;
+ }
+ return false;
+ },
+ menu: {
+ group: "textCommands", rank: 2,
+ display: {
+ render: function(cmd, pm) { return renderFontAwesomeIcon(cmd, pm, "fa-header fa-header-x fa-header-2"); }
+ },
+ },
+ select: function(){return true;}
+ };
+
+ updateCmd["heading3"] = {
+ derive: "toggle",
+ run: function(pm) {
+ var selection = pm.selection;
+ var from = selection.from;
+ var to = selection.to;
+ var attr = {name:"make", level:"3"};
+
+ var node = pm.doc.resolve(from).parent;
+ if (node && node.hasMarkup(pm.schema.nodes.heading, attr)) {
+ return pm.tr.setBlockType(from, to, pm.schema.defaultTextblockType(), {}).apply(pm.apply.scroll);
+ } else {
+ return pm.tr.setBlockType(from, to, pm.schema.nodes.heading, attr).apply(pm.apply.scroll);
+ }
+
+ },
+ active: function active(pm) {
+ var node = pm.doc.resolve(pm.selection.from).parent;
+ if (node && node.hasMarkup(pm.schema.nodes.heading, {name:"make", level:"3"})) {
+ return true;
+ }
+ return false;
+ },
+ menu: {
+ group: "textCommands", rank: 3,
+ display: {
+ render: function(cmd, pm) { return renderFontAwesomeIcon(cmd, pm, "fa-header fa-header-x fa-header-3"); }
+ },
+ },
+ select: function(){return true;}
+ };
+
+ updateCmd["strong:toggle"] = {
+ menu: {
+ group: "textCommands", rank: 4,
+ display: {
+ render: function(cmd, pm) { return renderFontAwesomeIcon(cmd, pm, "fa-bold"); }
+ }
+ },
+ select: function(){return true;}
+ };
+
+ updateCmd["em:toggle"] = {
+ menu: {
+ group: "textCommands", rank: 5,
+ display: {
+ render: function(cmd, pm) { return renderFontAwesomeIcon(cmd, pm, "fa-italic"); }
+ }
+ },
+ select: function(){return true;}
+ };
+
+ updateCmd["code:toggle"] = {
+ menu: {
+ group: "textCommands", rank: 6,
+ display: {
+ render: function(cmd, pm) { return renderFontAwesomeIcon(cmd, pm, "fa-code"); }
+ }
+ },
+ select: function(){return true;}
+ };
+
+ updateCmd["image:insert"] = {
+ menu: {
+ group: "insertCommands", rank: 1,
+ display: {
+ render: function(cmd, pm) { return renderFontAwesomeIcon(cmd, pm, "fa-picture-o"); }
+ }
+ }
+ };
+
+ updateCmd["selectParentNode"] = {
+ menu: {
+ group: "insertCommands", rank: 10,
+ display: {
+ render: function(cmd, pm) { return renderFontAwesomeIcon(cmd, pm, "fa-arrow-circle-o-left"); }
+ }
+ }
+ };
+
+ var pm = window.pm = new edit.ProseMirror({
+ place: document.querySelector('#visualEditor'),
+ autoInput: true,
+ doc: content.value,
+ menuBar: { float:true, content: menuItems},
+ commands: edit.CommandSet.default.update(updateCmd),
+ docFormat: "markdown"
+ });
+
+
+ var scrollStart = document.querySelector(".ProseMirror").offsetTop;
+
+
+ var ticking = false;
+ window.addEventListener("scroll", function() {
+ var scrollPosition = window.scrollY;
+ if (!ticking) {
+ window.requestAnimationFrame(function() {
+ if (!getFullScreenElement() && (scrollPosition > scrollStart)) {
+ document.querySelector(".ProseMirror-menubar").classList.add("scrolling");
+ } else {
+ document.querySelector(".ProseMirror-menubar").classList.remove("scrolling");
+ }
+ ticking = false;
+ });
+ }
+ ticking = true;
+ });
+}
+
+function renderFontAwesomeIcon(cmd, pm, classNames) {
+ var node = document.createElement("div");
+ node.className = "ProseMirror-icon";
+ var icon = document.createElement("i");
+ icon.setAttribute("class", "fa fa-fw " + classNames);
+
+ var active = cmd.active(pm);
+
+ if (active || cmd.spec.invert) node.classList.add("ProseMirror-menu-active");
+ node.appendChild(icon);
+ return node;
+}
+
+
+
+function getFullScreenElement() {
+ return document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement;
+}
+
+function toggleFullScreen(e) {
+ if (getFullScreenElement()) {
+ if (document.exitFullscreen) { document.exitFullscreen(); }
+ else if (document.msExitFullscreen) { document.msExitFullscreen(); }
+ else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); }
+ else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); }
+ return true;
+ } else {
+ if (e.requestFullscreen) { e.requestFullscreen(); }
+ else if (e.msRequestFullscreen) { e.msRequestFullscreen(); }
+ else if (e.mozRequestFullScreen) { e.mozRequestFullScreen(); }
+ else if (e.webkitRequestFullscreen) { e.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); }
+ }
+ return false;
+}
+
+commitChanges = function() {
+ document.querySelector('form#documentEditor').submit();
+}
+
diff --git a/src/main/js/package.json b/src/main/js/package.json
new file mode 100644
index 0000000..a8a3101
--- /dev/null
+++ b/src/main/js/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "gitblit",
+ "homepage": "http://gitblit.com/",
+ "version": "1.0.0",
+ "devDependencies": {
+ "babel-cli": "^6.4.5",
+ "babel-preset-es2015": "^6.3.13",
+ "babelify": "*",
+ "browserify": "^11.2.0",
+ "browserify-shim": "^3.8.10",
+ "suitcss-preprocessor": "^0.8.0",
+ "uglify-js": "^2.6.1"
+ },
+ "scripts": {
+ "build": "npm run build:js-min & npm run build:css-min",
+ "build:debug": "npm run build:js & npm run build:css",
+ "build:js": "browserify editor.dev.js > ../resources/gitblit-editor.js",
+ "build:js-min": "browserify editor.dev.js | uglifyjs --compress --mangle -o ../resources/gitblit-editor.min.js",
+ "build:css": "suitcss editor.dev.css ../resources/gitblit-editor.css",
+ "build:css-min": "suitcss -m editor.dev.css ../resources/gitblit-editor.min.css",
+ "postinstall": "cd prosemirror && npm install"
+ }
+}
diff --git a/src/main/js/prosemirror b/src/main/js/prosemirror
new file mode 160000
index 0000000..b53cace
--- /dev/null
+++ b/src/main/js/prosemirror
@@ -0,0 +1 @@
+Subproject commit b53cacec194d8f6fc5309383aed633c37c2c7114
diff --git a/src/main/resources/blink32.png b/src/main/resources/blink32.png
new file mode 100644
index 0000000..da59350
--- /dev/null
+++ b/src/main/resources/blink32.png
Binary files differ
diff --git a/src/main/resources/fontawesome/css/font-awesome.min.css b/src/main/resources/fontawesome/css/font-awesome.min.css
index 449d6ac..d0603cb 100644
--- a/src/main/resources/fontawesome/css/font-awesome.min.css
+++ b/src/main/resources/fontawesome/css/font-awesome.min.css
@@ -1,4 +1,4 @@
/*!
- * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome
+ * Font Awesome 4.5.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.0.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.3333333333333333em;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.2857142857142858em;text-align:center}.fa-ul{padding-left:0;margin-left:2.142857142857143em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;top:.14285714285714285em;text-align:center}.fa-li.fa-lg{left:-1.8571428571428572em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-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)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0,mirror=1);-webkit-transform:scale(-1,1);-moz-transform:scale(-1,1);-ms-transform:scale(-1,1);-o-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);-moz-transform:scale(1,-1);-ms-transform:scale(1,-1);-o-transform:scale(1,-1);transform:scale(1,-1)}.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-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-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{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:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.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-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-asc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-desc: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-reply-all:before{content:"\f122"}.fa-mail-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{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"}
\ No newline at end of file
+ */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.5.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.5.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"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}
diff --git a/src/main/resources/fontawesome/fonts/FontAwesome.otf b/src/main/resources/fontawesome/fonts/FontAwesome.otf
index 8b0f54e..3ed7f8b 100644
--- a/src/main/resources/fontawesome/fonts/FontAwesome.otf
+++ b/src/main/resources/fontawesome/fonts/FontAwesome.otf
Binary files differ
diff --git a/src/main/resources/fontawesome/fonts/fontawesome-webfont.eot b/src/main/resources/fontawesome/fonts/fontawesome-webfont.eot
index 7c79c6a..9b6afae 100644
--- a/src/main/resources/fontawesome/fonts/fontawesome-webfont.eot
+++ b/src/main/resources/fontawesome/fonts/fontawesome-webfont.eot
Binary files differ
diff --git a/src/main/resources/fontawesome/fonts/fontawesome-webfont.svg b/src/main/resources/fontawesome/fonts/fontawesome-webfont.svg
index 45fdf33..d05688e 100644
--- a/src/main/resources/fontawesome/fonts/fontawesome-webfont.svg
+++ b/src/main/resources/fontawesome/fonts/fontawesome-webfont.svg
@@ -14,10 +14,11 @@
<glyph unicode="®" horiz-adv-x="1792" />
<glyph unicode="´" horiz-adv-x="1792" />
<glyph unicode="Æ" horiz-adv-x="1792" />
+<glyph unicode="Ø" horiz-adv-x="1792" />
<glyph unicode=" " horiz-adv-x="768" />
-<glyph unicode=" " />
+<glyph unicode=" " horiz-adv-x="1537" />
<glyph unicode=" " horiz-adv-x="768" />
-<glyph unicode=" " />
+<glyph unicode=" " horiz-adv-x="1537" />
<glyph unicode=" " horiz-adv-x="512" />
<glyph unicode=" " horiz-adv-x="384" />
<glyph unicode=" " horiz-adv-x="256" />
@@ -30,7 +31,7 @@
<glyph unicode="™" horiz-adv-x="1792" />
<glyph unicode="∞" horiz-adv-x="1792" />
<glyph unicode="≠" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="500" d="M0 0z" />
+<glyph unicode="◼" horiz-adv-x="500" d="M0 0z" />
<glyph unicode="" 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="" 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="" 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" />
@@ -52,7 +53,7 @@
<glyph unicode="" 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="" 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="" 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="" horiz-adv-x="1280" d="M128 0h1024v768h-416q-40 0 -68 28t-28 68v416h-512v-1280zM768 896h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376zM1280 864v-896q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h640q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88z " />
+<glyph unicode="" 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="" 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="" 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="" 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" />
@@ -77,11 +78,11 @@
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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" />
@@ -109,8 +110,8 @@
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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" />
@@ -143,17 +144,17 @@
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" horiz-adv-x="2048" d="M640 640v-512h-256v512h256zM1024 1152v-1024h-256v1024h256zM2048 0v-128h-2048v1536h128v-1408h1920zM1408 896v-768h-256v768h256zM1792 1280v-1152h-256v1152h256z" />
<glyph unicode="" 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="" d="M1307 618l23 219h-198v109q0 49 15.5 68.5t71.5 19.5h110v219h-175q-152 0 -218 -72t-66 -213v-131h-131v-219h131v-635h262v635h175zM1536 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.5h960 q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" 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="" 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="" 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="" 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" />
@@ -176,14 +177,14 @@
<glyph unicode="" 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="" 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="" 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="" horiz-adv-x="768" d="M511 980h257l-30 -284h-227v-824h-341v824h-170v284h170v171q0 182 86 275.5t283 93.5h227v-284h-142q-39 0 -62.5 -6.5t-34 -23.5t-13.5 -34.5t-3 -49.5v-142z" />
+<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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" />
@@ -218,8 +219,8 @@
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" d="M917 631q0 26 -6 64h-362v-132h217q-3 -24 -16.5 -50t-37.5 -53t-66.5 -44.5t-96.5 -17.5q-99 0 -169 71t-70 171t70 171t169 71q92 0 153 -59l104 101q-108 100 -257 100q-160 0 -272 -112.5t-112 -271.5t112 -271.5t272 -112.5q165 0 266.5 105t101.5 270zM1262 585 h109v110h-109v110h-110v-110h-110v-110h110v-110h110v110zM1536 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="" horiz-adv-x="2304" d="M1437 623q0 -208 -87 -370.5t-248 -254t-369 -91.5q-149 0 -285 58t-234 156t-156 234t-58 285t58 285t156 234t234 156t285 58q286 0 491 -192l-199 -191q-117 113 -292 113q-123 0 -227.5 -62t-165.5 -168.5t-61 -232.5t61 -232.5t165.5 -168.5t227.5 -62 q83 0 152.5 23t114.5 57.5t78.5 78.5t49 83t21.5 74h-416v252h692q12 -63 12 -122zM2304 745v-210h-209v-209h-210v209h-209v210h209v209h210v-209h209z" />
<glyph unicode="" 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="" 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="" 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" />
@@ -247,10 +248,10 @@
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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 896h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376zM1280 864v-896q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h640q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88z" />
+<glyph unicode="" 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="" 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="" 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="" 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" />
@@ -274,7 +275,7 @@
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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" />
@@ -345,8 +346,8 @@
<glyph unicode="" 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="" 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="" 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="" horiz-adv-x="1280" d="M1280 768v-800q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h544v-544q0 -40 28 -68t68 -28h544zM1277 896h-509v509q82 -15 132 -65l312 -312q50 -50 65 -132z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1024 160v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1024 416v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1280 768v-800q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28 t-28 68v1344q0 40 28 68t68 28h544v-544q0 -40 28 -68t68 -28h544zM1277 896h-509v509q82 -15 132 -65l312 -312q50 -50 65 -132z" />
+<glyph unicode="" 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="" 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="" 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="" 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="" 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" />
@@ -361,14 +362,14 @@
<glyph unicode="" 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="" 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="" 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="" 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="" d="M1289 -96h-1118v480h-160v-640h1438v640h-160v-480zM347 428l33 157l783 -165l-33 -156zM450 802l67 146l725 -339l-67 -145zM651 1158l102 123l614 -513l-102 -123zM1048 1536l477 -641l-128 -96l-477 641zM330 65v159h800v-159h-800z" />
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" horiz-adv-x="1024" d="M390 1408h219v-388h364v-241h-364v-394q0 -136 14 -172q13 -37 52 -60q50 -31 117 -31q117 0 232 76v-242q-102 -48 -178 -65q-77 -19 -173 -19q-105 0 -186 27q-78 25 -138 75q-58 51 -79 105q-22 54 -22 161v539h-170v217q91 30 155 84q64 55 103 132q39 78 54 196z " />
-<glyph unicode="" d="M1123 127v181q-88 -56 -174 -56q-51 0 -88 23q-29 17 -39 45q-11 30 -11 129v295h274v181h-274v291h-164q-11 -90 -40 -147t-78 -99q-48 -40 -116 -63v-163h127v-404q0 -78 17 -121q17 -42 59 -78q43 -37 104 -57q62 -20 140 -20q67 0 129 14q57 13 134 49zM1536 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="" 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="" 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="" 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="" 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="" 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" />
@@ -379,7 +380,7 @@
<glyph unicode="" 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="" 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="" 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="" horiz-adv-x="1664" d="M1483 512l-587 -587q-52 -53 -127.5 -53t-128.5 53l-587 587q-53 53 -53 128t53 128l587 587q53 53 128 53t128 -53l265 -265l-398 -399l-188 188q-42 42 -99 42q-59 0 -100 -41l-120 -121q-42 -40 -42 -99q0 -58 42 -100l406 -408q30 -28 67 -37l6 -4h28q60 0 99 41 l619 619l2 -3q53 -53 53 -128t-53 -128zM1406 1138l120 -120q14 -15 14 -36t-14 -36l-730 -730q-17 -15 -37 -15v0q-4 0 -6 1q-18 2 -30 14l-407 408q-14 15 -14 36t14 35l121 120q13 15 35 15t36 -15l252 -252l574 575q15 15 36 15t36 -15z" />
+<glyph unicode="" 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="" 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="" 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="" 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" />
@@ -398,17 +399,257 @@
<glyph unicode="" 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="" 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="" 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="" d="M1254 899q16 85 -21 132q-52 65 -187 45q-17 -3 -41 -12.5t-57.5 -30.5t-64.5 -48.5t-59.5 -70t-44.5 -91.5q80 7 113.5 -16t26.5 -99q-5 -52 -52 -143q-43 -78 -71 -99q-44 -32 -87 14q-23 24 -37.5 64.5t-19 73t-10 84t-8.5 71.5q-23 129 -34 164q-12 37 -35.5 69 t-50.5 40q-57 16 -127 -25q-54 -32 -136.5 -106t-122.5 -102v-7q16 -8 25.5 -26t21.5 -20q21 -3 54.5 8.5t58 10.5t41.5 -30q11 -18 18.5 -38.5t15 -48t12.5 -40.5q17 -46 53 -187q36 -146 57 -197q42 -99 103 -125q43 -12 85 -1.5t76 31.5q131 77 250 237 q104 139 172.5 292.5t82.5 226.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="" 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="" 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="" 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="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
+<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M768 750h725q12 -67 12 -128q0 -217 -91 -387.5t-259.5 -266.5t-386.5 -96q-157 0 -299 60.5t-245 163.5t-163.5 245t-60.5 299t60.5 299t163.5 245t245 163.5t299 60.5q300 0 515 -201l-209 -201q-123 119 -306 119q-129 0 -238.5 -65t-173.5 -176.5t-64 -243.5 t64 -243.5t173.5 -176.5t238.5 -65q87 0 160 24t120 60t82 82t51.5 87t22.5 78h-436v264z" />
+<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" horiz-adv-x="1792" d="M1760 640q0 -176 -68.5 -336t-184 -275.5t-275.5 -184t-336 -68.5t-336 68.5t-275.5 184t-184 275.5t-68.5 336q0 213 97 398.5t265 305.5t374 151v-228q-221 -45 -366.5 -221t-145.5 -406q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5 t136.5 204t51 248.5q0 230 -145.5 406t-366.5 221v228q206 -31 374 -151t265 -305.5t97 -398.5z" />
+<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" horiz-adv-x="2048" d="M2048 0v-128h-2048v1536h128v-1408h1920zM1664 1024l256 -896h-1664v576l448 576l576 -576z" />
+<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" horiz-adv-x="1792" />
+<glyph unicode="" horiz-adv-x="1792" />
+<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" horiz-adv-x="1792" d="M597 1115v-1173q0 -25 -12.5 -42.5t-36.5 -17.5q-17 0 -33 8l-465 233q-21 10 -35.5 33.5t-14.5 46.5v1140q0 20 10 34t29 14q14 0 44 -15l511 -256q3 -3 3 -5zM661 1014l534 -866l-534 266v600zM1792 996v-1054q0 -25 -14 -40.5t-38 -15.5t-47 13l-441 220zM1789 1116 q0 -3 -256.5 -419.5t-300.5 -487.5l-390 634l324 527q17 28 52 28q14 0 26 -6l541 -270q4 -2 4 -6z" />
+<glyph unicode="" d="M809 532l266 499h-112l-157 -312q-24 -48 -44 -92l-42 92l-155 312h-120l263 -493v-324h101v318zM1536 1408v-1536h-1536v1536h1536z" />
+<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" horiz-adv-x="1792" d="M1493 1308q-165 110 -359 110q-155 0 -293 -73t-240 -200q-75 -93 -119.5 -218t-48.5 -266v-42q4 -141 48.5 -266t119.5 -218q102 -127 240 -200t293 -73q194 0 359 110q-121 -108 -274.5 -168t-322.5 -60q-29 0 -43 1q-175 8 -333 82t-272 193t-181 281t-67 339 q0 182 71 348t191 286t286 191t348 71h3q168 -1 320.5 -60.5t273.5 -167.5zM1792 640q0 -192 -77 -362.5t-213 -296.5q-104 -63 -222 -63q-137 0 -255 84q154 56 253.5 233t99.5 405q0 227 -99 404t-253 234q119 83 254 83q119 0 226 -65q135 -125 210.5 -295t75.5 -361z " />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 599q0 -56 -7 -104h-1151q0 -146 109.5 -244.5t257.5 -98.5q99 0 185.5 46.5t136.5 130.5h423q-56 -159 -170.5 -281t-267.5 -188.5t-321 -66.5q-187 0 -356 83q-228 -116 -394 -116q-237 0 -237 263q0 115 45 275q17 60 109 229q199 360 475 606 q-184 -79 -427 -354q63 274 283.5 449.5t501.5 175.5q30 0 45 -1q255 117 433 117q64 0 116 -13t94.5 -40.5t66.5 -76.5t24 -115q0 -116 -75 -286q101 -182 101 -390zM1722 1239q0 83 -53 132t-137 49q-108 0 -254 -70q121 -47 222.5 -131.5t170.5 -195.5q51 135 51 216z M128 2q0 -86 48.5 -132.5t134.5 -46.5q115 0 266 83q-122 72 -213.5 183t-137.5 245q-98 -205 -98 -332zM632 715h728q-5 142 -113 237t-251 95q-144 0 -251.5 -95t-112.5 -237z" />
+<glyph unicode="" 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="" 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="" d="M1401 -11l-6 -6q-113 -114 -259 -175q-154 -64 -317 -64q-165 0 -317 64q-148 63 -259 175q-113 112 -175 258q-42 103 -54 189q-4 28 48 36q51 8 56 -20q1 -1 1 -4q18 -90 46 -159q50 -124 152 -226q98 -98 226 -152q132 -56 276 -56q143 0 276 56q128 55 225 152l6 6 q10 10 25 6q12 -3 33 -22q36 -37 17 -58zM929 604l-66 -66l63 -63q21 -21 -7 -49q-17 -17 -32 -17q-10 0 -19 10l-62 61l-66 -66q-5 -5 -15 -5q-15 0 -31 16l-2 2q-18 15 -18 29q0 7 8 17l66 65l-66 66q-16 16 14 45q18 18 31 18q6 0 13 -5l65 -66l65 65q18 17 48 -13 q27 -27 11 -44zM1400 547q0 -118 -46 -228q-45 -105 -126 -186q-80 -80 -187 -126t-228 -46t-228 46t-187 126q-82 82 -125 186q-15 32 -15 40h-1q-9 27 43 44q50 16 60 -12q37 -99 97 -167h1v339v2q3 136 102 232q105 103 253 103q147 0 251 -103t104 -249 q0 -147 -104.5 -251t-250.5 -104q-58 0 -112 16q-28 11 -13 61q16 51 44 43l14 -3q14 -3 32.5 -6t30.5 -3q104 0 176 71.5t72 174.5q0 101 -72 171q-71 71 -175 71q-107 0 -178 -80q-64 -72 -64 -160v-413q110 -67 242 -67q96 0 185 36.5t156 103.5t103.5 155t36.5 183 q0 198 -141 339q-140 140 -339 140q-200 0 -340 -140q-53 -53 -77 -87l-2 -2q-8 -11 -13 -15.5t-21.5 -9.5t-38.5 3q-21 5 -36.5 16.5t-15.5 26.5v680q0 15 10.5 26.5t27.5 11.5h877q30 0 30 -55t-30 -55h-811v-483h1q40 42 102 84t108 61q109 46 231 46q121 0 228 -46 t187 -126q81 -81 126 -186q46 -112 46 -229zM1369 1128q9 -8 9 -18t-5.5 -18t-16.5 -21q-26 -26 -39 -26q-9 0 -16 7q-106 91 -207 133q-128 56 -276 56q-133 0 -262 -49q-27 -10 -45 37q-9 25 -8 38q3 16 16 20q130 57 299 57q164 0 316 -64q137 -58 235 -152z" />
+<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" horiz-adv-x="1024" d="M512 345l512 295v-591l-512 -296v592zM0 640v-591l512 296zM512 1527v-591l-512 -296v591zM512 936l512 295v-591z" />
+<glyph unicode="" 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="" d="M0 1408h1536v-1536h-1536v1536zM1085 293l-221 631l221 297h-634l221 -297l-221 -631l317 -304z" />
+<glyph unicode="" 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="" horiz-adv-x="1792" d="M1792 690q0 -58 -29.5 -105.5t-79.5 -72.5q12 -46 12 -96q0 -155 -106.5 -287t-290.5 -208.5t-400 -76.5t-399.5 76.5t-290 208.5t-106.5 287q0 47 11 94q-51 25 -82 73.5t-31 106.5q0 82 58 140.5t141 58.5q85 0 145 -63q218 152 515 162l116 521q3 13 15 21t26 5 l369 -81q18 37 54 59.5t79 22.5q62 0 106 -43.5t44 -105.5t-44 -106t-106 -44t-105.5 43.5t-43.5 105.5l-334 74l-104 -472q300 -9 519 -160q58 61 143 61q83 0 141 -58.5t58 -140.5zM418 491q0 -62 43.5 -106t105.5 -44t106 44t44 106t-44 105.5t-106 43.5q-61 0 -105 -44 t-44 -105zM1228 136q11 11 11 26t-11 26q-10 10 -25 10t-26 -10q-41 -42 -121 -62t-160 -20t-160 20t-121 62q-11 10 -26 10t-25 -10q-11 -10 -11 -25.5t11 -26.5q43 -43 118.5 -68t122.5 -29.5t91 -4.5t91 4.5t122.5 29.5t118.5 68zM1225 341q62 0 105.5 44t43.5 106 q0 61 -44 105t-105 44q-62 0 -106 -43.5t-44 -105.5t44 -106t106 -44z" />
+<glyph unicode="" horiz-adv-x="1792" d="M69 741h1q16 126 58.5 241.5t115 217t167.5 176t223.5 117.5t276.5 43q231 0 414 -105.5t294 -303.5q104 -187 104 -442v-188h-1125q1 -111 53.5 -192.5t136.5 -122.5t189.5 -57t213 -3t208 46.5t173.5 84.5v-377q-92 -55 -229.5 -92t-312.5 -38t-316 53 q-189 73 -311.5 249t-124.5 372q-3 242 111 412t325 268q-48 -60 -78 -125.5t-46 -159.5h635q8 77 -8 140t-47 101.5t-70.5 66.5t-80.5 41t-75 20.5t-56 8.5l-22 1q-135 -5 -259.5 -44.5t-223.5 -104.5t-176 -140.5t-138 -163.5z" />
+<glyph unicode="" horiz-adv-x="2304" d="M0 32v608h2304v-608q0 -66 -47 -113t-113 -47h-1984q-66 0 -113 47t-47 113zM640 256v-128h384v128h-384zM256 256v-128h256v128h-256zM2144 1408q66 0 113 -47t47 -113v-224h-2304v224q0 66 47 113t113 47h1984z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1549 857q55 0 85.5 -28.5t30.5 -83.5t-34 -82t-91 -27h-136v-177h-25v398h170zM1710 267l-4 -11l-5 -10q-113 -230 -330.5 -366t-474.5 -136q-182 0 -348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71q244 0 454.5 -124t329.5 -338l2 -4l8 -16 q-30 -15 -136.5 -68.5t-163.5 -84.5q-6 -3 -479 -268q384 -183 799 -366zM896 -234q250 0 462.5 132.5t322.5 357.5l-287 129q-72 -140 -206 -222t-292 -82q-151 0 -280 75t-204 204t-75 280t75 280t204 204t280 75t280 -73.5t204 -204.5l280 143q-116 208 -321 329 t-443 121q-119 0 -232.5 -31.5t-209 -87.5t-176.5 -137t-137 -176.5t-87.5 -209t-31.5 -232.5t31.5 -232.5t87.5 -209t137 -176.5t176.5 -137t209 -87.5t232.5 -31.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1427 827l-614 386l92 151h855zM405 562l-184 116v858l1183 -743zM1424 697l147 -95v-858l-532 335zM1387 718l-500 -802h-855l356 571z" />
+<glyph unicode="" horiz-adv-x="1792" d="M640 528v224q0 16 -16 16h-96q-16 0 -16 -16v-224q0 -16 16 -16h96q16 0 16 16zM1152 528v224q0 16 -16 16h-96q-16 0 -16 -16v-224q0 -16 16 -16h96q16 0 16 16zM1664 496v-752h-640v320q0 80 -56 136t-136 56t-136 -56t-56 -136v-320h-640v752q0 16 16 16h96 q16 0 16 -16v-112h128v624q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h16v393q-32 19 -32 55q0 26 19 45t45 19t45 -19t19 -45q0 -36 -32 -55v-9h272q16 0 16 -16v-224q0 -16 -16 -16h-272v-128h16q16 0 16 -16v-112h128 v112q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h96q16 0 16 -16v-624h128v112q0 16 16 16h96q16 0 16 -16z" />
+<glyph unicode="" horiz-adv-x="2304" d="M2288 731q16 -8 16 -27t-16 -27l-320 -192q-8 -5 -16 -5q-9 0 -16 4q-16 10 -16 28v128h-858q37 -58 83 -165q16 -37 24.5 -55t24 -49t27 -47t27 -34t31.5 -26t33 -8h96v96q0 14 9 23t23 9h320q14 0 23 -9t9 -23v-320q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23v96h-96 q-32 0 -61 10t-51 23.5t-45 40.5t-37 46t-33.5 57t-28.5 57.5t-28 60.5q-23 53 -37 81.5t-36 65t-44.5 53.5t-46.5 17h-360q-22 -84 -91 -138t-157 -54q-106 0 -181 75t-75 181t75 181t181 75q88 0 157 -54t91 -138h104q24 0 46.5 17t44.5 53.5t36 65t37 81.5q19 41 28 60.5 t28.5 57.5t33.5 57t37 46t45 40.5t51 23.5t61 10h107q21 57 70 92.5t111 35.5q80 0 136 -56t56 -136t-56 -136t-136 -56q-62 0 -111 35.5t-70 92.5h-107q-17 0 -33 -8t-31.5 -26t-27 -34t-27 -47t-24 -49t-24.5 -55q-46 -107 -83 -165h1114v128q0 18 16 28t32 -1z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1150 774q0 -56 -39.5 -95t-95.5 -39h-253v269h253q56 0 95.5 -39.5t39.5 -95.5zM1329 774q0 130 -91.5 222t-222.5 92h-433v-896h180v269h253q130 0 222 91.5t92 221.5zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348 t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="" horiz-adv-x="2304" d="M1645 438q0 59 -34 106.5t-87 68.5q-7 -45 -23 -92q-7 -24 -27.5 -38t-44.5 -14q-12 0 -24 3q-31 10 -45 38.5t-4 58.5q23 71 23 143q0 123 -61 227.5t-166 165.5t-228 61q-134 0 -247 -73t-167 -194q108 -28 188 -106q22 -23 22 -55t-22 -54t-54 -22t-55 22 q-75 75 -180 75q-106 0 -181 -74.5t-75 -180.5t75 -180.5t181 -74.5h1046q79 0 134.5 55.5t55.5 133.5zM1798 438q0 -142 -100.5 -242t-242.5 -100h-1046q-169 0 -289 119.5t-120 288.5q0 153 100 267t249 136q62 184 221 298t354 114q235 0 408.5 -158.5t196.5 -389.5 q116 -25 192.5 -118.5t76.5 -214.5zM2048 438q0 -175 -97 -319q-23 -33 -64 -33q-24 0 -43 13q-26 17 -32 48.5t12 57.5q71 104 71 233t-71 233q-18 26 -12 57t32 49t57.5 11.5t49.5 -32.5q97 -142 97 -318zM2304 438q0 -244 -134 -443q-23 -34 -64 -34q-23 0 -42 13 q-26 18 -32.5 49t11.5 57q108 164 108 358q0 195 -108 357q-18 26 -11.5 57.5t32.5 48.5q26 18 57 12t49 -33q134 -198 134 -442z" />
+<glyph unicode="" d="M1500 -13q0 -89 -63 -152.5t-153 -63.5t-153.5 63.5t-63.5 152.5q0 90 63.5 153.5t153.5 63.5t153 -63.5t63 -153.5zM1267 268q-115 -15 -192.5 -102.5t-77.5 -205.5q0 -74 33 -138q-146 -78 -379 -78q-109 0 -201 21t-153.5 54.5t-110.5 76.5t-76 85t-44.5 83 t-23.5 66.5t-6 39.5q0 19 4.5 42.5t18.5 56t36.5 58t64 43.5t94.5 18t94 -17.5t63 -41t35.5 -53t17.5 -49t4 -33.5q0 -34 -23 -81q28 -27 82 -42t93 -17l40 -1q115 0 190 51t75 133q0 26 -9 48.5t-31.5 44.5t-49.5 41t-74 44t-93.5 47.5t-119.5 56.5q-28 13 -43 20 q-116 55 -187 100t-122.5 102t-72 125.5t-20.5 162.5q0 78 20.5 150t66 137.5t112.5 114t166.5 77t221.5 28.5q120 0 220 -26t164.5 -67t109.5 -94t64 -105.5t19 -103.5q0 -46 -15 -82.5t-36.5 -58t-48.5 -36t-49 -19.5t-39 -5h-8h-32t-39 5t-44 14t-41 28t-37 46t-24 70.5 t-10 97.5q-15 16 -59 25.5t-81 10.5l-37 1q-68 0 -117.5 -31t-70.5 -70t-21 -76q0 -24 5 -43t24 -46t53 -51t97 -53.5t150 -58.5q76 -25 138.5 -53.5t109 -55.5t83 -59t60.5 -59.5t41 -62.5t26.5 -62t14.5 -63.5t6 -62t1 -62.5z" />
+<glyph unicode="" d="M704 352v576q0 14 -9 23t-23 9h-256q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h256q14 0 23 9t9 23zM1152 352v576q0 14 -9 23t-23 9h-256q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h256q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103 t-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="" 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 103zM768 96q148 0 273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273 t73 -273t198 -198t273 -73zM864 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-192zM480 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-192z" />
+<glyph unicode="" d="M1088 352v576q0 14 -9 23t-23 9h-576q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h576q14 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.5 t103 -385.5z" />
+<glyph unicode="" 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 103zM768 96q148 0 273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273 t73 -273t198 -198t273 -73zM480 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h576q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-576z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1757 128l35 -313q3 -28 -16 -50q-19 -21 -48 -21h-1664q-29 0 -48 21q-19 22 -16 50l35 313h1722zM1664 967l86 -775h-1708l86 775q3 24 21 40.5t43 16.5h256v-128q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5v128h384v-128q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5v128h256q25 0 43 -16.5t21 -40.5zM1280 1152v-256q0 -26 -19 -45t-45 -19t-45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-256q0 -26 -19 -45t-45 -19t-45 19t-19 45v256q0 159 112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="" horiz-adv-x="2048" d="M1920 768q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5h-15l-115 -662q-8 -46 -44 -76t-82 -30h-1280q-46 0 -82 30t-44 76l-115 662h-15q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5h1792zM485 -32q26 2 43.5 22.5t15.5 46.5l-32 416q-2 26 -22.5 43.5 t-46.5 15.5t-43.5 -22.5t-15.5 -46.5l32 -416q2 -25 20.5 -42t43.5 -17h5zM896 32v416q0 26 -19 45t-45 19t-45 -19t-19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45zM1280 32v416q0 26 -19 45t-45 19t-45 -19t-19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45zM1632 27l32 416 q2 26 -15.5 46.5t-43.5 22.5t-46.5 -15.5t-22.5 -43.5l-32 -416q-2 -26 15.5 -46.5t43.5 -22.5h5q25 0 43.5 17t20.5 42zM476 1244l-93 -412h-132l101 441q19 88 89 143.5t160 55.5h167q0 26 19 45t45 19h384q26 0 45 -19t19 -45h167q90 0 160 -55.5t89 -143.5l101 -441 h-132l-93 412q-11 44 -45.5 72t-79.5 28h-167q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45h-167q-45 0 -79.5 -28t-45.5 -72z" />
+<glyph unicode="" horiz-adv-x="1792" d="M991 512l64 256h-254l-64 -256h254zM1759 1016l-56 -224q-7 -24 -31 -24h-327l-64 -256h311q15 0 25 -12q10 -14 6 -28l-56 -224q-5 -24 -31 -24h-327l-81 -328q-7 -24 -31 -24h-224q-16 0 -26 12q-9 12 -6 28l78 312h-254l-81 -328q-7 -24 -31 -24h-225q-15 0 -25 12 q-9 12 -6 28l78 312h-311q-15 0 -25 12q-9 12 -6 28l56 224q7 24 31 24h327l64 256h-311q-15 0 -25 12q-10 14 -6 28l56 224q5 24 31 24h327l81 328q7 24 32 24h224q15 0 25 -12q9 -12 6 -28l-78 -312h254l81 328q7 24 32 24h224q15 0 25 -12q9 -12 6 -28l-78 -312h311 q15 0 25 -12q9 -12 6 -28z" />
+<glyph unicode="" d="M841 483l148 -148l-149 -149zM840 1094l149 -149l-148 -148zM710 -130l464 464l-306 306l306 306l-464 464v-611l-255 255l-93 -93l320 -321l-320 -321l93 -93l255 255v-611zM1429 640q0 -209 -32 -365.5t-87.5 -257t-140.5 -162.5t-181.5 -86.5t-219.5 -24.5 t-219.5 24.5t-181.5 86.5t-140.5 162.5t-87.5 257t-32 365.5t32 365.5t87.5 257t140.5 162.5t181.5 86.5t219.5 24.5t219.5 -24.5t181.5 -86.5t140.5 -162.5t87.5 -257t32 -365.5z" />
+<glyph unicode="" horiz-adv-x="1024" d="M596 113l173 172l-173 172v-344zM596 823l173 172l-173 172v-344zM628 640l356 -356l-539 -540v711l-297 -296l-108 108l372 373l-372 373l108 108l297 -296v711l539 -540z" />
+<glyph unicode="" d="M1280 256q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM512 1024q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1536 256q0 -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.5 t112.5 -271.5zM1440 1344q0 -20 -13 -38l-1056 -1408q-19 -26 -51 -26h-160q-26 0 -45 19t-19 45q0 20 13 38l1056 1408q19 26 51 26h160q26 0 45 -19t19 -45zM768 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="" horiz-adv-x="1792" />
+<glyph unicode="" horiz-adv-x="1792" />
+<glyph unicode="" horiz-adv-x="1792" />
+<glyph unicode="" horiz-adv-x="1792" />
+<glyph unicode="" horiz-adv-x="1792" />
+<glyph unicode="" horiz-adv-x="1792" />
+<glyph unicode="" horiz-adv-x="1792" />
+<glyph unicode="" horiz-adv-x="1792" />
+<glyph unicode="" horiz-adv-x="1792" />
<glyph unicode="" horiz-adv-x="1792" />
</font>
</defs></svg>
\ No newline at end of file
diff --git a/src/main/resources/fontawesome/fonts/fontawesome-webfont.ttf b/src/main/resources/fontawesome/fonts/fontawesome-webfont.ttf
index e89738d..26dea79 100644
--- a/src/main/resources/fontawesome/fonts/fontawesome-webfont.ttf
+++ b/src/main/resources/fontawesome/fonts/fontawesome-webfont.ttf
Binary files differ
diff --git a/src/main/resources/fontawesome/fonts/fontawesome-webfont.woff b/src/main/resources/fontawesome/fonts/fontawesome-webfont.woff
index 8c1748a..dc35ce3 100644
--- a/src/main/resources/fontawesome/fonts/fontawesome-webfont.woff
+++ b/src/main/resources/fontawesome/fonts/fontawesome-webfont.woff
Binary files differ
diff --git a/src/main/resources/fontawesome/fonts/fontawesome-webfont.woff2 b/src/main/resources/fontawesome/fonts/fontawesome-webfont.woff2
new file mode 100644
index 0000000..500e517
--- /dev/null
+++ b/src/main/resources/fontawesome/fonts/fontawesome-webfont.woff2
Binary files differ
diff --git a/src/main/resources/gitblit-editor.min.css b/src/main/resources/gitblit-editor.min.css
new file mode 100644
index 0000000..c89df03
--- /dev/null
+++ b/src/main/resources/gitblit-editor.min.css
@@ -0,0 +1 @@
+.ProseMirror-menubar{border-top-left-radius:inherit;border-top-right-radius:inherit;color:#666;padding:1px 6px;border-bottom:1px solid silver;background:#fff;z-index:10;box-sizing:border-box;overflow:visible}.ProseMirror-menubar.scrolling{top:47px}.ProseMirror-menuitem{display:inline-block;font-size:1.2em;text-align:center;min-width:30px;line-height:30px;box-sizing:border-box;margin:1px;border:1px solid #fff}.ProseMirror-menuitem:hover{border-radius:5px;background:#fcfcfc;border:1px solid #95a5a6;box-sizing:border-box}.ProseMirror-icon{line-height:inherit;vertical-align:middle}.ProseMirror-menubar .fa-header-x:after{font-family:Arial,Helvetica,sans-serif;font-size:65%;vertical-align:text-bottom;position:relative;top:2px}.fa-header-1:after{content:"1"}.fa-header-2:after{content:"2"}.fa-header-3:after{content:"3"}.forceHide{display:none!important}
diff --git a/src/main/resources/gitblit-editor.min.js b/src/main/resources/gitblit-editor.min.js
new file mode 100644
index 0000000..79af3c9
--- /dev/null
+++ b/src/main/resources/gitblit-editor.min.js
@@ -0,0 +1,10 @@
+!function e(t,n,r){function o(a,s){if(!n[a]){if(!t[a]){var u="function"==typeof require&&require;if(!s&&u)return u(a,!0);if(i)return i(a,!0);var l=new Error("Cannot find module '"+a+"'");throw l.code="MODULE_NOT_FOUND",l}var c=n[a]={exports:{}};t[a][0].call(c.exports,function(e){var n=t[a][1][e];return o(n?n:e)},c,c.exports,e,t,n,r)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;a<r.length;a++)o(r[a]);return o}({1:[function(e,t,n){function r(e,t,n){var r=document.createElement("div");r.className="ProseMirror-icon";var o=document.createElement("i");o.setAttribute("class","fa fa-fw "+n);var i=e.active(t);return(i||e.spec.invert)&&r.classList.add("ProseMirror-menu-active"),r.appendChild(o),r}function o(){return document.fullscreenElement||document.mozFullScreenElement||document.webkitFullscreenElement||document.msFullscreenElement}function i(e){return o()?(document.exitFullscreen?document.exitFullscreen():document.msExitFullscreen?document.msExitFullscreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.webkitExitFullscreen&&document.webkitExitFullscreen(),!0):(e.requestFullscreen?e.requestFullscreen():e.msRequestFullscreen?e.msRequestFullscreen():e.mozRequestFullScreen?e.mozRequestFullScreen():e.webkitRequestFullscreen&&e.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT),!1)}attachDocumentEditor=function(t,n){var a=e("./prosemirror/dist/edit");e("./prosemirror/dist/inputrules/autoinput"),e("./prosemirror/dist/menu/menubar"),e("./prosemirror/dist/markdown");var s=e("./prosemirror/dist/menu/menu"),u=document.querySelector("#editor");u.style.display="none";var l=new s.MenuCommandGroup("gitblitCommands"),c=new s.MenuCommandGroup("viewCommands"),p=new s.MenuCommandGroup("textCommands"),f=new s.MenuCommandGroup("insertCommands"),h=[l,c,p,s.inlineGroup,s.blockGroup,s.historyGroup,f];const d=Object.create(null);d.GitblitCommit={label:"GitblitCommit",run:function(){n.modal({show:!0}),t.value=m.getContent("markdown")},menu:{group:"gitblitCommands",rank:10,display:{render:function(e,t){return r(e,t,"fa-save")}}}},d.FullScreen={label:"Toggle Fullscreen",derive:"toggle",run:function(e){var t=window.scrollY,n=[document.querySelector("div.repositorynavbar"),document.querySelector("div.navbar"),document.querySelector("div.docnav")],r=n.reduce(function(e,t){return e+t.offsetHeight},0);n.forEach(function(e){e.classList.toggle("forceHide")}),i(document.documentElement)?r-=60:r=60,e.signal("commandsChanged"),setTimeout(function(){window.scrollTo(0,Math.max(0,t-r))},100)},menu:{group:"viewCommands",rank:11,display:{render:function(e,t){return r(e,t,"fa-arrows-alt")}}},active:function(e){return o()?!0:!1}},d.heading1={derive:"toggle",run:function(e){var t=e.selection,n=t.from,r=t.to,o={name:"make",level:"1"},i=e.doc.resolve(n).parent;return i&&i.hasMarkup(e.schema.nodes.heading,o)?e.tr.setBlockType(n,r,e.schema.defaultTextblockType(),{}).apply(e.apply.scroll):e.tr.setBlockType(n,r,e.schema.nodes.heading,o).apply(e.apply.scroll)},active:function(e){var t=e.doc.resolve(e.selection.from).parent;return t&&t.hasMarkup(e.schema.nodes.heading,{name:"make",level:"1"})?!0:!1},menu:{group:"textCommands",rank:1,display:{render:function(e,t){return r(e,t,"fa-header fa-header-x fa-header-1")}}},select:function(){return!0}},d.heading2={derive:"toggle",run:function(e){var t=e.selection,n=t.from,r=t.to,o={name:"make",level:"2"},i=e.doc.resolve(n).parent;return i&&i.hasMarkup(e.schema.nodes.heading,o)?e.tr.setBlockType(n,r,e.schema.defaultTextblockType(),{}).apply(e.apply.scroll):e.tr.setBlockType(n,r,e.schema.nodes.heading,o).apply(e.apply.scroll)},active:function(e){var t=e.doc.resolve(e.selection.from).parent;return t&&t.hasMarkup(e.schema.nodes.heading,{name:"make",level:"2"})?!0:!1},menu:{group:"textCommands",rank:2,display:{render:function(e,t){return r(e,t,"fa-header fa-header-x fa-header-2")}}},select:function(){return!0}},d.heading3={derive:"toggle",run:function(e){var t=e.selection,n=t.from,r=t.to,o={name:"make",level:"3"},i=e.doc.resolve(n).parent;return i&&i.hasMarkup(e.schema.nodes.heading,o)?e.tr.setBlockType(n,r,e.schema.defaultTextblockType(),{}).apply(e.apply.scroll):e.tr.setBlockType(n,r,e.schema.nodes.heading,o).apply(e.apply.scroll)},active:function(e){var t=e.doc.resolve(e.selection.from).parent;return t&&t.hasMarkup(e.schema.nodes.heading,{name:"make",level:"3"})?!0:!1},menu:{group:"textCommands",rank:3,display:{render:function(e,t){return r(e,t,"fa-header fa-header-x fa-header-3")}}},select:function(){return!0}},d["strong:toggle"]={menu:{group:"textCommands",rank:4,display:{render:function(e,t){return r(e,t,"fa-bold")}}},select:function(){return!0}},d["em:toggle"]={menu:{group:"textCommands",rank:5,display:{render:function(e,t){return r(e,t,"fa-italic")}}},select:function(){return!0}},d["code:toggle"]={menu:{group:"textCommands",rank:6,display:{render:function(e,t){return r(e,t,"fa-code")}}},select:function(){return!0}},d["image:insert"]={menu:{group:"insertCommands",rank:1,display:{render:function(e,t){return r(e,t,"fa-picture-o")}}}},d.selectParentNode={menu:{group:"insertCommands",rank:10,display:{render:function(e,t){return r(e,t,"fa-arrow-circle-o-left")}}}};var m=window.pm=new a.ProseMirror({place:document.querySelector("#visualEditor"),autoInput:!0,doc:u.value,menuBar:{"float":!0,content:h},commands:a.CommandSet["default"].update(d),docFormat:"markdown"}),v=document.querySelector(".ProseMirror").offsetTop,g=!1;window.addEventListener("scroll",function(){var e=window.scrollY;g||window.requestAnimationFrame(function(){!o()&&e>v?document.querySelector(".ProseMirror-menubar").classList.add("scrolling"):document.querySelector(".ProseMirror-menubar").classList.remove("scrolling"),g=!1}),g=!0})},commitChanges=function(){document.querySelector("form#documentEditor").submit()}},{"./prosemirror/dist/edit":13,"./prosemirror/dist/inputrules/autoinput":27,"./prosemirror/dist/markdown":30,"./prosemirror/dist/menu/menu":33,"./prosemirror/dist/menu/menubar":34}],2:[function(e,t,n){(function(e){!function(r){function o(e){throw new RangeError(j[e])}function i(e,t){for(var n=e.length,r=[];n--;)r[n]=t(e[n]);return r}function a(e,t){var n=e.split("@"),r="";n.length>1&&(r=n[0]+"@",e=n[1]),e=e.replace(P,".");var o=e.split("."),a=i(o,t).join(".");return r+a}function s(e){for(var t,n,r=[],o=0,i=e.length;i>o;)t=e.charCodeAt(o++),t>=55296&&56319>=t&&i>o?(n=e.charCodeAt(o++),56320==(64512&n)?r.push(((1023&t)<<10)+(1023&n)+65536):(r.push(t),o--)):r.push(t);return r}function u(e){return i(e,function(e){var t="";return e>65535&&(e-=65536,t+=R(e>>>10&1023|55296),e=56320|1023&e),t+=R(e)}).join("")}function l(e){return 10>e-48?e-22:26>e-65?e-65:26>e-97?e-97:_}function c(e,t){return e+22+75*(26>e)-((0!=t)<<5)}function p(e,t,n){var r=0;for(e=n?z(e/M):e>>1,e+=z(e/t);e>q*C>>1;r+=_)e=z(e/q);return z(r+(q+1)*e/(e+S))}function f(e){var t,n,r,i,a,s,c,f,h,d,m=[],v=e.length,g=0,y=A,k=O;for(n=e.lastIndexOf(T),0>n&&(n=0),r=0;n>r;++r)e.charCodeAt(r)>=128&&o("not-basic"),m.push(e.charCodeAt(r));for(i=n>0?n+1:0;v>i;){for(a=g,s=1,c=_;i>=v&&o("invalid-input"),f=l(e.charCodeAt(i++)),(f>=_||f>z((w-g)/s))&&o("overflow"),g+=f*s,h=k>=c?x:c>=k+C?C:c-k,!(h>f);c+=_)d=_-h,s>z(w/d)&&o("overflow"),s*=d;t=m.length+1,k=p(g-a,t,0==a),z(g/t)>w-y&&o("overflow"),y+=z(g/t),g%=t,m.splice(g++,0,y)}return u(m)}function h(e){var t,n,r,i,a,u,l,f,h,d,m,v,g,y,k,b=[];for(e=s(e),v=e.length,t=A,n=0,a=O,u=0;v>u;++u)m=e[u],128>m&&b.push(R(m));for(r=i=b.length,i&&b.push(T);v>r;){for(l=w,u=0;v>u;++u)m=e[u],m>=t&&l>m&&(l=m);for(g=r+1,l-t>z((w-n)/g)&&o("overflow"),n+=(l-t)*g,t=l,u=0;v>u;++u)if(m=e[u],t>m&&++n>w&&o("overflow"),m==t){for(f=n,h=_;d=a>=h?x:h>=a+C?C:h-a,!(d>f);h+=_)k=f-d,y=_-d,b.push(R(c(d+k%y,0))),f=z(k/y);b.push(R(c(f,0))),a=p(n,g,r==i),n=0,++r}++n,++t}return b.join("")}function d(e){return a(e,function(e){return E.test(e)?f(e.slice(4).toLowerCase()):e})}function m(e){return a(e,function(e){return D.test(e)?"xn--"+h(e):e})}var v="object"==typeof n&&n&&!n.nodeType&&n,g="object"==typeof t&&t&&!t.nodeType&&t,y="object"==typeof e&&e;(y.global===y||y.window===y||y.self===y)&&(r=y);var k,b,w=2147483647,_=36,x=1,C=26,S=38,M=700,O=72,A=128,T="-",E=/^xn--/,D=/[^\x20-\x7E]/,P=/[\x2E\u3002\uFF0E\uFF61]/g,j={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},q=_-x,z=Math.floor,R=String.fromCharCode;if(k={version:"1.3.2",ucs2:{decode:s,encode:u},decode:f,encode:h,toASCII:m,toUnicode:d},"function"==typeof define&&"object"==typeof define.amd&&define.amd)define("punycode",function(){return k});else if(v&&g)if(t.exports==v)g.exports=k;else for(b in k)k.hasOwnProperty(b)&&(v[b]=k[b]);else r.punycode=k}(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],3:[function(e,t,n){"use strict";function r(e,t){var n=document.createElement(e);if(t)for(var r in t)"style"==r?n.style.cssText=t[r]:null!=t[r]&&n.setAttribute(r,t[r]);for(var i=arguments.length,a=Array(i>2?i-2:0),s=2;i>s;s++)a[s-2]=arguments[s];for(var u=0;u<a.length;u++)o(a[u],n);return n}function o(e,t){if("string"==typeof e&&(e=document.createTextNode(e)),Array.isArray(e))for(var n=0;n<e.length;n++)o(e[n],t);else t.appendChild(e)}function i(e){return c?c(e):setTimeout(e,10)}function a(e){return c?p(e):void clearTimeout(e)}function s(e,t){return 1!=t.nodeType&&(t=t.parentNode),t&&e.contains(t)}function u(e){m?m.textContent+=e:d+=e}function l(){m||(m=document.createElement("style"),m.textContent="/* ProseMirror CSS */\n"+d,document.head.insertBefore(m,document.head.firstChild))}Object.defineProperty(n,"__esModule",{value:!0}),n.elt=r,n.requestAnimationFrame=i,n.cancelAnimationFrame=a,n.contains=s,n.insertCSS=u,n.ensureCSSAdded=l;var c=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame,p=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame,f=/MSIE \d/.test(navigator.userAgent),h=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent),d=(n.browser={mac:/Mac/.test(navigator.platform),ie_upto10:f,ie_11up:h,ie:f||h,gecko:/gecko\/\d/i.test(navigator.userAgent),ios:/AppleWebKit/.test(navigator.userAgent)&&/Mobile\/\w+/.test(navigator.userAgent)},""),m=null},{}],4:[function(e,t,n){"use strict";function r(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function o(e,t){var n=e.doc.resolve(t),o=n.nodeBefore,i=n.nodeAfter;if(o.type.canContainContent(i.type)){var a=e.tr.join(t);if(a.steps.length&&0==o.content.size&&!o.sameMarkup(i)&&a.setNodeType(t-o.nodeSize,i.type,i.attrs),a.apply(e.apply.scroll)!==!1)return}var s=void 0;if(i.isTextblock&&(s=o.type.findConnection(i.type))){var a=e.tr,u=t+i.nodeSize;if(a.step("ancestor",t,u,{types:[o.type].concat(r(s)),attrs:[o.attrs].concat(r(s.map(function(){return null})))}),a.join(u+2*s.length+2,1,!0),a.join(t),a.apply(e.apply.scroll)!==!1)return}var l=(0,f.findSelectionFrom)(e.doc,t,1);return e.tr.lift(l.from,l.to,!0).apply(e.apply.scroll)}function i(e,t,n){if("char"!=n&&"word"!=n)throw new RangeError("Unknown motion unit: "+n);for(var r=e.resolve(t),o=r.parent,i=r.parentOffset,a=null,s=0;;){if(0==i)return t;var u=o.childBefore(i),l=u.offset,c=u.node;if(!c)return t;if(!c.isText)return a?t:t-1;if("char"==n)for(var f=i-l;f>0;f--){if(!(0,p.isExtendingChar)(c.text.charAt(f-1)))return t-1;i--,t--}else if("word"==n)for(var f=i-l;f>0;f--){var h=(0,p.charCategory)(c.text.charAt(f-1));if(null==a||1==s&&"space"==a)a=h;else if(a!=h)return t;i--,t--,s++}}}function a(e,t,n){if("char"!=n&&"word"!=n)throw new RangeError("Unknown motion unit: "+n);for(var r=e.resolve(t),o=r.parent,i=r.parentOffset,a=null,s=0;;){if(i==o.content.size)return t;var u=o.childAfter(i),l=u.offset,c=u.node;if(!c)return t;if(!c.isText)return a?t:t+1;if("char"==n)for(var f=i-l;f<c.text.length;f++){if(!(0,p.isExtendingChar)(c.text.charAt(f+1)))return t+1;i++,t++}else if("word"==n)for(var f=i-l;f<c.text.length;f++){var h=(0,p.charCategory)(c.text.charAt(f));if(null==a||1==s&&"space"==a)a=h;else if(a!=h)return t;i++,t++,s++}}}function s(e){var t=e.selection,n=t.node,r=t.from;return n?(0,c.joinable)(e.doc,r)?r:null:(0,c.joinPoint)(e.doc,r,-1)}function u(e){var t=e.selection,n=t.node,r=t.to;return n?(0,c.joinable)(e.doc,r)?r:null:(0,c.joinPoint)(e.doc,r,1)}function l(e){var t=e.selection;if(t.node){var n=e.doc.resolve(t.from);return!!n.depth&&n.before(n.depth)}var r=e.doc.resolve(t.head),o=r.sameDepth(e.doc.resolve(t.anchor));return 0==o?!1:r.before(o)}Object.defineProperty(n,"__esModule",{value:!0}),n.baseCommands=void 0;var c=e("../transform"),p=e("./char"),f=e("./selection"),h=n.baseCommands=Object.create(null);h.deleteSelection={label:"Delete the selection",run:function(e){return e.tr.replaceSelection().apply(e.apply.scroll)},keys:{all:["Backspace(10)","Delete(10)","Mod-Backspace(10)","Mod-Delete(10)"],mac:["Ctrl-H(10)","Alt-Backspace(10)","Ctrl-D(10)","Ctrl-Alt-Backspace(10)","Alt-Delete(10)","Alt-D(10)"]}},h.joinBackward={label:"Join with the block above",run:function(e){var t=e.selection,n=t.head,r=t.empty;if(!r)return!1;var i=e.doc.resolve(n);if(i.parentOffset>0)return!1;for(var a=void 0,s=void 0,u=i.depth-1;!a&&u>=0;u--)i.index(u)>0&&(s=i.before(u+1),a=i.node(u).child(i.index(u)-1));if(!a)return e.tr.lift(n,n,!0).apply(e.apply.scroll);if(null==a.type.contains&&a.type.selectable&&0==i.parent.content.size){var l=e.tr["delete"](s,s+i.parent.nodeSize).apply(e.apply.scroll);return e.setNodeSelection(s-a.nodeSize),l}return null==a.type.contains?e.tr["delete"](s-a.nodeSize,s).apply(e.apply.scroll):o(e,s)},keys:["Backspace(30)","Mod-Backspace(30)"]},h.deleteCharBefore={label:"Delete a character before the cursor",run:function(e){var t=e.selection,n=t.head,r=t.empty;if(!r||0==e.doc.resolve(n).parentOffset)return!1;var o=i(e.doc,n,"char");return e.tr["delete"](o,n).apply(e.apply.scroll)},keys:{all:["Backspace(60)"],mac:["Ctrl-H(40)"]}},h.deleteWordBefore={label:"Delete the word before the cursor",run:function(e){var t=e.selection,n=t.head,r=t.empty;if(!r||0==e.doc.resolve(n).parentOffset)return!1;var o=i(e.doc,n,"word");return e.tr["delete"](o,n).apply(e.apply.scroll)},keys:{all:["Mod-Backspace(40)"],mac:["Alt-Backspace(40)"]}},h.joinForward={label:"Join with the block below",run:function(e){var t=e.selection,n=t.head,r=t.empty,i=void 0;if(!r||(i=e.doc.resolve(n)).parentOffset<i.parent.content.size)return!1;for(var a=void 0,s=void 0,u=i.depth-1;!a&&u>=0;u--){var l=i.node(u);i.index(u)+1<l.childCount&&(a=l.child(i.index(u)+1),s=i.after(u+1))}return a?null==a.type.contains?e.tr["delete"](s,s+a.nodeSize).apply(e.apply.scroll):o(e,s):!1},keys:["Delete(30)","Mod-Delete(30)"]},h.deleteCharAfter={label:"Delete a character after the cursor",run:function(e){var t=e.selection,n=t.head,r=t.empty,o=void 0;if(!r||(o=e.doc.resolve(n)).parentOffset==o.parent.content.size)return!1;var i=a(e.doc,n,"char");return e.tr["delete"](n,i).apply(e.apply.scroll)},keys:{all:["Delete(60)"],mac:["Ctrl-D(60)"]}},h.deleteWordAfter={label:"Delete a word after the cursor",run:function(e){var t=e.selection,n=t.head,r=t.empty,o=void 0;if(!r||(o=e.doc.resolve(n)).parentOffset==o.parent.content.size)return!1;var i=a(e.doc,n,"word");return e.tr["delete"](n,i).apply(e.apply.scroll)},keys:{all:["Mod-Delete(40)"],mac:["Ctrl-Alt-Backspace(40)","Alt-Delete(40)","Alt-D(40)"]}},h.joinUp={label:"Join with above block",run:function(e){var t=s(e),n=void 0;return t?(e.selection.node&&(n=t-e.doc.resolve(t).nodeBefore.nodeSize),e.tr.join(t).apply(),void(null!=n&&e.setNodeSelection(n))):!1},select:function(e){return s(e)},menu:{group:"block",rank:80,display:{type:"icon",width:800,height:900,path:"M0 75h800v125h-800z M0 825h800v-125h-800z M250 400h100v-100h100v100h100v100h-100v100h-100v-100h-100z"}},keys:["Alt-Up"]},h.joinDown={label:"Join with below block",run:function(e){var t=e.selection.node,n=e.selection.from,r=u(e);return r?(e.tr.join(r).apply(),void(t&&e.setNodeSelection(n))):!1},select:function(e){return u(e)},keys:["Alt-Down"]},h.lift={label:"Lift out of enclosing block",run:function(e){var t=e.selection,n=t.from,r=t.to;return e.tr.lift(n,r,!0).apply(e.apply.scroll)},select:function(e){var t=e.selection,n=t.from,r=t.to;return(0,c.canLift)(e.doc,n,r)},menu:{group:"block",rank:75,display:{type:"icon",width:1024,height:1024,path:"M219 310v329q0 7-5 12t-12 5q-8 0-13-5l-164-164q-5-5-5-13t5-13l164-164q5-5 13-5 7 0 12 5t5 12zM1024 749v109q0 7-5 12t-12 5h-987q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h987q7 0 12 5t5 12zM1024 530v109q0 7-5 12t-12 5h-621q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h621q7 0 12 5t5 12zM1024 310v109q0 7-5 12t-12 5h-621q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h621q7 0 12 5t5 12zM1024 91v109q0 7-5 12t-12 5h-987q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h987q7 0 12 5t5 12z"}},keys:["Mod-["]},h.newlineInCode={label:"Insert newline",run:function(e){var t=e.selection,n=t.from,r=t.to,o=t.node;if(o)return!1;var i=e.doc.resolve(n);return!i.parent.type.isCode||r>=i.end(i.depth)?!1:e.tr.typeText("\n").apply(e.apply.scroll)},keys:["Enter(10)"]},h.createParagraphNear={label:"Create a paragraph near the selected block",run:function(e){var t=e.selection,n=t.from,r=t.to,o=t.node;if(!o||!o.isBlock)return!1;var i=e.doc.resolve(n).parentOffset?r:n;e.tr.insert(i,e.schema.defaultTextblockType().create()).apply(e.apply.scroll),e.setTextSelection(i+1)},keys:["Enter(20)"]},h.liftEmptyBlock={label:"Move current block up",run:function(e){var t=e.selection,n=t.head,r=t.empty,o=void 0;return!r||(o=e.doc.resolve(n)).parentOffset>0||o.parent.content.size?!1:o.depth>1&&o.index(o.depth-1)>0&&o.index(o.depth-1)<o.node(o.depth-1).childCount-1&&e.tr.split(o.before(o.depth)).apply()!==!1?void 0:e.tr.lift(n,n,!0).apply(e.apply.scroll)},keys:["Enter(30)"]},h.splitBlock={label:"Split the current block",run:function(e){var t=e.selection,n=t.from,r=t.to,o=t.node,i=e.doc.resolve(n);if(o&&o.isBlock)return i.parentOffset?e.tr.split(n).apply(e.apply.scroll):!1;var a=e.doc.resolve(r),s=a.parentOffset==a.parent.content.size,u=e.schema.defaultTextblockType(),l=s?u:null,c=e.tr["delete"](n,r).split(n,1,l);return s||i.parentOffset||i.parent.type==u||c.setNodeType(i.before(i.depth),u),c.apply(e.apply.scroll)},keys:["Enter(60)"]},h.selectParentNode={label:"Select parent node",run:function(e){var t=l(e);return t===!1?!1:void e.setNodeSelection(t)},select:function(e){return l(e)},menu:{group:"block",rank:90,display:{type:"icon",text:"⬚",style:"font-weight: bold"}},keys:["Esc"]},h.undo={label:"Undo last change",run:function(e){return e.scrollIntoView(),e.history.undo()},select:function(e){return e.history.undoDepth>0},menu:{group:"history",rank:10,display:{type:"icon",width:1024,height:1024,path:"M761 1024c113-206 132-520-313-509v253l-384-384 384-384v248c534-13 594 472 313 775z"}},keys:["Mod-Z"]},h.redo={label:"Redo last undone change",run:function(e){return e.scrollIntoView(),e.history.redo()},select:function(e){return e.history.redoDepth>0},menu:{group:"history",rank:20,display:{type:"icon",width:1024,height:1024,path:"M576 248v-248l384 384-384 384v-253c-446-10-427 303-313 509-280-303-221-789 313-775z"}},keys:["Mod-Y","Shift-Mod-Z"]}},{"../transform":45,"./char":6,"./selection":19}],5:[function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(){}function i(e,t){var n=e.selection,r=n.from,o=n.to,i=n.node,a=e.doc.resolve(t>0?o:r);return(0,f.findSelectionFrom)(e.doc,i&&i.isBlock?a.pos:t>0?a.after(a.depth):a.before(a.depth),t)}function a(e,t){var n=e.selection,r=n.empty,o=n.node,a=n.from,s=n.to;if(!r&&!o)return!1;if(o&&o.isInline)return e.setTextSelection(t>0?s:a),!0;if(!o){var u=e.doc.resolve(a),l=t>0?u.parent.childAfter(u.parentOffset):u.parent.childBefore(u.parentOffset),c=l.node,p=l.offset;if(c)return c.type.selectable&&p==u.parentOffset-(t>0?0:c.nodeSize)?(e.setNodeSelection(0>t?a-c.nodeSize:a),!0):!1}var h=i(e,t);return h&&(h instanceof f.NodeSelection||o)?(e.setSelection(h),!0):!1}function s(e){return function(t){var n=a(t,e);return n&&t.scrollIntoView(),n}}function u(e,t){var n=e.selection,r=n.empty,o=n.node,a=n.from,s=n.to;if(!r&&!o)return!1;var u=!0;if((!o||o.isInline)&&(e.flush(),u=(0,f.verticalMotionLeavesTextblock)(e,t>0?s:a,t)),u){var l=i(e,t);if(l&&l instanceof f.NodeSelection)return e.setSelection(l),!0}if(!o||o.isInline)return!1;var c=(0,f.findSelectionFrom)(e.doc,0>t?a:s,t);return c&&e.setSelection(c),!0}function l(e){return function(t){var n=u(t,e);return n!==!1&&t.scrollIntoView(),n}}Object.defineProperty(n,"__esModule",{value:!0}),n.captureKeys=void 0;var c=e("browserkeymap"),p=r(c),f=e("./selection"),h=e("../dom"),d={Esc:o,Enter:o,"Ctrl-Enter":o,"Mod-Enter":o,"Shift-Enter":o,Backspace:o,Delete:o,"Mod-B":o,"Mod-I":o,"Mod-Backspace":o,"Mod-Delete":o,"Shift-Backspace":o,"Shift-Delete":o,"Shift-Mod-Backspace":o,"Shift-Mod-Delete":o,"Mod-Z":o,"Mod-Y":o,"Shift-Mod-Z":o,"Ctrl-D":o,"Ctrl-H":o,"Ctrl-Alt-Backspace":o,"Alt-D":o,"Alt-Delete":o,"Alt-Backspace":o,Left:s(-1),"Mod-Left":s(-1),Right:s(1),"Mod-Right":s(1),Up:l(-1),Down:l(1)};h.browser.mac&&(d["Alt-Left"]=s(-1),d["Alt-Right"]=s(1),d["Ctrl-Backspace"]=d["Ctrl-Delete"]=o);n.captureKeys=new p["default"](d)},{"../dom":3,"./selection":19,browserkeymap:60}],6:[function(e,t,n){"use strict";function r(e){return/\w/.test(e)||i(e)||e>""&&(e.toUpperCase()!=e.toLowerCase()||a.test(e))}function o(e){return/\s/.test(e)?"space":r(e)?"word":"other"}function i(e){return e.charCodeAt(0)>=768&&s.test(e)}Object.defineProperty(n,"__esModule",{value:!0}),n.isWordChar=r,n.charCategory=o,n.isExtendingChar=i;var a=/[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/,s=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/},{}],7:[function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t,n){if(!t.derive)return t;var r="object"==b(t.derive)?t.derive:{},o=r.name||n,i=e.constructor.derivableCommands[o];if(!i)throw new RangeError("Don't know how to derive command "+o);var a=i.call(e,r);for(var s in t)"derive"!=s&&(a[s]=t[s]);return a}function s(e){function t(e,t){for(var r=0;r<t.length;r++){var o=/^(.+?)(?:\((\d+)\))?$/.exec(t[r]),i=k(o,3),a=(i[0],i[1]),s=i[2],u=void 0===s?50:s;(0,A["default"])(n[a]||(n[a]=[]),{command:e,rank:u},function(e,t){return e.rank-t.rank})}}var n={},r=M.browser.mac?"mac":"pc";for(var o in e.commands){var i=e.commands[o],a=i.spec.keys;a&&(Array.isArray(a)?t(i,a):(a.all&&t(i,a.all),a[r]&&t(i,a[r])))}for(var s in n)n[s]=n[s].map(function(e){return e.command.name});return new x["default"](n)}function u(e,t){e.signal("commandsChanging"),e.commands=t.derive(e.schema),e.input.baseKeymap=s(e),e.commandKeys=Object.create(null),e.signal("commandsChanged")}function l(e,t){var n=e.selection;return n.empty?t.isInSet(e.activeMarks()):e.doc.rangeHasMark(n.from,n.to,t)}function c(e,t){var n=e.selection,r=n.from,o=n.to,i=n.empty;if(i)return!t.isInSet(e.activeMarks())&&e.doc.resolve(r).parent.type.canContainMark(t);var a=!1;return e.doc.nodesBetween(r,o,function(e){return a||e.isTextblock&&!e.type.canContainMark(t)?!1:void(e.isInline&&!t.isInSet(e.marks)&&(a=!0))}),a}function p(e,t){var n=e.selection,r=n.from,o=n.to,i=!1;return e.doc.nodesBetween(r,o,function(e){return e.isTextblock?(e.type.canContainMark(t)&&(i=!0),!1):void 0}),i}function f(e,t,n){var r=e.selection,o=r.from,i=r.to,a=r.empty,s=void 0,u=void 0;if(a)s=u=t.isInSet(e.activeMarks());else{var l=e.doc.resolve(o).nodeAfter;s=l?t.isInSet(l.marks):null,u=t.isInSet(e.doc.marksAt(i))}return s&&u&&s.attrs[n]==u.attrs[n]?s.attrs[n]:void 0}function h(e,t,n){var r=e.selection.node;return r&&r.type==t?r.attrs[n]:void 0}function d(e,t){return t&&t.map(function(t){var n=e.attrs[t.attr],r={type:"text","default":n["default"],prefill:e instanceof C.NodeType?function(e){return h(e,this,t.attr)}:function(e){return f(e,this,t.attr)}};for(var o in t)r[o]=t[o];return r})}function m(e,t){var n=e.attrs;return e.params&&!function(){var r=Object.create(null);if(n)for(var o in n)r[o]=n[o];e.params.forEach(function(e,n){return r[e.attr]=t[n]}),n=r}(),n}function v(e,t,n,r){var o=e.resolve(t);return o.sameParent(e.resolve(n))&&o.depth>=2&&0==o.index(o.depth-1)&&r.canContain(o.node(o.depth-1))}function g(e,t,n,r,o){var i=!1;return o||(o={}),e.nodesBetween(t,n||t,function(e){return e.isTextblock?(e.hasMarkup(r,o)&&(i=!0),!1):void 0}),i}function y(e,t,n){var r=e.selection,o=r.from,i=r.to,a=r.node;if(!a||a.isInline){var s=e.doc.resolve(o);if(!s.sameParent(e.doc.resolve(i)))return!1;a=s.parent}else if(!a.isTextblock)return!1;return a.hasMarkup(t,n)}var k=function(){function e(e,t){var n=[],r=!0,o=!1,i=void 0;try{for(var a,s=e[Symbol.iterator]();!(r=(a=s.next()).done)&&(n.push(a.value),!t||n.length!==t);r=!0);}catch(u){o=!0,i=u}finally{try{!r&&s["return"]&&s["return"]()}finally{if(o)throw i}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),b="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e},w=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.CommandSet=n.Command=void 0,n.updateCommands=u,n.selectedNodeAttr=h;var _=e("browserkeymap"),x=r(_),C=e("../model"),S=e("../transform"),M=e("../dom"),O=e("../util/sortedinsert"),A=r(O),T=e("../util/obj"),E=e("./base_commands"),D=n.Command=function(){function e(t,n,r){if(i(this,e),this.name=r,!this.name)throw new RangeError("Trying to define a command without a name");this.spec=t,this.self=n}return w(e,[{key:"exec",value:function(e,t){var n=this.spec.run;if(t){if(this.params.length!=(t?t.length:0))throw new RangeError("Invalid amount of parameters for command "+this.name);return n.call.apply(n,[this.self,e].concat(o(t)))}return this.params.length?new e.options.commandParamPrompt(e,this).open():n.call(this.self,e)}},{key:"select",value:function(e){var t=this.spec.select;return t?t.call(this.self,e):!0}},{key:"active",value:function(e){var t=this.spec.active;return t?t.call(this.self,e):!1}},{key:"params",get:function(){return this.spec.params||P}},{key:"label",get:function(){return this.spec.label||this.name}}]),e}(),P=[],j=function(){function e(t,n){i(this,e),this.base=t,this.op=n}return w(e,[{key:"add",value:function(t,n){return new e(this,function(e,r){function o(t,r,o){if(!n||n(t,r)){if(e[t])throw new RangeError("Duplicate definition of command "+t);e[t]=new D(r,o,t)}}if("schema"===t)r.registry("command",function(e,t,n,r){o(r+":"+e,a(n,t,e),n)});else for(var i in t)o(i,t[i])})}},{key:"update",value:function(t){return new e(this,function(e){for(var n in t){var r=t[n];if(r)if(r.run)e[n]=new D(r,null,n);else{var o=e[n];o&&(e[n]=new D((0,T.copyObj)(r,(0,T.copyObj)(o.spec)),o.self,n))}else delete e[n]}})}},{key:"derive",value:function(e){var t=this.base?this.base.derive(e):Object.create(null);return this.op(t,e),t}}]),e}();n.CommandSet=j,j.empty=new j(null,function(){return null}),j["default"]=j.empty.add("schema").add(E.baseCommands),C.NodeType.derivableCommands=Object.create(null),C.MarkType.derivableCommands=Object.create(null),C.MarkType.derivableCommands.set=function(e){return{run:function(t){for(var n=arguments.length,r=Array(n>1?n-1:0),o=1;n>o;o++)r[o-1]=arguments[o];t.setMark(this,!0,m(e,r))},select:function(t){return e.inverseSelect?p(t,this)&&!l(t,this):c(t,this)},params:d(this,e.params)}},C.MarkType.derivableCommands.unset=function(){return{run:function(e){e.setMark(this,!1)},select:function(e){return l(e,this)}}},C.MarkType.derivableCommands.toggle=function(){return{run:function(e){e.setMark(this,null)},active:function(e){return l(e,this)},select:function(e){return p(e,this)}}},C.NodeType.derivableCommands.wrap=function(e){return{run:function(t){var n=t.selection,r=n.from,o=n.to,i=n.head,a=!1,s=t.doc.resolve(r);if(e.list&&i&&v(t.doc,r,o,this)){if(0==s.index(s.depth-2))return!1;a=!0}for(var u=arguments.length,l=Array(u>1?u-1:0),c=1;u>c;c++)l[c-1]=arguments[c];var p=t.tr.wrap(r,o,this,m(e,l));return a&&p.join(s.before(s.depth-1)),p.apply(t.apply.scroll)},select:function(t){var n=t.selection,r=n.from,o=n.to,i=n.head,a=void 0;return e.list&&i&&v(t.doc,r,o,this)&&0==(a=t.doc.resolve(r)).index(a.depth-2)?!1:(0,S.canWrap)(t.doc,r,o,this,e.attrs)},params:d(this,e.params)}},C.NodeType.derivableCommands.make=function(e){return{run:function(t){var n=t.selection,r=n.from,o=n.to;return t.tr.setBlockType(r,o,this,e.attrs).apply(t.apply.scroll)},select:function(t){var n=t.selection,r=n.from,o=n.to,i=n.node;return i?i.isTextblock&&!i.hasMarkup(this,e.attrs):!g(t.doc,r,o,this,e.attrs)},active:function(t){return y(t,this,e.attrs)}}},C.NodeType.derivableCommands.insert=function(e){return{run:function(t){for(var n=arguments.length,r=Array(n>1?n-1:0),o=1;n>o;o++)r[o-1]=arguments[o];return t.tr.replaceSelection(this.create(m(e,r))).apply(t.apply.scroll)},select:this.isInline?function(e){return e.doc.resolve(e.selection.from).parent.type.canContainType(this)}:null,params:d(this,e.params)}}},{"../dom":3,"../model":38,"../transform":45,"../util/obj":58,"../util/sortedinsert":59,"./base_commands":4,browserkeymap:60}],8:[function(e,t,n){"use strict";var r=e("../dom");(0,r.insertCSS)('\n\n.ProseMirror {\n border: 1px solid silver;\n position: relative;\n}\n\n.ProseMirror-content {\n padding: 4px 8px 4px 14px;\n white-space: pre-wrap;\n line-height: 1.2;\n}\n\n.ProseMirror-drop-target {\n position: absolute;\n width: 1px;\n background: #666;\n}\n\n.ProseMirror-content ul.tight p, .ProseMirror-content ol.tight p {\n margin: 0;\n}\n\n.ProseMirror-content ul, .ProseMirror-content ol {\n padding-left: 30px;\n cursor: default;\n}\n\n.ProseMirror-content blockquote {\n padding-left: 1em;\n border-left: 3px solid #eee;\n margin-left: 0; margin-right: 0;\n}\n\n.ProseMirror-content pre {\n white-space: pre-wrap;\n}\n\n.ProseMirror-selectednode {\n outline: 2px solid #8cf;\n}\n\n.ProseMirror-nodeselection *::selection { background: transparent; }\n.ProseMirror-nodeselection *::-moz-selection { background: transparent; }\n\n.ProseMirror-content p:first-child,\n.ProseMirror-content h1:first-child,\n.ProseMirror-content h2:first-child,\n.ProseMirror-content h3:first-child,\n.ProseMirror-content h4:first-child,\n.ProseMirror-content h5:first-child,\n.ProseMirror-content h6:first-child {\n margin-top: .3em;\n}\n\n/* Add space around the hr to make clicking it easier */\n\n.ProseMirror-content hr {\n position: relative;\n height: 6px;\n border: none;\n}\n\n.ProseMirror-content hr:after {\n content: "";\n position: absolute;\n left: 10px;\n right: 10px;\n top: 2px;\n border-top: 2px solid silver;\n}\n\n.ProseMirror-content img {\n cursor: default;\n}\n\n/* Make sure li selections wrap around markers */\n\n.ProseMirror-content li {\n position: relative;\n pointer-events: none; /* Don\'t do weird stuff with marker clicks */\n}\n.ProseMirror-content li > * {\n pointer-events: auto;\n}\n\nli.ProseMirror-selectednode {\n outline: none;\n}\n\nli.ProseMirror-selectednode:after {\n content: "";\n position: absolute;\n left: -32px;\n right: -2px; top: -2px; bottom: -2px;\n border: 2px solid #8cf;\n pointer-events: none;\n}\n\n');
+},{"../dom":3}],9:[function(e,t,n){"use strict";function r(e){return e.ensureOperation({readSelection:!1}),c(e,u(e))}function o(e,t){return c(e,l(e,t))}function i(e,t,n){for(var r=(0,y.DOMFromPos)(e,t,!0),o=r.node,i=r.offset,a=(0,y.DOMFromPos)(e,n,!0).offset;i;){var s=o.childNodes[i-1];if(1==s.nodeType&&s.hasAttribute("pm-offset"))break;--i}for(;a<o.childNodes.length;){var u=o.childNodes[a];if(1==u.nodeType&&u.hasAttribute("pm-offset"))break;++a}return(0,m.fromDOM)(e.schema,o,{topNode:e.doc.resolve(t).parent.copy(),from:i,to:a,preserveWhitespace:!0,editableContent:!0})}function a(e,t){for(var n=t||0;n<e.depth;n++)if(e.index(n)+1<e.node(n).childCount)return!1;return e.parentOffset==e.parent.content.size}function s(e,t){for(var n=t||0;n<e.depth;n++)if(e.index(0)>0)return!1;return 0==e.parentOffset}function u(e){var t=e.operation,n=t.sel,r=t.doc,o=r.resolve(n.from),i=r.resolve(n.to);if(o.sameParent(i)&&o.parent.isTextblock&&o.parentOffset&&i.parentOffset<i.parent.content.size)return l(e,0);for(var u=0;;u++){var c=s(o,u+1),p=a(i,u+1);if(c||p||o.index(u)!=i.index(u)||i.node(u).isTextblock){var f=o.before(u+1),h=i.after(u+1);return c&&o.index(u)>0&&(f-=o.node(u).child(o.index(u)-1).nodeSize),p&&i.index(u)+1<i.node(u).childCount&&(h+=i.node(u).child(i.index(u)+1).nodeSize),{from:f,to:h}}}}function l(e,t){var n=e.operation,r=n.sel,o=n.doc,i=o.resolve(r.from),a=o.resolve(r.to);if(!i.sameParent(a))return u(e);var s=Math.max(0,i.parentOffset-t),l=i.parent.content.size,c=Math.min(l,a.parentOffset+t);if(s>0&&(s=i.parent.childBefore(s).offset),l>c){var p=i.parent.childAfter(c);c=p.offset+p.node.nodeSize}var f=i.start(i.depth);return{from:f+s,to:f+c}}function c(e,t){var n=e.operation;if(n.docSet)return void e.markAllDirty();var r=i(e,t.from,t.to),o=n.doc.slice(t.from,t.to),a=f(o.content,r.content,t.from,n.sel.from);if(a){var s=(0,v.mapThroughResult)(n.mappings,a.start),u=(0,v.mapThroughResult)(n.mappings,a.endA);if(!s.deleted||!u.deleted){h(e,n.doc,a.start,a.endA);var l=r.resolveNoCache(a.start-t.from),c=r.resolveNoCache(a.endB-t.from),d=void 0,m=void 0;if(!l.sameParent(c)&&l.pos<r.content.size&&(d=(0,g.findSelectionFrom)(r,l.pos+1,1,!0))&&d.head==c.pos)e.input.dispatchKey("Enter");else if(l.sameParent(c)&&l.parent.isTextblock&&null!=(m=p(r,l.pos,c.pos)))e.input.insertText(s.pos,u.pos,m);else{var y=r.slice(a.start-t.from,a.endB-t.from);e.tr.replace(s.pos,u.pos,y).apply(e.apply.scroll)}}}}function p(e,t,n){var r="",o=!0,i=null;return e.nodesBetween(t,n,function(e,a){if(e.isInline||!(t>a)){if(!e.isText)return o=!1;i?d.Mark.sameSet(i,e.marks)||(o=!1):i=e.marks,r+=e.text.slice(Math.max(0,t-a),n-a)}}),o?r:null}function f(e,t,n,r){var o=(0,d.findDiffStart)(e,t,n);if(!o)return null;var i=(0,d.findDiffEnd)(e,t,n+e.size,n+t.size),a=i.a,s=i.b;if(o>a){var u=o>=r&&r>=a?o-r:0;o-=u,s=o+(s-a),a=o}else if(o>s){var u=o>=r&&r>=s?o-r:0;o-=u,a=o+(a-s),s=o}return{start:o,endA:a,endB:s}}function h(e,t,n,r){var o=t.resolve(n),i=t.resolve(r),a=o.sameDepth(i);0==a?e.markAllDirty():e.markRangeDirty(o.before(a),o.after(a),t)}Object.defineProperty(n,"__esModule",{value:!0}),n.readInputChange=r,n.readCompositionChange=o;var d=e("../model"),m=e("../format"),v=e("../transform/map"),g=e("./selection"),y=e("./dompos")},{"../format":23,"../model":38,"../transform/map":47,"./dompos":10,"./selection":19}],10:[function(e,t,n){"use strict";function r(e,t){for(var n=0,r=0,o=t;o!=e.content;o=o.parentNode){var i=o.getAttribute("pm-offset");i&&(n+=+i+r,r=1)}return n}function o(e,t,n){if(e.operation&&e.doc!=e.operation.doc)throw new RangeError("Fetching a position from an outdated DOM structure");null==n&&(n=Array.prototype.indexOf.call(t.parentNode.childNodes,t),t=t.parentNode);for(var o=0,a=void 0;;){var s=0;if(3==t.nodeType)o+=n;else{if(a=t.getAttribute("pm-offset")&&!i(t)){var u=+t.getAttribute("pm-size");return r(e,t)+(n==t.childNodes.length?u:Math.min(o,u))}if(t.hasAttribute("pm-container"))break;(a=t.getAttribute("pm-inner-offset"))?(o+=+a,s=-1):n&&n==t.childNodes.length&&(s=1)}var l=t.parentNode;n=0>s?0:Array.prototype.indexOf.call(l.childNodes,t)+s,t=l}for(var c=t==e.content?0:r(e,t)+1,p=0,f=t.childNodes[n-1];f;f=f.previousSibling)if(1==f.nodeType&&(a=f.getAttribute("pm-offset"))){p+=+a+ +f.getAttribute("pm-size");break}return c+p+o}function i(e){return e.hasAttribute("pm-container")?e:e.querySelector("[pm-container]")}function a(e,t,n){if(!n&&e.operation&&e.doc!=e.operation.doc)throw new RangeError("Resolving a position in an outdated DOM structure");for(var r=e.content,o=t;;)for(var a=r.firstChild,s=0;;a=a.nextSibling,s++){if(!a){if(o&&!n)throw new RangeError("Failed to find node at "+t+" rem = "+o);return{node:r,offset:s}}var l=1==a.nodeType&&a.getAttribute("pm-size");if(l){if(!o)return{node:r,offset:s};if(l=+l,l>o){if(r=i(a)){o--;break}return u(a,o)}o-=l}}}function s(e,t){var n=a(e,t),r=n.node,o=n.offset;if(1!=r.nodeType||o==r.childNodes.length)throw new RangeError("No node after pos "+t);return r.childNodes[o]}function u(e,t){for(;;){var n=e.firstChild;if(!n)return{node:e,offset:t};if(1!=n.nodeType)return{node:n,offset:t};if(n.hasAttribute("pm-inner-offset")){for(var r=0;;){var o=n.nextSibling,i=void 0;if(!o||(i=+o.getAttribute("pm-inner-offset"))>=t)break;n=o,r=i}t-=r}e=n}}function l(){return{left:0,right:window.innerWidth,top:0,bottom:window.innerHeight}}function c(e,t){t||(t=e.sel.range.head||e.sel.range.from);for(var n=v(e,t),r=e.content;;r=r.parentNode){var o=r==document.body,i=o?l():r.getBoundingClientRect(),a=0,s=0;if(n.top<i.top?s=-(i.top-n.top+b):n.bottom>i.bottom&&(s=n.bottom-i.bottom+b),n.left<i.left?a=-(i.left-n.left+b):n.right>i.right&&(a=n.right-i.right+b),(a||s)&&(o?window.scrollBy(a,s):(s&&(r.scrollTop+=s),a&&(r.scrollLeft+=a))),o)break}}function p(e,t){for(var n=void 0,r=1e8,o=void 0,i=0,a=e.firstChild;a;a=a.nextSibling){var s=void 0;if(1==a.nodeType)s=a.getClientRects();else{if(3!=a.nodeType)continue;s=m(a)}for(var u=0;u<s.length;u++){var l=s[u];if(l.left<=t.left&&l.right>=t.left){var c=l.top>t.top?l.top-t.top:l.bottom<t.top?t.top-l.bottom:0;if(r>c){n=a,r=c,o=c?{left:t.left,top:l.top}:t,1!=a.nodeType||a.firstChild||(i=u+(t.left>=(l.left+l.right)/2?1:0));continue}}!n&&(t.top>=l.bottom||t.top>=l.top&&t.left>=l.right)&&(i=u+1)}}return n?3==n.nodeType?f(n,o):n.firstChild?p(n,o):{node:e,offset:i}:{node:e,offset:i}}function f(e,t){for(var n=e.nodeValue.length,r=document.createRange(),o=0;n>o;o++){r.setEnd(e,o+1),r.setStart(e,o);var i=r.getBoundingClientRect();if(i.top!=i.bottom&&i.left-1<=t.left&&i.right+1>=t.left&&i.top-1<=t.top&&i.bottom+1>=t.top)return{node:e,offset:o+(t.left>=(i.left+i.right)/2?1:0)}}return{node:e,offset:0}}function h(e,t){var n=document.elementFromPoint(t.left,t.top+1);if(!(0,k.contains)(e.content,n))return null;n.firstChild||(n=n.parentNode);var r=p(n,t),i=r.node,a=r.offset;return o(e,i,a)}function d(e,t,n){var r=document.createRange();return r.setEnd(e,n),r.setStart(e,t),r.getBoundingClientRect()}function m(e){var t=document.createRange();return t.setEnd(e,e.nodeValue.length),t.setStart(e,0),t.getClientRects()}function v(e,t){var n=a(e,t),r=n.node,o=n.offset,i=void 0,s=void 0;if(3==r.nodeType)o<r.nodeValue.length&&(s=d(r,o,o+1),i="left"),s&&s.left!=s.right||!o||(s=d(r,o-1,o),i="right");else if(r.firstChild){if(o<r.childNodes.length){var u=r.childNodes[o];s=3==u.nodeType?d(u,0,u.nodeValue.length):u.getBoundingClientRect(),i="left"}if((!s||s.left==s.right)&&o){var u=r.childNodes[o-1];s=3==u.nodeType?d(u,0,u.nodeValue.length):u.getBoundingClientRect(),i="right"}}else s=r.getBoundingClientRect(),i="left";var l=s[i];return{top:s.top,bottom:s.bottom,left:l,right:l}}function g(e,t,n,o){for(;t&&t!=e.content;t=t.parentNode)if(t.hasAttribute("pm-offset")){var i=r(e,t),a=e.doc.nodeAt(i);if(a.type.countCoordsAsChild){var s=a.type.countCoordsAsChild(a,i,t,n);if(null!=s)return s}if((o||null==a.type.contains)&&a.type.selectable)return i;if(!o)return null}}function y(e,t,n,o,i){for(var a=o;a&&a!=e.content;a=a.parentNode)if(a.hasAttribute("pm-offset")){var s=r(e,a),u=e.doc.nodeAt(s),l=u.type[t]&&u.type[t](e,n,s,u)!==!1;if(i||l)return l}}Object.defineProperty(n,"__esModule",{value:!0}),n.posBeforeFromDOM=r,n.posFromDOM=o,n.childContainer=i,n.DOMFromPos=a,n.DOMAfterPos=s,n.scrollIntoView=c,n.posAtCoords=h,n.coordsAtPos=v,n.selectableNodeAbove=g,n.handleNodeClick=y;var k=e("../dom"),b=5},{"../dom":3}],11:[function(e,t,n){"use strict";function r(e){return{pos:0,preRenderContent:function(){this.pos++},postRenderContent:function(){this.pos++},onRender:function(e,t,n){return e.isBlock&&(null!=n&&t.setAttribute("pm-offset",n),t.setAttribute("pm-size",e.nodeSize),e.isTextblock&&a(t,e),"false"==t.contentEditable&&(t=(0,p.elt)("div",null,t)),e.type.contains||this.pos++),t},onContainer:function(e){e.setAttribute("pm-container",!0)},renderInlineFlat:function(t,n,r){e.advanceTo(this.pos);for(var i=this.pos,a=i+t.nodeSize,s=e.nextChangeBefore(a),u=n,l=void 0,c=0;c<t.marks.length;c++)u=u.firstChild;1!=n.nodeType&&(n=(0,p.elt)("span",null,n),-1==s&&(l=n)),!l&&(s>-1||e.current.length)&&(l=u==n?n=(0,p.elt)("span",null,u):u.parentNode.appendChild((0,p.elt)("span",null,u))),n.setAttribute("pm-offset",r),n.setAttribute("pm-size",t.nodeSize);for(var f=0;s>-1;){var h=s-i,d=o(l,h);e.current.length&&(d.className=e.current.join(" ")),d.setAttribute("pm-inner-offset",f),f+=h,e.advanceTo(s),s=e.nextChangeBefore(a),-1==s&&l.setAttribute("pm-inner-offset",f),i+=h}return e.current.length&&(l.className=e.current.join(" ")),this.pos+=t.nodeSize,n},document:document}}function o(e,t){var n=e.firstChild,r=n.nodeValue,o=e.parentNode.insertBefore((0,p.elt)("span",null,r.slice(0,t)),e);return n.nodeValue=r.slice(t),o}function i(e,t){e.content.textContent="",e.content.appendChild((0,c.toDOM)(t,r(e.ranges.activeRangeTracker())))}function a(e,t){var n=0==t.content.size||t.lastChild.type.isBR||t.type.isCode&&t.lastChild.isText&&/\n$/.test(t.lastChild.text)?"br":t.lastChild.isText||null!=t.lastChild.type.contains?null:"text",r=e.lastChild,o=r&&1==r.nodeType&&r.hasAttribute("pm-ignore")?"BR"==r.nodeName?"br":"text":null;n!=o&&(o&&e.removeChild(r),n&&e.appendChild("br"==n?(0,p.elt)("br",{"pm-ignore":"trailing-break"}):(0,p.elt)("span",{"pm-ignore":"cursor-text"},"")))}function s(e,t,n){for(;t<e.childCount;t++){var r=e.child(t);if(r==n)return t}return-1}function u(e){var t=e.nextSibling;return e.parentNode.removeChild(e),t}function l(e,t,n,o){function l(e,n,r,o){for(var i=0,d=r.firstChild,m=e.firstChild,v=0,g=0;v<n.childCount;v++){var y=n.child(v),k=void 0,b=void 0,w=d==y?i:s(r,i+1,y);if(w>-1)for(k=y;i!=w;)i++,m=u(m);if(k&&!t.get(k))b=!0;else if(d&&!y.isText&&y.sameMarkup(d)&&t.get(d)!=f.DIRTY_REDRAW)b=!0,d.type.contains&&l((0,h.childContainer)(m),y,d,o+g+1);else{p.pos=o+g;var _=(0,c.nodeToDOM)(y,p,g);e.insertBefore(_,m),b=!1}b&&(m.setAttribute("pm-offset",g),m.setAttribute("pm-size",y.nodeSize),m=m.nextSibling,d=r.maybeChild(++i)),g+=y.nodeSize}for(;d;)m=u(m),d=r.maybeChild(++i);n.isTextblock&&a(e,n)}if(t.get(o)==f.DIRTY_REDRAW)return i(e,n);var p=r(e.ranges.activeRangeTracker());l(e.content,n,o,0)}Object.defineProperty(n,"__esModule",{value:!0}),n.draw=i,n.redraw=l;var c=e("../format"),p=e("../dom"),f=e("./main"),h=e("./dompos")},{"../dom":3,"../format":23,"./dompos":10,"./main":15}],12:[function(e,t,n){"use strict";function r(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function o(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var a=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.History=void 0;var s=e("../transform"),u=500,l=function(){function e(t){i(this,e),this.events=0,this.maxEvents=t,this.items=[new p]}return a(e,[{key:"popEvent",value:function(e,t,n){for(var r=t,o=new s.Transform(e),i=new d,a=void 0,u=[],l=this.items.length;;){var c=this.items[--l];if(n&&c==n)break;if(!c.map)return null;if(c.step){if(r){var p=c.step.map(i.remap),f=void 0;this.items[l]=new h(c.map),p&&o.maybeStep(p).doc&&(f=o.maps[o.maps.length-1],this.items.push(new h(f,this.items[l].id))),i.movePastStep(c,f)}else this.items.pop(),o.maybeStep(c.step);if(u.push(c.id),c.selection&&(this.events--,!n)){a=c.selection.type.mapToken(c.selection,i.remap);break}}else i.add(c),r=!0}return{transform:o,selection:a,ids:u}}},{key:"clear",value:function(){this.items.length=1,this.events=0}},{key:"addTransform",value:function(e,t,n){for(var r=0;r<e.steps.length;r++){var o=e.steps[r].invert(e.docs[r]);this.items.push(new f(e.maps[r],n&&n[r],o,t)),t&&(this.events++,t=null)}this.events>this.maxEvents&&this.clip()}},{key:"clip",value:function(){for(var e=0,t=this.events-this.maxEvents,n=0;;n++){var r=this.items[n];if(r.selection){if(!(t>e))return this.items.splice(0,n,new p(null,this.events[t-1])),void(this.events=this.maxEvents);++e}}}},{key:"addMaps",value:function(e){if(0!=this.events)for(var t=0;t<e.length;t++)this.items.push(new h(e[t]))}},{key:"findChangeID",value:function(e){if(e==this.items[0].id)return this.items[0];for(var t=this.items.length-1;t>=0;t--){var n=this.items[t];if(n.step){if(n.id==e)return n;if(n.id<e)return null}}}},{key:"rebased",value:function(e,t,n){if(0!=this.events){var r=[],o=this.items.length-n.length,i=0;if(1>o&&(i=1-o,o=1,this.items[0]=new p),n.length){for(var a=new s.Remapping([],e.slice()),l=o,c=i;l<this.items.length;l++){var d=this.items[l],m=n[c++],v=void 0;if(-1!=m){var g=t.maps[m];if(d.step){var y=t.steps[m].invert(t.docs[m]),k=d.selection&&d.selection.type.mapToken(d.selection,a);r.push(new f(g,d.id,y,k))}else r.push(new h(g));v=a.addToBack(g)}a.addToFront(d.map.invert(),v)}this.items.length=o}for(var b=0;b<e.length;b++)this.items.push(new h(e[b]));for(var b=0;b<r.length;b++)this.items.push(r[b]);!this.compressing&&this.emptyItems(o)+e.length>u&&this.compress(o+e.length)}}},{key:"emptyItems",value:function(e){for(var t=0,n=1;e>n;n++)this.items[n].step||t++;return t}},{key:"compress",value:function(e){for(var t=new d,n=[],r=0,o=this.items.length-1;o>=0;o--){var i=this.items[o];if(o>=e)n.push(i);else if(i.step){var a=i.step.map(t.remap),s=a&&a.posMap();if(t.movePastStep(i,s),a){var u=i.selection&&i.selection.type.mapToken(i.selection,t.remap);n.push(new f(s.invert(),i.id,a,u)),u&&r++}}else i.map?t.add(i):n.push(i)}this.items=n.reverse(),this.events=r}},{key:"toString",value:function(){return this.items.join("\n")}},{key:"changeID",get:function(){for(var e=this.items.length-1;e>0;e--)if(this.items[e].step)return this.items[e].id;return this.items[0].id}}]),e}(),c=1,p=function(){function e(t,n){i(this,e),this.map=t,this.id=n||c++}return a(e,[{key:"toString",value:function(){return this.id+":"+(this.map||"")+(this.step?":"+this.step:"")+(null!=this.mirror?"->"+this.mirror:"")}}]),e}(),f=function(e){function t(e,n,o,a){i(this,t);var s=r(this,Object.getPrototypeOf(t).call(this,e,n));return s.step=o,s.selection=a,s}return o(t,e),t}(p),h=function(e){function t(e,n){i(this,t);var o=r(this,Object.getPrototypeOf(t).call(this,e));return o.mirror=n,o}return o(t,e),t}(p),d=function(){function e(){i(this,e),this.remap=new s.Remapping,this.mirrorBuffer=Object.create(null)}return a(e,[{key:"add",value:function(e){var t=this.remap.addToFront(e.map,this.mirrorBuffer[e.id]);return null!=e.mirror&&(this.mirrorBuffer[e.mirror]=t),t}},{key:"movePastStep",value:function(e,t){var n=this.add(e);t&&this.remap.addToBack(t,n)}}]),e}();n.History=function(){function e(t){i(this,e),this.pm=t,this.done=new l(t.options.historyDepth),this.undone=new l(t.options.historyDepth),this.lastAddedAt=0,this.ignoreTransform=!1,this.preserveItems=0,t.on("transform",this.recordTransform.bind(this))}return a(e,[{key:"recordTransform",value:function(e,t,n){if(!this.ignoreTransform)if(0==n.addToHistory)this.done.addMaps(e.maps),this.undone.addMaps(e.maps);else{var r=Date.now(),o=r>this.lastAddedAt+this.pm.options.historyEventDelay;this.done.addTransform(e,o?t.token:null),this.undone.clear(),this.lastAddedAt=r}}},{key:"undo",value:function(){return this.shift(this.done,this.undone)}},{key:"redo",value:function(){return this.shift(this.undone,this.done)}},{key:"shift",value:function(e,t){var n=e.popEvent(this.pm.doc,this.preserveItems>0);if(!n)return!1;var r=this.pm.selection;if(!n.transform.steps.length)return this.shift(e,t);var o=n.selection.type.fromToken(n.selection,n.transform.doc);return this.applyIgnoring(n.transform,o),t.addTransform(n.transform,r.token,n.ids),this.lastAddedAt=0,!0}},{key:"applyIgnoring",value:function(e,t){this.ignoreTransform=!0,this.pm.apply(e,{selection:t,filter:!1}),this.ignoreTransform=!1}},{key:"getVersion",value:function(){return this.done.changeID}},{key:"isAtVersion",value:function(e){return this.done.changeID==e}},{key:"backToVersion",value:function(e){var t=this.done.findChangeID(e);if(!t)return!1;var n=this.done.popEvent(this.pm.doc,this.preserveItems>0,t),r=n.transform;return this.applyIgnoring(r),this.undone.clear(),!0}},{key:"rebased",value:function(e,t,n){this.done.rebased(e,t,n),this.undone.rebased(e,t,n)}},{key:"undoDepth",get:function(){return this.done.events}},{key:"redoDepth",get:function(){return this.undone.events}}]),e}()},{"../transform":45}],13:[function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(n,"__esModule",{value:!0}),n.Keymap=n.baseCommands=n.Command=n.CommandSet=n.MarkedRange=n.NodeSelection=n.TextSelection=n.Selection=n.defineOption=n.ProseMirror=void 0;var o=e("./main");Object.defineProperty(n,"ProseMirror",{enumerable:!0,get:function(){return o.ProseMirror}});var i=e("./options");Object.defineProperty(n,"defineOption",{enumerable:!0,get:function(){return i.defineOption}});var a=e("./selection");Object.defineProperty(n,"Selection",{enumerable:!0,get:function(){return a.Selection}}),Object.defineProperty(n,"TextSelection",{enumerable:!0,get:function(){return a.TextSelection}}),Object.defineProperty(n,"NodeSelection",{enumerable:!0,get:function(){return a.NodeSelection}});var s=e("./range");Object.defineProperty(n,"MarkedRange",{enumerable:!0,get:function(){return s.MarkedRange}});var u=e("./command");Object.defineProperty(n,"CommandSet",{enumerable:!0,get:function(){return u.CommandSet}}),Object.defineProperty(n,"Command",{enumerable:!0,get:function(){return u.Command}});var l=e("./base_commands");Object.defineProperty(n,"baseCommands",{enumerable:!0,get:function(){return l.baseCommands}}),e("./schema_commands");var c=e("browserkeymap"),p=r(c);n.Keymap=p["default"]},{"./base_commands":4,"./command":7,"./main":15,"./options":16,"./range":17,"./schema_commands":18,"./selection":19,browserkeymap:60}],14:[function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){return e.operation&&e.flush()?document.elementFromPoint(t.clientX,t.clientY):t.target}function a(e,t,n){var r=(0,C.selectableNodeAbove)(e,n,{left:t.clientX,top:t.clientY},!0);if(null==r)return e.sel.fastPoll();var o=e.selection,i=o.node,a=o.from;if(i){var s=e.doc.resolve(r),u=e.doc.resolve(a);if(s.depth>=u.depth&&s.before(u.depth)==a){if(0==u.depth)return e.sel.fastPoll();r=s.before(s.depth)}}e.setNodeSelection(r),e.focus(),t.preventDefault()}function s(e,t,n){t.preventDefault();var r=(0,C.selectableNodeAbove)(e,n,{left:t.clientX,top:t.clientY},!0);if(null!=r){var o=e.doc.resolve(r),i=o.nodeAfter;i.isBlock&&!i.isTextblock?e.setNodeSelection(r):i.isInline?e.setTextSelection(o.start(o.depth),o.end(o.depth)):e.setTextSelection(r+1,r+1+i.content.size),e.focus()}}function u(e,t,n,r){var o=e.slice(t,n),i=e.resolve(t),a=i.node(i.depth-o.openLeft),s=a.type.name+" "+o.openLeft+" "+o.openRight,u='<div pm-context="'+s+'">'+(0,k.toHTML)(o.content)+"</div>";return r.clearData(),r.setData("text/html",u),r.setData("text/plain",(0,k.toText)(o.content)),o}function l(e){return null!=E?E:(e.setData("text/html","<hr>"),E="<hr>"==e.getData("text/html"))}function c(e,t,n){var r=t.getData("text/plain"),o=t.getData("text/html");if(!o&&!r)return null;var i=void 0,a=void 0;if(!n&&o||!r){var s=document.createElement("div");s.innerHTML=e.signalPipelined("transformPastedHTML",o);var u=s.querySelector("[pm-context]"),l=void 0,c=void 0,f=void 0;u&&(l=/^(\w+) (\d+) (\d+)$/.exec(u.getAttribute("pm-context")))&&(c=e.schema.nodes[l[1]])&&c.defaultAttrs&&(f=p(u,c,+l[2],+l[3]))?a=f:i=(0,k.fromDOM)(e.schema,s)}else i=(0,k.parseFrom)(e.schema,e.signalPipelined("transformPastedText",r),"text");return a||(a=i.slice((0,x.findSelectionAtStart)(i).from,(0,x.findSelectionAtEnd)(i).to)),e.signalPipelined("transformPasted",a)}function p(e,t,n,r){var o=t.schema,i=t.create(),a=(0,k.fromDOM)(o,e,{topNode:i,preserveWhitespace:!0});return new v.Slice(a.content,f(a.content,n,!0),f(a.content,r,!1),i)}function f(e,t,n){for(var r=0;t>r;r++){var o=n?e.firstChild:e.lastChild;if(!o||null==o.type.contains)return r;e=o.content}return t}function h(e,t,n){var r=e.posAtCoords({left:t.clientX,top:t.clientY});if(null==r||!n||!n.content.size)return r;for(var o=e.doc.resolve(r),i=n.content.leastSuperKind(),a=o.depth;a>=0;a--)if(i.isSubKind(o.node(a).type.contains))return a==o.depth?r:r<=(o.start(a+1)+o.end(a+1))/2?o.before(a+1):o.after(a+1);return r}function d(e){e.input.dropTarget&&(e.wrapper.removeChild(e.input.dropTarget),e.input.dropTarget=null)}var m=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.Input=void 0;var v=e("../model"),g=e("browserkeymap"),y=r(g),k=e("../format"),b=e("./capturekeys"),w=e("../dom"),_=e("./domchange"),x=e("./selection"),C=e("./dompos"),S=null,M={};n.Input=function(){function e(t){var n=this;o(this,e),this.pm=t,this.baseKeymap=null,this.keySeq=null,this.mouseDown=null,this.dragging=null,this.dropTarget=null,this.shiftKey=!1,this.finishComposing=null,this.keymaps=[],this.defaultKeymap=null,this.storedMarks=null;var r=function(e){var n=M[e];t.content.addEventListener(e,function(e){return n(t,e)})};for(var i in M)r(i);t.on("selectionChange",function(){return n.storedMarks=null})}return m(e,[{key:"dispatchKey",value:function(e,t){var n=this.pm,r=n.input.keySeq;if(r){if(y["default"].isModifierKey(e))return!0;clearTimeout(S),S=setTimeout(function(){n.input.keySeq==r&&(n.input.keySeq=null)},50),e=r+" "+e}for(var o=function s(e){if(e===!1)return"nothing";if("..."==e)return"multi";if(null==e)return!1;var t=!1;if(Array.isArray(e))for(var r=0;t===!1&&r<e.length;r++)t=s(e[r]);else t="string"==typeof e?n.execCommand(e):e(n);return 0==t?!1:"handled"},i=void 0,a=0;!i&&a<n.input.keymaps.length;a++)i=o(n.input.keymaps[a].map.lookup(e,n));return i||(i=o(n.input.baseKeymap.lookup(e,n))||o(b.captureKeys.lookup(e))),"multi"==i&&(n.input.keySeq=e),"handled"!=i&&"multi"!=i||!t||t.preventDefault(),r&&!i&&/\'$/.test(e)?(t&&t.preventDefault(),!0):!!i}},{key:"insertText",value:function(e,t,n){if(e!=t||n){var r=this.pm,o=r.input.storedMarks||r.doc.marksAt(e),i=r.tr.replaceWith(e,t,n?r.schema.text(n,o):null);i.apply({scrollIntoView:!0,selection:(0,x.findSelectionNear)(i.doc,i.map(t),-1,!0)}),n&&r.signal("textInput",n)}}},{key:"startComposition",value:function(e,t){this.pm.ensureOperation({noFlush:!0,readSelection:t}).composing={ended:!1,applied:!1,margin:e},this.pm.unscheduleFlush()}},{key:"applyComposition",value:function(e){var t=this.composing;t.applied||((0,_.readCompositionChange)(this.pm,t.margin),t.applied=!0,e&&this.pm.flush())}},{key:"composing",get:function(){return this.pm.operation&&this.pm.operation.composing}}]),e}();M.keydown=function(e,t){if((0,x.hasFocus)(e)&&(e.signal("interaction"),16==t.keyCode&&(e.input.shiftKey=!0),!e.input.composing)){var n=y["default"].keyName(t);n&&e.input.dispatchKey(n,t)||e.sel.fastPoll()}},M.keyup=function(e,t){16==t.keyCode&&(e.input.shiftKey=!1)},M.keypress=function(e,t){if(!(!(0,x.hasFocus)(e)||e.input.composing||!t.charCode||t.ctrlKey&&!t.altKey||w.browser.mac&&t.metaKey||e.input.dispatchKey(y["default"].keyName(t),t))){var n=e.selection;w.browser.ios||(e.input.insertText(n.from,n.to,String.fromCharCode(t.charCode)),t.preventDefault())}};var O=0,A=0;M.mousedown=function(e,t){e.signal("interaction");var n=Date.now(),r=500>n-O,o=600>n-A;A=O,O=n;var a=i(e,t);o?s(e,t,a):r&&(0,C.handleNodeClick)(e,"handleDoubleClick",t,a,!0)||(e.input.mouseDown=new T(e,t,a,r))};var T=function(){function e(t,n,r,i){o(this,e),this.pm=t,this.event=n,this.target=r,this.leaveToBrowser=t.input.shiftKey||i;var a=(0,C.posBeforeFromDOM)(t,this.target),s=t.doc.nodeAt(a);this.mightDrag=s.type.draggable||s==t.sel.range.node?a:null,null!=this.mightDrag&&(this.target.draggable=!0,w.browser.gecko&&(this.setContentEditable=!this.target.hasAttribute("contentEditable"))&&this.target.setAttribute("contentEditable","false")),this.x=n.clientX,this.y=n.clientY,window.addEventListener("mouseup",this.up=this.up.bind(this)),window.addEventListener("mousemove",this.move=this.move.bind(this)),t.sel.fastPoll()}return m(e,[{key:"done",value:function(){window.removeEventListener("mouseup",this.up),window.removeEventListener("mousemove",this.move),null!=this.mightDrag&&(this.target.draggable=!1,w.browser.gecko&&this.setContentEditable&&this.target.removeAttribute("contentEditable"))}},{key:"up",value:function(e){this.done();var t=i(this.pm,e);if(this.leaveToBrowser||!(0,w.contains)(this.pm.content,t))this.pm.sel.fastPoll();else if(this.event.ctrlKey)a(this.pm,e,t);else if(!(0,C.handleNodeClick)(this.pm,"handleClick",e,t,!0)){var n=(0,C.selectableNodeAbove)(this.pm,t,{left:this.x,top:this.y});n?(this.pm.setNodeSelection(n),this.pm.focus()):this.pm.sel.fastPoll()}}},{key:"move",value:function(e){!this.leaveToBrowser&&(Math.abs(this.x-e.clientX)>4||Math.abs(this.y-e.clientY)>4)&&(this.leaveToBrowser=!0),this.pm.sel.fastPoll()}}]),e}();M.touchdown=function(e){e.sel.fastPoll()},M.contextmenu=function(e,t){(0,C.handleNodeClick)(e,"handleContextMenu",t,i(e,t),!1)},M.compositionstart=function(e,t){!e.input.composing&&(0,x.hasFocus)(e)&&e.input.startComposition(t.data?t.data.length:0,!0)},M.compositionupdate=function(e){!e.input.composing&&(0,x.hasFocus)(e)&&e.input.startComposition(0,!1)},M.compositionend=function(e,t){if((0,x.hasFocus)(e)){var n=e.input.composing;if(n){if(n.applied)return}else{if(!t.data)return;e.input.startComposition(t.data.length,!1)}clearTimeout(e.input.finishComposing),e.operation.composing.ended=!0,e.input.finishComposing=window.setTimeout(function(){var t=e.input.composing;t&&t.ended&&e.input.applyComposition(!0)},20)}},M.input=function(e){if((0,x.hasFocus)(e)){var t=e.input.composing;if(t)return void(t.ended&&e.input.applyComposition(!0));(0,_.readInputChange)(e),e.flush()}};var E=null;M.copy=M.cut=function(e,t){var n=e.selection,r=n.from,o=n.to,i=n.empty;!i&&t.clipboardData&&l(t.clipboardData)&&(u(e.doc,r,o,t.clipboardData),t.preventDefault(),"cut"!=t.type||i||e.tr["delete"](r,o).apply())},M.paste=function(e,t){if((0,x.hasFocus)(e)&&t.clipboardData){var n=e.selection,r=c(e,t.clipboardData,e.input.shiftKey);r&&(t.preventDefault(),e.tr.replace(n.from,n.to,r).apply(e.apply.scroll))}};var D=function P(e,t,n){o(this,P),this.slice=e,this.from=t,this.to=n};M.dragstart=function(e,t){var n=e.input.mouseDown;if(n&&n.done(),t.dataTransfer){var r=e.selection,o=r.from,i=r.to,a=r.empty,s=void 0,l=!a&&e.posAtCoords({left:t.clientX,top:t.clientY});if(null!=l&&l>=o&&i>=l)s={from:o,to:i};else if(n&&null!=n.mightDrag){var c=n.mightDrag;s={from:c,to:c+e.doc.nodeAt(c).nodeSize}}if(s){var p=u(e.doc,s.from,s.to,t.dataTransfer);e.input.dragging=new D(p,s.from,s.to)}}},M.dragend=function(e){return window.setTimeout(function(){return e.input.dragging=null},50)},M.dragover=M.dragenter=function(e,t){t.preventDefault();var n=e.input.dropTarget;n||(n=e.input.dropTarget=e.wrapper.appendChild((0,w.elt)("div",{"class":"ProseMirror-drop-target"})));var r=h(e,t,e.input.dragging&&e.input.dragging.slice);if(null!=r){var o=e.coordsAtPos(r),i=e.wrapper.getBoundingClientRect();o.top-=i.top,o.right-=i.left,o.bottom-=i.top,o.left-=i.left,n.style.left=o.left-1+"px",n.style.top=o.top+"px",n.style.height=o.bottom-o.top+"px"}},M.dragleave=function(e,t){t.target==e.content&&d(e)},M.drop=function(e,t){var n=e.input.dragging;if(e.input.dragging=null,d(e),t.dataTransfer&&!e.signalDOM(t)){var r=n&&n.slice||c(e,t.dataTransfer);if(r){t.preventDefault();var o=h(e,t,r),i=o;if(null==o)return;var a=e.tr;n&&!t.ctrlKey&&null!=n.from&&(a["delete"](n.from,n.to),o=a.map(o)),a.replace(o,o,r).apply();var s=void 0;if(1==r.content.childCount&&0==r.openLeft&&0==r.openRight&&r.content.child(0).type.selectable&&(s=e.doc.nodeAt(o))&&s.sameMarkup(r.content.child(0)))e.setNodeSelection(o);else{var u=(0,x.findSelectionNear)(e.doc,o,1,!0).from,l=(0,x.findSelectionNear)(e.doc,a.map(i),-1,!0).to;e.setTextSelection(u,l)}e.focus()}}},M.focus=function(e){e.wrapper.classList.add("ProseMirror-focused"),e.signal("focus")},M.blur=function(e){e.wrapper.classList.remove("ProseMirror-focused"),e.signal("blur")}},{"../dom":3,"../format":23,"../model":38,"./capturekeys":5,"./domchange":9,"./dompos":10,"./selection":19,browserkeymap:60}],15:[function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.DIRTY_REDRAW=n.DIRTY_RESCAN=n.ProseMirror=void 0,e("./css");var a=e("browserkeymap"),s=r(a),u=e("../util/sortedinsert"),l=r(u),c=e("../util/map"),p=e("../util/event"),f=e("../dom"),h=e("../format"),d=e("./options"),m=e("./selection"),v=e("./dompos"),g=e("./draw"),y=e("./input"),k=e("./history"),b=e("./range"),w=e("./transform"),_=n.ProseMirror=function(){function e(t){o(this,e),(0,f.ensureCSSAdded)(),t=this.options=(0,d.parseOptions)(t),this.schema=t.schema,null==t.doc&&(t.doc=this.schema.node("doc",null,[this.schema.node("paragraph")])),this.content=(0,f.elt)("div",{"class":"ProseMirror-content","pm-container":!0}),this.wrapper=(0,f.elt)("div",{"class":"ProseMirror"},this.content),this.wrapper.ProseMirror=this,t.place&&t.place.appendChild?t.place.appendChild(this.wrapper):t.place&&t.place(this.wrapper),this.setDocInner(t.docFormat?(0,h.parseFrom)(this.schema,t.doc,t.docFormat):t.doc),(0,g.draw)(this,this.doc),this.content.contentEditable=!0,t.label&&this.content.setAttribute("aria-label",t.label),this.mod=Object.create(null),this.cached=Object.create(null),this.operation=null,this.dirtyNodes=new c.Map,this.flushScheduled=null,this.sel=new m.SelectionState(this,(0,m.findSelectionAtStart)(this.doc)),this.accurateSelection=!1,this.input=new y.Input(this),this.commands=null,this.commandKeys=null,(0,d.initOptions)(this)}return i(e,[{key:"setOption",value:function(e,t){(0,d.setOption)(this,e,t),this.signal("optionChanged",e,t)}},{key:"getOption",value:function(e){return this.options[e]}},{key:"setTextSelection",value:function(e){var t=arguments.length<=1||void 0===arguments[1]?e:arguments[1];this.checkPos(t,!0),e!=t&&this.checkPos(e,!0),this.setSelection(new m.TextSelection(e,t))}},{key:"setNodeSelection",value:function(e){
+this.checkPos(e,!1);var t=this.doc.nodeAt(e);if(!t)throw new RangeError("Trying to set a node selection that doesn't point at a node");if(!t.type.selectable)throw new RangeError("Trying to select a non-selectable node");this.setSelection(new m.NodeSelection(e,e+t.nodeSize,t))}},{key:"setSelection",value:function(e){this.ensureOperation(),e.eq(this.sel.range)||this.sel.setAndSignal(e)}},{key:"setContent",value:function(e,t){t&&(e=(0,h.parseFrom)(this.schema,e,t)),this.setDoc(e)}},{key:"getContent",value:function(e){return e?(0,h.serializeTo)(this.doc,e):this.doc}},{key:"setDocInner",value:function(e){if(e.type!=this.schema.nodes.doc)throw new RangeError("Trying to set a document with a different schema");this.doc=e,this.ranges=new b.RangeStore(this),this.history=new k.History(this)}},{key:"setDoc",value:function(e,t){t||(t=(0,m.findSelectionAtStart)(e)),this.signal("beforeSetDoc",e,t),this.ensureOperation(),this.setDocInner(e),this.operation.docSet=!0,this.sel.set(t,!0),this.signal("setDoc",e,t)}},{key:"updateDoc",value:function(e,t,n){this.ensureOperation(),this.ranges.transform(t),this.operation.mappings.push(t),this.doc=e,this.sel.setAndSignal(n||this.sel.range.map(e,t)),this.signal("change")}},{key:"apply",value:function(e){var t=arguments.length<=1||void 0===arguments[1]?S:arguments[1];if(!e.steps.length)return!1;if(!e.docs[0].eq(this.doc))throw new RangeError("Applying a transform that does not start with the current document");if(t.filter!==!1&&this.signalHandleable("filterTransform",e))return!1;var n=this.selection;return this.signal("beforeTransform",e,t),this.updateDoc(e.doc,e,t.selection),this.signal("transform",e,n,t),t.scrollIntoView&&this.scrollIntoView(),e}},{key:"checkPos",value:function(e,t){var n=e>=0&&e<=this.doc.content.size;if(n&&t&&(n=this.doc.resolve(e).parent.isTextblock),!n)throw new RangeError("Position "+e+" is not valid in current document")}},{key:"ensureOperation",value:function(e){return this.operation||this.startOperation(e)}},{key:"startOperation",value:function(e){var t=this;return this.operation=new M(this),e&&e.readSelection===!1||!this.sel.readFromDOM()||(this.operation.sel=this.sel.range),null==this.flushScheduled&&(this.flushScheduled=(0,f.requestAnimationFrame)(function(){return t.flush()})),this.operation}},{key:"unscheduleFlush",value:function(){null!=this.flushScheduled&&((0,f.cancelAnimationFrame)(this.flushScheduled),this.flushScheduled=null)}},{key:"flush",value:function(){if(this.unscheduleFlush(),!document.body.contains(this.wrapper)||!this.operation)return!1;this.signal("flushing");var e=this.operation,t=!1;return e?(e.composing&&this.input.applyComposition(),this.operation=null,this.accurateSelection=!0,(e.doc!=this.doc||this.dirtyNodes.size)&&((0,g.redraw)(this,this.dirtyNodes,this.doc,e.doc),this.dirtyNodes.clear(),t=!0),(t||!e.sel.eq(this.sel.range)||e.focus)&&this.sel.toDOM(e.focus),e.scrollIntoView!==!1&&(0,v.scrollIntoView)(this,e.scrollIntoView),t&&this.signal("draw"),this.signal("flush"),this.accurateSelection=!1,t):!1}},{key:"addKeymap",value:function(e){var t=arguments.length<=1||void 0===arguments[1]?50:arguments[1];(0,l["default"])(this.input.keymaps,{map:e,rank:t},function(e,t){return e.rank-t.rank})}},{key:"removeKeymap",value:function(e){for(var t=this.input.keymaps,n=0;n<t.length;++n)if(t[n].map==e||t[n].map.options.name==e)return t.splice(n,1),!0}},{key:"markRange",value:function(e,t,n){this.checkPos(e),this.checkPos(t);var r=new b.MarkedRange(e,t,n);return this.ranges.addRange(r),r}},{key:"removeRange",value:function(e){this.ranges.removeRange(e)}},{key:"setMark",value:function(e,t,n){var r=this.selection;if(r.empty){var o=this.activeMarks();if(null==t&&(t=!e.isInSet(o)),t&&!this.doc.resolve(r.head).parent.type.canContainMark(e))return;this.input.storedMarks=t?e.create(n).addToSet(o):e.removeFromSet(o),this.signal("activeMarkChange")}else(null!=t?t:!this.doc.rangeHasMark(r.from,r.to,e))?this.apply(this.tr.addMark(r.from,r.to,e.create(n))):this.apply(this.tr.removeMark(r.from,r.to,e))}},{key:"activeMarks",value:function(){var e;return this.input.storedMarks||(null!=(e=this.selection.head)?this.doc.marksAt(e):[])}},{key:"focus",value:function(){this.operation?this.operation.focus=!0:this.sel.toDOM(!0)}},{key:"hasFocus",value:function(){return this.sel.range instanceof m.NodeSelection?document.activeElement==this.content:(0,m.hasFocus)(this)}},{key:"posAtCoords",value:function(e){return this.flush(),(0,v.posAtCoords)(this,e)}},{key:"coordsAtPos",value:function(e){return this.checkPos(e),this.flush(),(0,v.coordsAtPos)(this,e)}},{key:"scrollIntoView",value:function(){var e=arguments.length<=0||void 0===arguments[0]?null:arguments[0];e&&this.checkPos(e),this.ensureOperation(),this.operation.scrollIntoView=e}},{key:"execCommand",value:function(e,t){var n=this.commands[e];return!(!n||n.exec(this,t)===!1)}},{key:"keyForCommand",value:function(e){var t=this.commandKeys[e];if(void 0!==t)return t;var n=this.commands[e],r=this.input.baseKeymap;if(!n)return this.commandKeys[e]=null;var o=n.spec.key||(f.browser.mac?n.spec.macKey:n.spec.pcKey);if(o){o=s["default"].normalizeKeyName(Array.isArray(o)?o[0]:o);var i=r.bindings[o];if(Array.isArray(i)?i.indexOf(e)>-1:i==e)return this.commandKeys[e]=o}for(var a in r.bindings){var u=r.bindings[a];if(Array.isArray(u)?u.indexOf(e)>-1:u==e)return this.commandKeys[e]=a}return this.commandKeys[e]=null}},{key:"markRangeDirty",value:function(e,t){var n=arguments.length<=2||void 0===arguments[2]?this.doc:arguments[2];this.ensureOperation();for(var r=this.dirtyNodes,o=n.resolve(e),i=n.resolve(t),a=o.sameDepth(i),s=0;a>=s;s++){var u=o.node(s);r.has(u)||r.set(u,x)}for(var l=o.index(a),c=Math.max(l+1,i.index(a)+(a==i.depth?0:1)),p=o.node(a),f=l;c>f;f++)r.set(p.child(f),C)}},{key:"markAllDirty",value:function(){this.dirtyNodes.set(this.doc,C)}},{key:"translate",value:function(e){var t=this.options.translate;return t?t(e):e}},{key:"selection",get:function(){return this.accurateSelection||this.ensureOperation(),this.sel.range}},{key:"tr",get:function(){return new w.EditorTransform(this)}}]),e}();_.prototype.apply.scroll={scrollIntoView:!0};var x=n.DIRTY_RESCAN=1,C=n.DIRTY_REDRAW=2,S={};(0,p.eventMixin)(_);var M=function O(e){o(this,O),this.doc=e.doc,this.docSet=!1,this.sel=e.sel.range,this.scrollIntoView=!1,this.focus=!1,this.mappings=[],this.composing=null}},{"../dom":3,"../format":23,"../util/event":56,"../util/map":57,"../util/sortedinsert":59,"./css":8,"./dompos":10,"./draw":11,"./history":12,"./input":14,"./options":16,"./range":17,"./selection":19,"./transform":20,browserkeymap:60}],16:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t,n,r){f[e]=new p(t,n,r)}function i(e){var t=Object.create(null),n=e?[e].concat(e.use||[]):[];e:for(var r in f){for(var o=0;o<n.length;o++)if(r in n[o]){t[r]=n[o][r];continue e}t[r]=f[r].defaultValue}return t}function a(e){for(var t in f){var n=f[t];n.update&&n.updateOnInit&&n.update(e,e.options[t],null,!0)}}function s(e,t,n){var r=f[t];if(void 0===r)throw new RangeError("Option '"+t+"' is not defined");if(r.update===!1)throw new RangeError("Option '"+t+"' can not be changed");var o=e.options[t];e.options[t]=n,r.update&&r.update(e,n,o,!1)}Object.defineProperty(n,"__esModule",{value:!0}),n.defineOption=o,n.parseOptions=i,n.initOptions=a,n.setOption=s;var u=e("../model"),l=e("../ui/prompt"),c=e("./command"),p=function h(e,t,n){r(this,h),this.defaultValue=e,this.update=t,this.updateOnInit=n!==!1},f=Object.create(null);o("schema",u.defaultSchema,!1),o("doc",null,function(e,t){return e.setDoc(t)},!1),o("docFormat",null),o("place",null),o("historyDepth",100),o("historyEventDelay",500),o("commands",c.CommandSet["default"],c.updateCommands),o("commandParamPrompt",l.ParamPrompt),o("label",null),o("translate",null)},{"../model":38,"../ui/prompt":53,"./command":7}],17:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.RangeStore=n.MarkedRange=void 0;var i=e("../util/event"),a=n.MarkedRange=function(){function e(t,n,o){r(this,e),this.options=o||{},this.from=t,this.to=n}return o(e,[{key:"remove",value:function(){this.signal("removed",this.from,Math.max(this.to,this.from)),this.from=this.to=null}}]),e}();(0,i.eventMixin)(a);var s=function(){function e(){r(this,e),this.sorted=[]}return o(e,[{key:"find",value:function(e){for(var t=0,n=this.sorted.length;;){if(t+10>n){for(var r=t;n>r;r++)if(this.sorted[r].at>=e)return r;return n}var o=t+n>>1;this.sorted[o].at>e?n=o:t=o}}},{key:"insert",value:function(e){this.sorted.splice(this.find(e.at),0,e)}},{key:"remove",value:function(e,t){for(var n=this.find(e),r=0;;r++){var o=n-r-1,i=n+r;if(o>=0&&this.sorted[o].range==t)return void this.sorted.splice(o,1);if(i<this.sorted.length&&this.sorted[i].range==t)return void this.sorted.splice(i,1)}}},{key:"resort",value:function(){for(var e=0;e<this.sorted.length;e++)for(var t=this.sorted[e],n=t.at="open"==t.type?t.range.from:t.range.to,r=e;r>0&&this.sorted[r-1].at>n;)this.sorted[r]=this.sorted[r-1],this.sorted[--r]=t}}]),e}(),u=(n.RangeStore=function(){function e(t){r(this,e),this.pm=t,this.ranges=[],this.sorted=new s}return o(e,[{key:"addRange",value:function(e){this.ranges.push(e),this.sorted.insert({type:"open",at:e.from,range:e}),this.sorted.insert({type:"close",at:e.to,range:e}),e.options.className&&this.pm.markRangeDirty(e.from,e.to)}},{key:"removeRange",value:function(e){var t=this.ranges.indexOf(e);t>-1&&(this.ranges.splice(t,1),this.sorted.remove(e.from,e),this.sorted.remove(e.to,e),e.options.className&&this.pm.markRangeDirty(e.from,e.to),e.remove())}},{key:"transform",value:function(e){for(var t=0;t<this.ranges.length;t++){var n=this.ranges[t];n.from=e.map(n.from,n.options.inclusiveLeft?-1:1),n.to=e.map(n.to,n.options.inclusiveRight?1:-1),n.options.removeWhenEmpty!==!1&&n.from>=n.to?(this.removeRange(n),t--):n.from>n.to&&(n.to=n.from)}this.sorted.resort()}},{key:"activeRangeTracker",value:function(){return new u(this.sorted.sorted)}}]),e}(),function(){function e(t){r(this,e),this.sorted=t,this.pos=0,this.current=[]}return o(e,[{key:"advanceTo",value:function(e){for(var t=void 0;this.pos<this.sorted.length&&(t=this.sorted[this.pos]).at<=e;){var n=t.range.options.className;n&&("open"==t.type?this.current.push(n):this.current.splice(this.current.indexOf(n),1)),this.pos++}}},{key:"nextChangeBefore",value:function(e){for(;;){if(this.pos==this.sorted.length)return-1;var t=this.sorted[this.pos];if(t.range.options.className)return t.at>=e?-1:t.at;this.pos++}}}]),e}())},{"../util/event":56}],18:[function(e,t,n){"use strict";function r(e,t){var n=e.selection,r=n.node,o=n.from,i=n.to,a=e.doc.resolve(o);if(r&&r.type==t)return{from:o,to:i,depth:a.depth+1};var s=a.parent.type==t?a.depth:a.depth>0&&a.node(a.depth-1).type==t?a.depth-1:null;if(null!=s){var u=e.doc.resolve(i);return a.sameDepth(u)<s-1?null:{from:a.before(s),to:u.after(s),depth:s}}}var o=e("../model"),i=e("./command"),a=e("../format");o.StrongMark.register("command","set",{derive:!0,label:"Set strong"}),o.StrongMark.register("command","unset",{derive:!0,label:"Unset strong"}),o.StrongMark.register("command","toggle",{derive:!0,label:"Toggle strong",menu:{group:"inline",rank:20,display:{type:"icon",width:805,height:1024,path:"M317 869q42 18 80 18 214 0 214-191 0-65-23-102-15-25-35-42t-38-26-46-14-48-6-54-1q-41 0-57 5 0 30-0 90t-0 90q0 4-0 38t-0 55 2 47 6 38zM309 442q24 4 62 4 46 0 81-7t62-25 42-51 14-81q0-40-16-70t-45-46-61-24-70-8q-28 0-74 7 0 28 2 86t2 86q0 15-0 45t-0 45q0 26 0 39zM0 950l1-53q8-2 48-9t60-15q4-6 7-15t4-19 3-18 1-21 0-19v-37q0-561-12-585-2-4-12-8t-25-6-28-4-27-2-17-1l-2-47q56-1 194-6t213-5q13 0 39 0t38 0q40 0 78 7t73 24 61 40 42 59 16 78q0 29-9 54t-22 41-36 32-41 25-48 22q88 20 146 76t58 141q0 57-20 102t-53 74-78 48-93 27-100 8q-25 0-75-1t-75-1q-60 0-175 6t-132 6z"}},keys:["Mod-B"]}),o.EmMark.register("command","set",{derive:!0,label:"Add emphasis"}),o.EmMark.register("command","unset",{derive:!0,label:"Remove emphasis"}),o.EmMark.register("command","toggle",{derive:!0,label:"Toggle emphasis",menu:{group:"inline",rank:21,display:{type:"icon",width:585,height:1024,path:"M0 949l9-48q3-1 46-12t63-21q16-20 23-57 0-4 35-165t65-310 29-169v-14q-13-7-31-10t-39-4-33-3l10-58q18 1 68 3t85 4 68 1q27 0 56-1t69-4 56-3q-2 22-10 50-17 5-58 16t-62 19q-4 10-8 24t-5 22-4 26-3 24q-15 84-50 239t-44 203q-1 5-7 33t-11 51-9 47-3 32l0 10q9 2 105 17-1 25-9 56-6 0-18 0t-18 0q-16 0-49-5t-49-5q-78-1-117-1-29 0-81 5t-69 6z"}},keys:["Mod-I"]}),o.CodeMark.register("command","set",{derive:!0,label:"Set code style"}),o.CodeMark.register("command","unset",{derive:!0,label:"Remove code style"}),o.CodeMark.register("command","toggle",{derive:!0,label:"Toggle code style",menu:{group:"inline",rank:22,display:{type:"icon",width:896,height:1024,path:"M608 192l-96 96 224 224-224 224 96 96 288-320-288-320zM288 192l-288 320 288 320 96-96-224-224 224-224-96-96z"}},keys:["Mod-`"]});var s={type:"icon",width:951,height:1024,path:"M832 694q0-22-16-38l-118-118q-16-16-38-16-24 0-41 18 1 1 10 10t12 12 8 10 7 14 2 15q0 22-16 38t-38 16q-8 0-15-2t-14-7-10-8-12-12-10-10q-18 17-18 41 0 22 16 38l117 118q15 15 38 15 22 0 38-14l84-83q16-16 16-38zM430 292q0-22-16-38l-117-118q-16-16-38-16-22 0-38 15l-84 83q-16 16-16 38 0 22 16 38l118 118q15 15 38 15 24 0 41-17-1-1-10-10t-12-12-8-10-7-14-2-15q0-22 16-38t38-16q8 0 15 2t14 7 10 8 12 12 10 10q18-17 18-41zM941 694q0 68-48 116l-84 83q-47 47-116 47-69 0-116-48l-117-118q-47-47-47-116 0-70 50-119l-50-50q-49 50-118 50-68 0-116-48l-118-118q-48-48-48-116t48-116l84-83q47-47 116-47 69 0 116 48l117 118q47 47 47 116 0 70-50 119l50 50q49-50 118-50 68 0 116 48l118 118q48 48 48 116z"};o.LinkMark.register("command","unset",{derive:!0,label:"Unlink",menu:{group:"inline",rank:30,display:s},active:function(){return!0}}),o.LinkMark.register("command","set",{derive:{inverseSelect:!0,params:[{label:"Target",attr:"href"},{label:"Title",attr:"title"}]},label:"Add link",menu:{group:"inline",rank:30,display:s}}),o.Image.register("command","insert",{derive:{params:[{label:"Image URL",attr:"src"},{label:"Description / alternative text",attr:"alt",prefill:function(e){return(0,i.selectedNodeAttr)(e,this,"alt")||(0,a.toText)(e.doc.cut(e.selection.from,e.selection.to))}},{label:"Title",attr:"title"}]},label:"Insert image",menu:{group:"insert",rank:20,display:{type:"label",label:"Image"}}}),o.BulletList.register("command","wrap",{derive:{list:!0},label:"Wrap the selection in a bullet list",menu:{group:"block",rank:40,display:{type:"icon",width:768,height:896,path:"M0 512h128v-128h-128v128zM0 256h128v-128h-128v128zM0 768h128v-128h-128v128zM256 512h512v-128h-512v128zM256 256h512v-128h-512v128zM256 768h512v-128h-512v128z"}},keys:["Shift-Ctrl-8"]}),o.OrderedList.register("command","wrap",{derive:{list:!0},label:"Wrap the selection in an ordered list",menu:{group:"block",rank:41,display:{type:"icon",width:768,height:896,path:"M320 512h448v-128h-448v128zM320 768h448v-128h-448v128zM320 128v128h448v-128h-448zM79 384h78v-256h-36l-85 23v50l43-2v185zM189 590c0-36-12-78-96-78-33 0-64 6-83 16l1 66c21-10 42-15 67-15s32 11 32 28c0 26-30 58-110 112v50h192v-67l-91 2c49-30 87-66 87-113l1-1z"}},keys:["Shift-Ctrl-9"]}),o.BlockQuote.register("command","wrap",{derive:!0,label:"Wrap the selection in a block quote",menu:{group:"block",rank:45,display:{type:"icon",width:640,height:896,path:"M0 448v256h256v-256h-128c0 0 0-128 128-128v-128c0 0-256 0-256 256zM640 320v-128c0 0-256 0-256 256v256h256v-256h-128c0 0 0-128 128-128z"}},keys:["Shift-Ctrl-."]}),o.HardBreak.register("command","insert",{label:"Insert hard break",run:function(e){var t=e.selection,n=t.node,r=t.from;return n&&n.isBlock?!1:e.doc.resolve(r).parent.type.isCode?e.tr.typeText("\n").apply(e.apply.scroll):e.tr.replaceSelection(this.create()).apply(e.apply.scroll)},keys:{all:["Mod-Enter","Shift-Enter"],mac:["Ctrl-Enter"]}}),o.ListItem.register("command","split",{label:"Split the current list item",run:function(e){var t=e.selection,n=t.from,r=t.to,o=t.node,i=e.doc.resolve(n);if(o&&o.isBlock||i.depth<2||!i.sameParent(e.doc.resolve(r)))return!1;var a=i.node(i.depth-1);if(a.type!=this)return!1;var s=r==i.end(i.depth)?e.schema.defaultTextblockType():null;return e.tr["delete"](n,r).split(n,2,s).apply(e.apply.scroll)},keys:["Enter(50)"]}),o.ListItem.register("command","lift",{label:"Lift the selected list items to an outer list",run:function(e){var t=r(e,this);if(!t||t.depth<3)return!1;var n=e.doc.resolve(e.selection.to);if(n.node(t.depth-2).type!=this)return!1;var o=t.to<n.end(t.depth-1),i=e.tr.splitIfNeeded(t.to,2).splitIfNeeded(t.from,2),a=i.map(t.to,-1);return i.step("ancestor",i.map(t.from),a,{depth:2}),o&&i.join(a-2),i.apply(e.apply.scroll)},keys:["Mod-[(20)"]}),o.ListItem.register("command","sink",{label:"Sink the selected list items into an inner list",run:function(e){var t=r(e,this);if(!t)return!1;var n=e.doc.resolve(e.selection.from),o=n.index(t.depth-1);if(0==o)return!1;var i=n.node(t.depth-1),a=i.child(o-1),s=e.tr.wrap(t.from,t.to,i.type,i.attrs);return a.type==this&&s.join(t.from,a.lastChild&&a.lastChild.type==i.type?2:1),s.apply(e.apply.scroll)},keys:["Mod-](20)"]});for(var u=function(e){o.Heading.registerComputed("command","make"+e,function(t){var n={level:String(e)};return e<=t.maxLevel?{derive:{name:"make",attrs:n},label:"Change to heading "+e,keys:6>=e&&["Shift-Ctrl-"+e],menu:{group:"textblockHeading",rank:30+e,display:{type:"label",label:"Level "+e},activeDisplay:"Head "+e}}:void 0})},l=1;10>=l;l++)u(l);o.Paragraph.register("command","make",{derive:!0,label:"Change to paragraph",keys:["Shift-Ctrl-0"],menu:{group:"textblock",rank:10,display:{type:"label",label:"Plain"},activeDisplay:"Plain"}}),o.CodeBlock.register("command","make",{derive:!0,label:"Change to code block",keys:["Shift-Ctrl-\\"],menu:{group:"textblock",rank:20,display:{type:"label",label:"Code"},activeDisplay:"Code"}}),o.HorizontalRule.register("command","insert",{derive:!0,label:"Insert horizontal rule",keys:["Mod-Shift--"],menu:{group:"insert",rank:70,display:{type:"label",label:"Horizontal rule"}}})},{"../format":23,"../model":38,"./command":7}],19:[function(e,t,n){"use strict";function r(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function o(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e){if(document.activeElement!=e.content)return!1;var t=window.getSelection();return t.rangeCount&&(0,d.contains)(e.content,t.anchorNode)}function s(e,t,n,r,o){for(var i=n-(r>0?0:1);r>0?i<e.childCount:i>=0;i+=r){var a=e.child(i);if(a.isTextblock)return new g(t+r);if(a.type.contains){var u=s(a,t+r,0>r?a.childCount:0,r,o);if(u)return u}else if(!o&&a.type.selectable)return new y(t-(0>r?a.nodeSize:0),t+(r>0?a.nodeSize:0),a);t+=a.nodeSize*r}}function u(e,t,n,r){var o=e.resolve(t),i=o.parent.isTextblock?new g(t):s(o.parent,t,o.index(o.depth),n,r);if(i)return i;for(var a=o.depth-1;a>=0;a--){var u=0>n?s(o.node(a),o.before(a+1),o.index(a),n,r):s(o.node(a),o.after(a+1),o.index(a)+1,n,r);if(u)return u}}function l(e,t){var n=arguments.length<=2||void 0===arguments[2]?1:arguments[2],r=arguments[3],o=u(e,t,n,r)||u(e,t,-n,r);if(!o)throw new RangeError("Searching for selection in invalid document "+e);return o}function c(e,t){return s(e,0,0,1,t)}function p(e,t){return s(e,e.content.size,e.childCount,-1,t)}function f(e,t,n){for(var r=e.doc.resolve(t),o=(0,m.DOMAfterPos)(e,r.before(r.depth)),i=(0,m.coordsAtPos)(e,t),a=o.firstChild;a;a=a.nextSibling)if(1==a.nodeType)for(var s=a.getClientRects(),u=0;u<s.length;u++){var l=s[u];if(0>n?l.bottom<i.top:l.top>i.bottom)return!1}return!0}var h=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.NodeSelection=n.TextSelection=n.Selection=n.SelectionState=void 0,n.hasFocus=a,n.findSelectionFrom=u,n.findSelectionNear=l,n.findSelectionAtStart=c,n.findSelectionAtEnd=p,n.verticalMotionLeavesTextblock=f;var d=e("../dom"),m=e("./dompos"),v=(n.SelectionState=function(){function e(t,n){var r=this;i(this,e),this.pm=t,this.range=n,this.polling=null,this.lastAnchorNode=this.lastHeadNode=this.lastAnchorOffset=this.lastHeadOffset=null,this.lastNode=null,t.content.addEventListener("focus",function(){return r.receivedFocus()}),this.poller=this.poller.bind(this)}return h(e,[{key:"setAndSignal",value:function(e,t){this.set(e,t),this.pm.signal("selectionChange")}},{key:"set",value:function(e,t){this.pm.ensureOperation({readSelection:!1}),this.range=e,t!==!1&&(this.lastAnchorNode=null)}},{key:"poller",value:function(){a(this.pm)?(this.pm.operation||this.readFromDOM(),this.polling=setTimeout(this.poller,100)):this.polling=null}},{key:"startPolling",value:function(){clearTimeout(this.polling),this.polling=setTimeout(this.poller,50)}},{key:"fastPoll",value:function(){this.startPolling()}},{key:"stopPolling",value:function(){clearTimeout(this.polling),this.polling=null}},{key:"domChanged",value:function(){var e=window.getSelection();return e.anchorNode!=this.lastAnchorNode||e.anchorOffset!=this.lastAnchorOffset||e.focusNode!=this.lastHeadNode||e.focusOffset!=this.lastHeadOffset}},{key:"storeDOMState",value:function(){var e=window.getSelection();this.lastAnchorNode=e.anchorNode,this.lastAnchorOffset=e.anchorOffset,this.lastHeadNode=e.focusNode,this.lastHeadOffset=e.focusOffset}},{key:"readFromDOM",value:function(){if(!a(this.pm)||!this.domChanged())return!1;var e=window.getSelection(),t=this.pm.doc,n=(0,m.posFromDOM)(this.pm,e.anchorNode,e.anchorOffset),r=e.isCollapsed?n:(0,m.posFromDOM)(this.pm,e.focusNode,e.focusOffset),o=l(t,r,null!=this.range.head&&this.range.head<r?1:-1);if(o instanceof g){var i=l(t,n,n>o.to?-1:1,!0);o=new g(i.anchor,o.head)}else if(n<o.from||n>o.to){var s=n>o.to;o=new g(l(t,n,s?-1:1,!0).anchor,l(t,s?o.from:o.to,s?1:-1,!0).head)}return this.setAndSignal(o),o instanceof y||o.head!=r||o.anchor!=n?this.toDOM():(this.clearNode(),this.storeDOMState()),!0}},{key:"toDOM",value:function(e){if(!a(this.pm)){if(!e)return;d.browser.gecko&&this.pm.content.focus()}this.range instanceof y?this.nodeToDOM():this.rangeToDOM()}},{key:"nodeToDOM",value:function(){var e=(0,m.DOMAfterPos)(this.pm,this.range.from);e!=this.lastNode&&(this.clearNode(),e.classList.add("ProseMirror-selectednode"),this.pm.content.classList.add("ProseMirror-nodeselection"),this.lastNode=e);var t=document.createRange(),n=window.getSelection();t.selectNode(e),n.removeAllRanges(),n.addRange(t),this.storeDOMState()}},{key:"rangeToDOM",value:function(){this.clearNode();var e=(0,m.DOMFromPos)(this.pm,this.range.anchor),t=(0,m.DOMFromPos)(this.pm,this.range.head),n=window.getSelection(),r=document.createRange();if(n.extend)r.setEnd(e.node,e.offset),r.collapse(!1);else{if(this.range.anchor>this.range.head){var o=e;e=t,t=o}r.setEnd(t.node,t.offset),r.setStart(e.node,e.offset)}n.removeAllRanges(),n.addRange(r),n.extend&&n.extend(t.node,t.offset),this.storeDOMState()}},{key:"clearNode",value:function(){return this.lastNode?(this.lastNode.classList.remove("ProseMirror-selectednode"),this.pm.content.classList.remove("ProseMirror-nodeselection"),this.lastNode=null,!0):void 0}},{key:"receivedFocus",value:function(){null==this.polling&&this.startPolling()}}]),e}(),n.Selection=function b(){i(this,b)}),g=n.TextSelection=function(e){function t(e,n){i(this,t);var o=r(this,Object.getPrototypeOf(t).call(this));return o.anchor=e,o.head=null==n?e:n,o}return o(t,e),h(t,[{key:"eq",value:function(e){return e instanceof t&&e.head==this.head&&e.anchor==this.anchor}},{key:"map",value:function(e,n){var r=n.map(this.head);if(!e.resolve(r).parent.isTextblock)return l(e,r);var o=n.map(this.anchor);return new t(e.resolve(o).parent.isTextblock?o:r,r)}},{key:"inverted",get:function(){return this.anchor>this.head}},{key:"from",get:function(){return Math.min(this.head,this.anchor)}},{key:"to",get:function(){return Math.max(this.head,this.anchor)}},{key:"empty",get:function(){return this.anchor==this.head}},{key:"token",get:function(){return new k(t,this.anchor,this.head)}}],[{key:"mapToken",value:function(e,n){return new k(t,n.map(e.a),n.map(e.b))}},{key:"fromToken",value:function(e,n){return n.resolve(e.b).parent.isTextblock?new t(n.resolve(e.a).parent.isTextblock?e.a:e.b,e.b):l(n,e.b)}}]),t}(v),y=n.NodeSelection=function(e){function t(e,n,o){i(this,t);var a=r(this,Object.getPrototypeOf(t).call(this));return a.from=e,a.to=n,a.node=o,a}return o(t,e),h(t,[{key:"eq",value:function(e){return e instanceof t&&this.from==e.from}},{key:"map",value:function(e,n){var r=n.map(this.from,1),o=n.map(this.to,-1),i=e.nodeAt(r);return i&&o==r+i.nodeSize&&i.type.selectable?new t(r,o,i):l(e,r)}},{key:"empty",get:function(){return!1}},{key:"token",get:function(){return new k(t,this.from,this.to)}}],[{key:"mapToken",value:function(e,t){return new k(g,t.map(e.a,1),t.map(e.b,-1))}},{key:"fromToken",value:function(e,n){var r=n.nodeAt(e.a);return r&&e.b==e.a+r.nodeSize&&r.type.selectable?new t(e.a,e.b,r):l(n,e.a)}}]),t}(v),k=function w(e,t,n){i(this,w),this.type=e,this.a=t,this.b=n}},{"../dom":3,"./dompos":10}],20:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var a=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.EditorTransform=void 0;var s=e("../transform");n.EditorTransform=function(e){function t(e){r(this,t);var n=o(this,Object.getPrototypeOf(t).call(this,e.doc));return n.pm=e,n}return i(t,e),a(t,[{key:"apply",value:function(e){return this.pm.apply(this,e)}},{key:"replaceSelection",value:function(e,t){var n=this.selection,r=n.empty,o=n.from,i=n.to,a=n.node;if(e&&e.isInline&&t!==!1&&(e=e.mark(r?this.pm.input.storedMarks:this.doc.marksAt(o))),a&&a.isTextblock&&e&&e.isInline)o++,i--;else if(a){for(var s=this.doc.resolve(o),u=s.depth;u&&1==s.node(u).childCount&&!(e?s.node(u).type.canContain(e):s.node(u).type.canBeEmpty);)u--;u<s.depth&&(o=s.before(u+1),i=s.after(u+1))}else if(e&&e.isBlock){var s=this.doc.resolve(o);if(s.depth&&s.node(s.depth-1).type.canContain(e))return this["delete"](o,i),s.parentOffset&&s.parentOffset<s.parent.content.size&&this.split(o),this.insert(o+(s.parentOffset?1:-1),e)}return this.replaceWith(o,i,e)}},{key:"deleteSelection",value:function(){return this.replaceSelection()}},{key:"typeText",value:function(e){return this.replaceSelection(this.pm.schema.text(e),!0)}},{key:"selection",get:function(){return this.steps.length?this.pm.selection.map(this):this.pm.selection}}]),t}(s.Transform)},{"../transform":45}],21:[function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t,n){n||(n={});var r=new b(e,n.topNode||e.node("doc"),n),o=n.from?t.childNodes[n.from]:t.firstChild,i=null!=n.to&&t.childNodes[n.to]||null;r.addAll(o,i,!0);var a=void 0;do a=r.leave();while(r.stack.length);return a}function a(e,t,n){var r=(n&&n.document||window.document).createElement("div");return r.innerHTML=t,i(e,r,n)}function s(e,t){return(e.matches||e.msMatchesSelector||e.webkitMatchesSelector||e.mozMatchesSelector).call(e,t)}function u(e){for(var t=/\s*([\w-]+)\s*:\s*([^;]+)/g,n=void 0,r=[];n=t.exec(e);)r.push(n[1],n[2].trim());return r}function l(e){return e.cached.parseDOMInfo||(e.cached.parseDOMInfo=c(e))}function c(e){var t=Object.create(null),n=Object.create(null);return t._=[],e.registry("parseDOM",function(e,n,r){var o=n.parse;"block"==o?o=function(e,t){t.wrapIn(e,this)}:"mark"==o&&(o=function(e,t){t.wrapMark(e,this)}),(0,d["default"])(t[e]||(t[e]=[]),{type:r,parse:o,selector:n.selector,rank:null==n.rank?50:n.rank},function(e,t){return e.rank-t.rank})}),e.registry("parseDOMStyle",function(e,t,r){(0,d["default"])(n[e]||(n[e]=[]),{type:r,parse:t.parse,rank:null==t.rank?50:t.rank},function(e,t){return e.rank-t.rank})}),{tags:t,styles:n}}var p=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.fromDOM=i,n.fromHTML=a;var f=e("../model"),h=e("../util/sortedinsert"),d=r(h),m=e("./register");(0,m.defineSource)("dom",i),(0,m.defineSource)("html",a);var v={address:!0,article:!0,aside:!0,blockquote:!0,canvas:!0,dd:!0,div:!0,dl:!0,fieldset:!0,figcaption:!0,figure:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,li:!0,noscript:!0,ol:!0,output:!0,p:!0,pre:!0,section:!0,table:!0,tfoot:!0,ul:!0},g={head:!0,noscript:!0,object:!0,script:!0,style:!0,title:!0},y={ol:!0,ul:!0},k=[],b=function(){function e(t,n,r){o(this,e),this.options=r||{},this.schema=t,this.stack=[],this.marks=k,this.closing=!1,this.enter(n.type,n.attrs);var i=l(t);this.tagInfo=i.tags,this.styleInfo=i.styles}return p(e,[{key:"addDOM",value:function(e){if(3==e.nodeType){var t=e.nodeValue,n=this.top,r=void 0;(/\S/.test(t)||n.type.isTextblock)&&(this.options.preserveWhitespace||(t=t.replace(/\s+/g," "),/^\s/.test(t)&&(!(r=n.content[n.content.length-1])||"text"==r.type.name&&/\s$/.test(r.text))&&(t=t.slice(1))),t&&this.insertNode(this.schema.text(t,this.marks)))}else if(1==e.nodeType&&!e.hasAttribute("pm-ignore")){var o=e.getAttribute("style");o?this.addElementWithStyles(u(o),e):this.addElement(e)}}},{key:"addElement",value:function(e){var t=e.nodeName.toLowerCase();y.hasOwnProperty(t)&&this.normalizeList(e),(!this.options.editableContent||"br"!=t||e.nextSibling)&&(this.parseNodeType(t,e)||g.hasOwnProperty(t)||(this.addAll(e.firstChild,null),v.hasOwnProperty(t)&&this.top.type==this.schema.defaultTextblockType()&&(this.closing=!0)))}},{key:"addElementWithStyles",value:function(e,t){for(var n=this,r=[],o=0;o<e.length;o+=2){var i=this.styleInfo[e[o]],a=e[o+1];if(i)for(var s=0;s<i.length;s++)r.push(i[s],a)}var u=function l(e){if(e==r.length)n.addElement(t);else{var o=r[e];o.parse.call(o.type,r[e+1],n,l.bind(null,e+2))}};u(0)}},{key:"tryParsers",value:function(e,t){if(e)for(var n=0;n<e.length;n++){var r=e[n];if((!r.selector||s(t,r.selector))&&r.parse.call(r.type,t,this)!==!1)return!0}}},{key:"parseNodeType",value:function(e,t){return this.tryParsers(this.tagInfo[e],t)||this.tryParsers(this.tagInfo._,t)}},{key:"addAll",value:function(e,t,n){for(var r=n&&this.stack.slice(),o=e;o!=t;o=o.nextSibling)this.addDOM(o),
+n&&v.hasOwnProperty(o.nodeName.toLowerCase())&&this.sync(r)}},{key:"doClose",value:function(){if(this.closing&&!(this.stack.length<2)){var e=this.leave();this.enter(e.type,e.attrs),this.closing=!1}}},{key:"insertNode",value:function(e){if(this.top.type.canContain(e))this.doClose();else{for(var t=void 0,n=this.stack.length-1;n>=0;n--){var r=this.stack[n].type.findConnection(e.type);if(r){if(n==this.stack.length-1)this.doClose();else for(;this.stack.length>n+1;)this.leave();t=r;break}}if(!t)return;for(var o=0;o<t.length;o++)this.enter(t[o]);this.marks.length&&(this.marks=k)}return this.top.content.push(e),e}},{key:"close",value:function(e,t,n){return n=f.Fragment.from(n),e.checkContent(n,t)||(n=e.fixContent(n,t))?e.create(t,n,this.marks):null}},{key:"insert",value:function(e,t,n){var r=this.close(e,t,n);return r?this.insertNode(r):void 0}},{key:"enter",value:function(e,t){this.stack.push({type:e,attrs:t,content:[]})}},{key:"leave",value:function(){this.marks.length&&(this.marks=k);var e=this.stack.pop(),t=e.content[e.content.length-1];!this.options.preserveWhitespace&&t&&t.isText&&/\s$/.test(t.text)&&(1==t.text.length?e.content.pop():e.content[e.content.length-1]=t.copy(t.text.slice(0,t.text.length-1)));var n=this.close(e.type,e.attrs,e.content);return n&&this.stack.length&&this.insertNode(n),n}},{key:"sync",value:function(e){for(;this.stack.length>e.length;)this.leave();for(;;){var t=this.stack.length-1,n=this.stack[t],r=e[t];if(n.type==r.type&&f.Node.sameAttrs(n.attrs,r.attrs))break;this.leave()}for(;e.length>this.stack.length;){var o=e[this.stack.length];this.enter(o.type,o.attrs)}this.marks.length&&(this.marks=k),this.closing=!1}},{key:"wrapIn",value:function(e,t,n){this.enter(t,n),this.addAll(e.firstChild,null,!0),this.leave()}},{key:"wrapMark",value:function(e,t){var n=this.marks;this.marks=(t.instance||t).addToSet(n),e.call?e():this.addAll(e.firstChild,null),this.marks=n}},{key:"normalizeList",value:function(e){for(var t,n=e.firstChild;n;n=n.nextSibling)1==n.nodeType&&y.hasOwnProperty(n.nodeName.toLowerCase())&&(t=n.previousSibling)&&(t.appendChild(n),n=t)}},{key:"top",get:function(){return this.stack[this.stack.length-1]}}]),e}();f.Paragraph.register("parseDOM","p",{parse:"block"}),f.BlockQuote.register("parseDOM","blockquote",{parse:"block"});for(var w=function(e){f.Heading.registerComputed("parseDOM","h"+e,function(t){return e<=t.maxLevel?{parse:function(t,n){n.wrapIn(t,this,{level:String(e)})}}:void 0})},_=1;6>=_;_++)w(_);f.HorizontalRule.register("parseDOM","hr",{parse:"block"}),f.CodeBlock.register("parseDOM","pre",{parse:function(e,t){var n=e.firstChild&&/^code$/i.test(e.firstChild.nodeName)&&e.firstChild.getAttribute("class");if(n&&/fence/.test(n)){for(var r=[],o=/(?:^|\s)lang-(\S+)/g,i=void 0;i=o.exec(n);)r.push(i[1]);n=r.join(" ")}else n=null;var a=e.textContent;t.insert(this,{params:n},a?[t.schema.text(a)]:[])}}),f.BulletList.register("parseDOM","ul",{parse:"block"}),f.OrderedList.register("parseDOM","ol",{parse:function(e,t){var n={order:e.getAttribute("start")||"1"};t.wrapIn(e,this,n)}}),f.ListItem.register("parseDOM","li",{parse:"block"}),f.HardBreak.register("parseDOM","br",{parse:function(e,t){t.insert(this)}}),f.Image.register("parseDOM","img",{parse:function(e,t){t.insert(this,{src:e.getAttribute("src"),title:e.getAttribute("title")||null,alt:e.getAttribute("alt")||null})}}),f.LinkMark.register("parseDOM","a",{parse:function(e,t){t.wrapMark(e,this.create({href:e.getAttribute("href"),title:e.getAttribute("title")}))},selector:"[href]"}),f.EmMark.register("parseDOM","i",{parse:"mark"}),f.EmMark.register("parseDOM","em",{parse:"mark"}),f.EmMark.register("parseDOMStyle","font-style",{parse:function(e,t,n){"italic"==e?t.wrapMark(n,this):n()}}),f.StrongMark.register("parseDOM","b",{parse:"mark"}),f.StrongMark.register("parseDOM","strong",{parse:"mark"}),f.StrongMark.register("parseDOMStyle","font-weight",{parse:function(e,t,n){"bold"==e||"bolder"==e||!/\D/.test(e)&&+e>=500?t.wrapMark(n,this):n()}}),f.CodeMark.register("parseDOM","code",{parse:"mark"})},{"../model":38,"../util/sortedinsert":59,"./register":24}],22:[function(e,t,n){"use strict";function r(e,t){for(var n=t.trim().split(/\n{2,}/),r=[],o=0;o<n.length;o++){for(var i=[],a=n[o].split("\n"),s=0;s<a.length;s++)s&&i.push(e.node("hard_break")),a[s]&&i.push(e.text(a[s]));r.push(e.node("paragraph",null,i))}return r.length||r.push(e.node("paragraph")),e.node("doc",null,r)}Object.defineProperty(n,"__esModule",{value:!0}),n.fromText=r;var o=e("./register");(0,o.defineSource)("text",r)},{"./register":24}],23:[function(e,t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r=e("./register");Object.defineProperty(n,"serializeTo",{enumerable:!0,get:function(){return r.serializeTo}}),Object.defineProperty(n,"knownTarget",{enumerable:!0,get:function(){return r.knownTarget}}),Object.defineProperty(n,"defineTarget",{enumerable:!0,get:function(){return r.defineTarget}}),Object.defineProperty(n,"parseFrom",{enumerable:!0,get:function(){return r.parseFrom}}),Object.defineProperty(n,"knownSource",{enumerable:!0,get:function(){return r.knownSource}}),Object.defineProperty(n,"defineSource",{enumerable:!0,get:function(){return r.defineSource}});var o=e("./from_dom");Object.defineProperty(n,"fromDOM",{enumerable:!0,get:function(){return o.fromDOM}}),Object.defineProperty(n,"fromHTML",{enumerable:!0,get:function(){return o.fromHTML}});var i=e("./to_dom");Object.defineProperty(n,"toDOM",{enumerable:!0,get:function(){return i.toDOM}}),Object.defineProperty(n,"toHTML",{enumerable:!0,get:function(){return i.toHTML}}),Object.defineProperty(n,"nodeToDOM",{enumerable:!0,get:function(){return i.nodeToDOM}});var a=e("./from_text");Object.defineProperty(n,"fromText",{enumerable:!0,get:function(){return a.fromText}});var s=e("./to_text");Object.defineProperty(n,"toText",{enumerable:!0,get:function(){return s.toText}})},{"./from_dom":21,"./from_text":22,"./register":24,"./to_dom":25,"./to_text":26}],24:[function(e,t,n){"use strict";function r(e,t,n){var r=l[t];if(!r)throw new RangeError("Target format "+t+" not defined");return r(e,n)}function o(e){return!!l[e]}function i(e,t){l[e]=t}function a(e,t,n,r){var o=c[n];if(!o)throw new RangeError("Source format "+n+" not defined");return o(e,t,r)}function s(e){return!!c[e]}function u(e,t){c[e]=t}Object.defineProperty(n,"__esModule",{value:!0}),n.serializeTo=r,n.knownTarget=o,n.defineTarget=i,n.parseFrom=a,n.knownSource=s,n.defineSource=u;var l=Object.create(null);i("json",function(e){return e.toJSON()});var c=Object.create(null);u("json",function(e,t){return e.nodeFromJSON(t)})},{}],25:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){return new p(t).renderFragment(e instanceof l.Node?e.content:e)}function i(e,t,n){var r=new p(t),o=r.renderNode(e,n);return e.isInline&&(o=r.wrapInlineFlat(o,e.marks),r.options.renderInlineFlat&&(o=t.renderInlineFlat(e,o,n)||o)),o}function a(e,t){var n=new p(t),r=n.elt("div");return r.appendChild(n.renderFragment(e instanceof l.Node?e.content:e)),r.innerHTML}function s(e,t){e.prototype.serializeDOM=t}var u=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.toDOM=o,n.nodeToDOM=i,n.toHTML=a;var l=e("../model"),c=e("./register"),p=function(){function e(t){r(this,e),this.options=t||{},this.doc=this.options.document||window.document}return u(e,[{key:"elt",value:function(e,t){var n=this.doc.createElement(e);if(t)for(var r in t)"style"==r?n.style.cssText=t[r]:t[r]&&n.setAttribute(r,t[r]);for(var o=arguments.length,i=Array(o>2?o-2:0),a=2;o>a;a++)i[a-2]=arguments[a];for(var s=0;s<i.length;s++)n.appendChild("string"==typeof i[s]?this.doc.createTextNode(i[s]):i[s]);return n}},{key:"renderNode",value:function(e,t){var n=e.type.serializeDOM(e,this);return this.options.onRender&&(n=this.options.onRender(e,n,t)||n),n}},{key:"renderFragment",value:function(e,t){return t||(t=this.doc.createDocumentFragment()),0==e.size?t:(e.firstChild.isInline?this.options.renderInlineFlat?this.renderInlineFlatInto(e,t):this.renderInlineInto(e,t):this.renderBlocksInto(e,t),t)}},{key:"renderBlocksInto",value:function(e,t){var n=this;e.forEach(function(e,r){return t.appendChild(n.renderNode(e,r))})}},{key:"renderInlineInto",value:function(e,t){var n=this,r=t,o=[];e.forEach(function(e,t){for(var i=0;i<Math.min(o.length,e.marks.length)&&e.marks[i].eq(o[i]);++i);for(;i<o.length;)o.pop(),r=r.parentNode;for(;o.length<e.marks.length;){var a=e.marks[o.length];o.push(a),r=r.appendChild(n.renderMark(a))}r.appendChild(n.renderNode(e,t))})}},{key:"renderInlineFlatInto",value:function(e,t){var n=this;e.forEach(function(e,r){var o=n.renderNode(e,r);o=n.wrapInlineFlat(o,e.marks),o=n.options.renderInlineFlat(e,o,r)||o,t.appendChild(o)})}},{key:"renderMark",value:function(e){return e.type.serializeDOM(e,this)}},{key:"wrapInlineFlat",value:function(e,t){for(var n=t.length-1;n>=0;n--){var r=this.renderMark(t[n]);r.appendChild(e),e=r}return e}},{key:"renderAs",value:function(e,t,n){this.options.preRenderContent&&this.options.preRenderContent(e);var r=this.renderFragment(e.content,this.elt(t,n));return this.options.onContainer&&this.options.onContainer(r),this.options.postRenderContent&&this.options.postRenderContent(e),r}}]),e}();(0,c.defineTarget)("dom",o),(0,c.defineTarget)("html",a),s(l.BlockQuote,function(e,t){return t.renderAs(e,"blockquote")}),l.BlockQuote.prototype.countCoordsAsChild=function(e,t,n,r){var o=n.firstChild.getBoundingClientRect();return r.left<o.left-2?t:void 0},s(l.BulletList,function(e,t){return t.renderAs(e,"ul")}),s(l.OrderedList,function(e,t){return t.renderAs(e,"ol",{start:"1"!=e.attrs.order&&e.attrs.order})}),l.OrderedList.prototype.countCoordsAsChild=l.BulletList.prototype.countCoordsAsChild=function(e,t,n,r){for(var o=n.firstChild;o;o=o.nextSibling){var i=o.getAttribute("pm-offset");if(i){var a=o.getBoundingClientRect();if(r.left>a.left-2)return null;if(a.top<=r.top&&a.bottom>=r.top)return t+1+ +i}}},s(l.ListItem,function(e,t){return t.renderAs(e,"li")}),s(l.HorizontalRule,function(e,t){return t.elt("div",null,t.elt("hr"))}),s(l.Paragraph,function(e,t){return t.renderAs(e,"p")}),s(l.Heading,function(e,t){return t.renderAs(e,"h"+e.attrs.level)}),s(l.CodeBlock,function(e,t){var n=t.renderAs(e,"code");return null!=e.attrs.params&&(n.className="fence "+e.attrs.params.replace(/(^|\s+)/g,"$&lang-")),t.elt("pre",null,n)}),s(l.Text,function(e,t){return t.doc.createTextNode(e.text)}),s(l.Image,function(e,t){return t.elt("img",{src:e.attrs.src,alt:e.attrs.alt,title:e.attrs.title})}),s(l.HardBreak,function(e,t){return t.elt("br")}),s(l.EmMark,function(e,t){return t.elt("em")}),s(l.StrongMark,function(e,t){return t.elt("strong")}),s(l.CodeMark,function(e,t){return t.elt("code")}),s(l.LinkMark,function(e,t){return t.elt("a",{href:e.attrs.href,title:e.attrs.title})})},{"../model":38,"./register":24}],26:[function(e,t,n){"use strict";function r(e){var t="";return e.forEach(function(e){return t+=e.type.serializeText(e)}),t}function o(e){return r(e).trim()}Object.defineProperty(n,"__esModule",{value:!0}),n.toText=o;var i=e("../model"),a=e("./register");i.Block.prototype.serializeText=function(e){return r(e.content)},i.Textblock.prototype.serializeText=function(e){var t=i.Block.prototype.serializeText(e);return t&&t+"\n\n"},i.Inline.prototype.serializeText=function(){return""},i.HardBreak.prototype.serializeText=function(){return"\n"},i.Text.prototype.serializeText=function(e){return e.text},(0,a.defineTarget)("text",o)},{"../model":38,"./register":24}],27:[function(e,t,n){"use strict";function r(e,t,n){var r=arguments.length<=3||void 0===arguments[3]?null:arguments[3],o=arguments.length<=4||void 0===arguments[4]?null:arguments[4],i=e.doc.resolve(t),a=i.depth-1,s=i.index(a)>0&&i.node(a).child(i.index(a)-1),u=s&&s.type==n&&(!o||o(s)),l=t-i.parentOffset,c=e.tr["delete"](l,t).wrap(l,l,n,r);u&&c.join(i.before(i.depth)),c.apply()}function o(e,t,n,r){var o=e.doc.resolve(t),i=t-o.parentOffset;e.tr["delete"](i,t).setBlockType(i,i,n,r).apply()}Object.defineProperty(n,"__esModule",{value:!0}),n.autoInputRules=void 0;var i=e("../model"),a=e("../edit"),s=e("./inputrules"),u=n.autoInputRules=Object.create(null);(0,a.defineOption)("autoInput",!1,function(e,t){e.mod.autoInput&&(e.mod.autoInput.forEach(function(t){return(0,s.removeInputRule)(e,t)}),e.mod.autoInput=null),t&&!function(){t===!0&&(t=["schema",u]);var n=Object.create(null),r=e.mod.autoInput=[];t.forEach(function(t){if("schema"===t)e.schema.registry("autoInput",function(e,t,r,o){var i=o+":"+e,a=t.handler;a.bind&&(a=a.bind(r)),n[i]=new s.InputRule(t.match,t.filter,a)});else for(var r in t){var o=t[r];null==o?delete n[r]:n[r]=o}});for(var o in n)(0,s.addInputRule)(e,n[o]),r.push(n[o])}()}),u.emDash=new s.InputRule(/--$/,"-","—"),u.openDoubleQuote=new s.InputRule(/(?:^|[\s\{\[\(\<'"\u2018\u201C])(")$/,'"',"“"),u.closeDoubleQuote=new s.InputRule(/"$/,'"',"”"),u.openSingleQuote=new s.InputRule(/(?:^|[\s\{\[\(\<'"\u2018\u201C])(')$/,"'","‘"),u.closeSingleQuote=new s.InputRule(/'$/,"'","’"),i.BlockQuote.register("autoInput","startBlockQuote",new s.InputRule(/^\s*> $/," ",function(e,t,n){r(e,n,this)})),i.OrderedList.register("autoInput","startOrderedList",new s.InputRule(/^(\d+)\. $/," ",function(e,t,n){var o=+t[1];r(e,n,this,{order:o||null},function(e){return e.childCount+ +e.attrs.order==o})})),i.BulletList.register("autoInput","startBulletList",new s.InputRule(/^\s*([-+*]) $/," ",function(e,t,n){var o=t[1];r(e,n,this,null,function(e){return e.attrs.bullet==o})})),i.CodeBlock.register("autoInput","startCodeBlock",new s.InputRule(/^```$/,"`",function(e,t,n){o(e,n,this,{params:""})})),i.Heading.registerComputed("autoInput","startHeading",function(e){var t=new RegExp("^(#{1,"+e.maxLevel+"}) $");return new s.InputRule(t," ",function(e,t,n){o(e,n,this,{level:t[1].length})})})},{"../edit":13,"../model":38,"./inputrules":28}],28:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){e.mod.interpretInput||(e.mod.interpretInput=new l(e)),e.mod.interpretInput.addRule(t)}function i(e,t){var n=e.mod.interpretInput;n&&(n.removeRule(t),0==n.rules.length&&(n.unregister(),e.mod.interpretInput=null))}function a(e){for(var t=e.parent,n=t.type.isCode,r="",o=0,i=e.parentOffset;i>0;o++){var a=t.child(o);r+=a.isText?a.text.slice(0,i):"",i-=a.nodeSize,0>=i&&a.marks.some(function(e){return e.type.isCode})&&(n=!0)}return{textBefore:r,isCode:n}}var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.InputRule=void 0,n.addInputRule=o,n.removeInputRule=i;var u=e("../edit"),l=(n.InputRule=function c(e,t,n){r(this,c),this.filter=t,this.match=e,this.handler=n},function(){function e(t){var n=this;r(this,e),this.pm=t,this.rules=[],this.cancelVersion=null,t.on("selectionChange",this.onSelChange=function(){return n.cancelVersion=null}),t.on("textInput",this.onTextInput=this.onTextInput.bind(this)),t.addKeymap(new u.Keymap({Backspace:function(e){return n.backspace(e)}},{name:"inputRules"}),20)}return s(e,[{key:"unregister",value:function(){this.pm.off("selectionChange",this.onSelChange),this.pm.off("textInput",this.onTextInput),this.pm.removeKeymap("inputRules")}},{key:"addRule",value:function(e){this.rules.push(e)}},{key:"removeRule",value:function(e){var t=this.rules.indexOf(e);return t>-1?(this.rules.splice(t,1),!0):void 0}},{key:"onTextInput",value:function(e){var t=this.pm.selection.head;if(t)for(var n=void 0,r=void 0,o=void 0,i=e[e.length-1],s=0;s<this.rules.length;s++){var u=this.rules[s],l=void 0;if(!u.filter||u.filter==i){if(!o){o=this.pm.doc.resolve(t);var c=a(o);if(n=c.textBefore,r=c.isCode)return}if(l=u.match.exec(n)){var p=this.pm.history.getVersion();if("string"==typeof u.handler){var f=t-(l[1]||l[0]).length,h=this.pm.doc.marksAt(t);this.pm.tr["delete"](f,t).insert(f,this.pm.schema.text(u.handler,h)).apply()}else u.handler(this.pm,l,t);return void(this.cancelVersion=p)}}}}},{key:"backspace",value:function(){return this.cancelVersion?(this.pm.history.backToVersion(this.cancelVersion),void(this.cancelVersion=null)):!1}}]),e}())},{"../edit":13}],29:[function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t,n){var r=p(e).parse(t,{}),o=new _(e,r,n),i=void 0;o.parseTokens(r);do i=o.closeNode();while(o.stack.length);return i}function a(e,t){return e.isText&&t.isText&&g.Mark.sameSet(e.marks,t.marks)?e.copy(e.text+t.text):void 0}function s(e){return e.cached.markdownTokens||(e.cached.markdownTokens=l(e))}function u(e,t,n,r){if("block"==r.parse)e[t+"_open"]=function(e,t){var o="function"==typeof r.attrs?r.attrs.call(n,e,t):r.attrs;e.openNode(n,o)},e[t+"_close"]=function(e){return e.closeNode()};else if("mark"==r.parse)e[t+"_open"]=function(e,t){var o=r.attrs instanceof Function?r.attrs.call(n,e,t):r.attrs;e.openMark(n.create(o))},e[t+"_close"]=function(e){return e.closeMark(n)};else{if(!r.parse)throw new RangeError("Unrecognized markdown parsing spec: "+r);e[t]=r.parse.bind(n)}}function l(e){var t=Object.create(null);return t.text=function(e,t){return e.addText(t.content)},t.inline=function(e,t){return e.parseTokens(t.children)},t.softbreak=function(e){return e.addText("\n")},e.registry("parseMarkdown",function(e,n,r){u(t,e,r,n)}),t}function c(e){var t=e.cached.markdownConfig;return t||!function(){var n=null,r=[];e.registry("configureMarkdown",function(e,t){if("init"==e){if(n)throw new RangeError("Two markdown parser initializers defined in schema");n=t}else{var o=(/_(\d+)$/.exec(e)||[0,50])[1];(0,b["default"])(r,{f:t,rank:o},function(e,t){return e.rank-t.rank})}}),t={init:n||function(){return(0,v["default"])("commonmark",{html:!1})},modifiers:r.map(function(e){return e.f})}}(),t}function p(e){var t=c(e),n=t.init();return t.modifiers.forEach(function(e){return n=e(n)}),n}function f(e){return"\n"==e.charAt(e.length-1)?e.slice(0,e.length-1):e}function h(e,t){e.openNode(this),e.addText(f(t.content)),e.closeNode()}var d=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.fromMarkdown=i;var m=e("markdown-it"),v=r(m),g=e("../model"),y=e("../format"),k=e("../util/sortedinsert"),b=r(k);(0,y.defineSource)("markdown",i);var w=[],_=function(){function e(t,n,r){o(this,e),this.schema=t,this.stack=[{type:t.nodes.doc,content:[]}],this.tokens=n,this.marks=w,this.tokenTypes=s(t),this.options=r}return d(e,[{key:"top",value:function(){return this.stack[this.stack.length-1]}},{key:"push",value:function(e){this.stack.length&&this.top().content.push(e)}},{key:"addText",value:function(e){var t=this.top().content,n=t[t.length-1],r=this.schema.text(e,this.marks),o=void 0;n&&(o=a(n,r))?t[t.length-1]=o:t.push(r)}},{key:"openMark",value:function(e){this.marks=e.addToSet(this.marks)}},{key:"closeMark",value:function(e){this.marks=e.removeFromSet(this.marks)}},{key:"parseTokens",value:function(e){for(var t=0;t<e.length;t++){var n=e[t],r=this.tokenTypes[n.type];if(!r)throw new Error("Token type `"+n.type+"` not supported by Markdown parser");r(this,n)}}},{key:"addNode",value:function(e,t,n){if(n=g.Fragment.from(n),!e.checkContent(n,t)&&(n=e.fixContent(n,t),!n))return null;var r=e.create(t,n,this.marks);return this.push(r),r}},{key:"openNode",value:function(e,t){this.stack.push({type:e,attrs:t,content:[]})}},{key:"closeNode",value:function(){this.marks.length&&(this.marks=w);var e=this.stack.pop();return this.addNode(e.type,e.attrs,e.content)}},{key:"getAttr",value:function(e,t){if(e.attrs)for(var n=0;n<e.attrs.length;n++)if(e.attrs[n][0]==t)return e.attrs[n][1]}}]),e}();g.BlockQuote.register("parseMarkdown","blockquote",{parse:"block"}),g.Paragraph.register("parseMarkdown","paragraph",{parse:"block"}),g.ListItem.register("parseMarkdown","list_item",{parse:"block"}),g.BulletList.register("parseMarkdown","bullet_list",{parse:"block"}),g.OrderedList.register("parseMarkdown","ordered_list",{parse:"block",attrs:function(e,t){return{order:e.getAttr(t,"order")||"1"}}}),g.Heading.register("parseMarkdown","heading",{parse:"block",attrs:function(e,t){return{level:""+Math.min(this.maxLevel,+t.tag.slice(1))}}}),g.CodeBlock.register("parseMarkdown","code_block",{parse:h}),g.CodeBlock.register("parseMarkdown","fence",{parse:h}),g.HorizontalRule.register("parseMarkdown","hr",{parse:function(e,t){e.addNode(this,{markup:t.markup})}}),g.Image.register("parseMarkdown","image",{parse:function(e,t){e.addNode(this,{src:e.getAttr(t,"src"),title:e.getAttr(t,"title")||null,alt:t.children[0]&&t.children[0].content||null})}}),g.HardBreak.register("parseMarkdown","hardbreak",{parse:function(e){e.addNode(this)}}),g.EmMark.register("parseMarkdown","em",{parse:"mark"}),g.StrongMark.register("parseMarkdown","strong",{parse:"mark"}),g.LinkMark.register("parseMarkdown","link",{parse:"mark",attrs:function(e,t){return{href:e.getAttr(t,"href"),title:e.getAttr(t,"title")||null}}}),g.CodeMark.register("parseMarkdown","code_inline",{parse:function(e,t){e.openMark(this.create()),e.addText(t.content),e.closeMark(this)}})},{"../format":23,"../model":38,"../util/sortedinsert":59,"markdown-it":64}],30:[function(e,t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r=e("./from_markdown");Object.defineProperty(n,"fromMarkdown",{enumerable:!0,get:function(){return r.fromMarkdown}});var o=e("./to_markdown");Object.defineProperty(n,"toMarkdown",{enumerable:!0,get:function(){return o.toMarkdown}})},{"./from_markdown":29,"./to_markdown":31}],31:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){var n=new l(t);return n.renderContent(e),n.out}function i(e,t){e.prototype.serializeMarkdown=t}var a=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.toMarkdown=o;var s=e("../model"),u=e("../format");(0,u.defineTarget)("markdown",o);var l=function(){function e(t){r(this,e),this.delim=this.out="",this.closed=!1,this.inTightList=!1,this.options=t}return a(e,[{key:"flushClose",value:function(e){if(this.closed){if(this.atBlank()||(this.out+="\n"),null==e&&(e=2),e>1){var t=this.delim,n=/\s+$/.exec(t);n&&(t=t.slice(0,t.length-n[0].length));for(var r=1;e>r;r++)this.out+=t+"\n"}this.closed=!1}}},{key:"wrapBlock",value:function(e,t,n,r){var o=this.delim;this.write(t||e),this.delim+=e,r(),this.delim=o,this.closeBlock(n)}},{key:"atBlank",value:function(){return/(^|\n)$/.test(this.out)}},{key:"ensureNewLine",value:function(){this.atBlank()||(this.out+="\n")}},{key:"write",value:function(e){this.flushClose(),this.delim&&this.atBlank()&&(this.out+=this.delim),e&&(this.out+=e)}},{key:"closeBlock",value:function(e){this.closed=e}},{key:"text",value:function(e,t){for(var n=e.split("\n"),r=0;r<n.length;r++){var o=this.atBlank()||this.closed;this.write(),this.out+=t!==!1?this.esc(n[r],o):n[r],r!=n.length-1&&(this.out+="\n")}}},{key:"render",value:function(e){e.type.serializeMarkdown(this,e)}},{key:"renderContent",value:function(e){var t=this;e.forEach(function(e){return t.render(e)})}},{key:"renderInline",value:function(e){var t=this,n=[],r=function(e){var r=e?e.marks:[],o=r.length&&r[r.length-1].type.isCode&&r[r.length-1],i=r.length-(o?1:0);e:for(var a=0;i>a;a++){var s=r[a];if(!s.type.markdownMixable)break;for(var u=0;u<n.length;u++){var l=n[u];if(!l.type.markdownMixable)break;if(s.eq(l)){a>u?r=r.slice(0,u).concat(s).concat(r.slice(u,a)).concat(r.slice(a+1),i):u>a&&(r=r.slice(0,a).concat(r.slice(a+1,u)).concat(s).concat(r.slice(u,i)));continue e}}}for(var c=0;c<Math.min(n.length,i)&&r[c].eq(n[c]);)++c;for(;c<n.length;)t.text(t.markString(n.pop(),!1),!1);for(;n.length<i;){var p=r[n.length];n.push(p),t.text(t.markString(p,!0),!1)}e&&(o&&e.isText?t.text(t.markString(o,!1)+e.text+t.markString(o,!0),!1):t.render(e))};e.forEach(r),r(null)}},{key:"renderList",value:function(e,t,n){var r=this;this.closed&&this.closed.type==e.type?this.flushClose(3):this.inTightList&&this.flushClose(1);var o=this.inTightList;this.inTightList=e.attrs.tight;for(var i=function(o){o&&e.attrs.tight&&r.flushClose(1),r.wrapBlock(t,n(o),e,function(){return r.render(e.child(o))})},a=0;a<e.childCount;a++)i(a);this.inTightList=o}},{key:"esc",value:function(e,t){return e=e.replace(/[`*\\~+\[\]]/g,"\\$&"),t&&(e=e.replace(/^[:#-]/,"\\$&")),e}},{key:"quote",value:function(e){var t=-1==e.indexOf('"')?'""':-1==e.indexOf("'")?"''":"()";return t[0]+e+t[1]}},{key:"repeat",value:function(e,t){for(var n="",r=0;t>r;r++)n+=e;return n}},{key:"markString",value:function(e,t){var n=t?e.type.openMarkdown:e.type.closeMarkdown;return"string"==typeof n?n:n(this,e)}}]),e}();i(s.BlockQuote,function(e,t){e.wrapBlock("> ",null,t,function(){return e.renderContent(t)})}),i(s.CodeBlock,function(e,t){null==t.attrs.params?e.wrapBlock(" ",null,t,function(){return e.text(t.textContent,!1)}):(e.write("```"+t.attrs.params+"\n"),e.text(t.textContent,!1),e.ensureNewLine(),e.write("```"),e.closeBlock(t))}),i(s.Heading,function(e,t){e.write(e.repeat("#",t.attrs.level)+" "),e.renderInline(t),e.closeBlock(t)}),i(s.HorizontalRule,function(e,t){e.write(t.attrs.markup||"---"),e.closeBlock(t)}),i(s.BulletList,function(e,t){e.renderList(t," ",function(){return(t.attrs.bullet||"*")+" "})}),i(s.OrderedList,function(e,t){var n=Number(t.attrs.order||1),r=String(n+t.childCount-1).length,o=e.repeat(" ",r+2);e.renderList(t,o,function(t){var o=String(n+t);return e.repeat(" ",r-o.length)+o+". "})}),i(s.ListItem,function(e,t){return e.renderContent(t)}),i(s.Paragraph,function(e,t){e.renderInline(t),e.closeBlock(t)}),i(s.Image,function(e,t){e.write("+(t.attrs.title?" "+e.quote(t.attrs.title):"")+")")}),i(s.HardBreak,function(e){return e.write("\\\n")}),i(s.Text,function(e,t){return e.text(t.text)}),s.EmMark.prototype.openMarkdown=s.EmMark.prototype.closeMarkdown="*",s.EmMark.prototype.markdownMixable=!0,s.StrongMark.prototype.openMarkdown=s.StrongMark.prototype.closeMarkdown="**",s.StrongMark.prototype.markdownMixable=!0,s.LinkMark.prototype.openMarkdown="[",s.LinkMark.prototype.closeMarkdown=function(e,t){return"]("+e.esc(t.attrs.href)+(t.attrs.title?" "+e.quote(t.attrs.title):"")+")"},s.CodeMark.prototype.openMarkdown=s.CodeMark.prototype.closeMarkdown="`"},{"../format":23,"../model":38}],32:[function(e,t,n){"use strict";function r(e,t){var n=document.createElement("div");if(n.className=c,t.path){s[e]||o(e,t);var r=n.appendChild(document.createElementNS(u,"svg"));r.style.width=t.width/t.height+"em";var i=r.appendChild(document.createElementNS(u,"use"));i.setAttributeNS(l,"href",/([^#]*)/.exec(document.location)[1]+"#pm-icon-"+e)}else t.dom?n.appendChild(t.dom.cloneNode(!0)):(n.appendChild(document.createElement("span")).textContent=t.text||"",t.style&&(n.firstChild.style.cssText=t.style));return n}function o(e,t){a||(a=document.createElementNS(u,"svg"),a.style.display="none",document.body.insertBefore(a,document.body.firstChild));var n=document.createElementNS(u,"symbol");n.id="pm-icon-"+e,n.setAttribute("viewBox","0 0 "+t.width+" "+t.height);var r=n.appendChild(document.createElementNS(u,"path"));r.setAttribute("d",t.path),a.appendChild(n),s[e]=!0}Object.defineProperty(n,"__esModule",{value:!0}),n.getIcon=r;var i=e("../dom"),a=null,s=Object.create(null),u="http://www.w3.org/2000/svg",l="http://www.w3.org/1999/xlink",c="ProseMirror-icon";(0,i.insertCSS)("\n."+c+" {\n display: inline-block;\n line-height: .8;\n vertical-align: -2px; /* Compensate for padding */\n padding: 2px 8px;\n cursor: pointer;\n}\n\n."+c+" svg {\n fill: currentColor;\n height: 1em;\n}\n\n."+c+" span {\n vertical-align: text-top;\n}")},{"../dom":3}],33:[function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!t.label)return null;var n=e.translate(t.label),r=t.name&&e.keyForCommand(t.name);return r?n+" ("+r+")":n}function a(e,t){for(var n=[],r=0;r<e.length;r++){var o=e[r].render(t);o&&n.push((0,p.elt)("div",{"class":v+"-dropdown-item"},o))}return n}function s(e,t){for(var n=void 0,r=Array.isArray(t),o=0;o<(r?t.length:1);o++){var i=r?t[o]:t;if(i instanceof y){var a=i.get(e);if(!r||1==t.length)return a;n=(n||t.slice(0,o)).concat(a)}else n&&n.push(i)}return n||(r?t:[t])}function u(e,t){for(var n=document.createDocumentFragment(),r=!1,o=0;o<t.length;o++){for(var i=s(e,t[o]),a=!1,u=0;u<i.length;u++){var c=i[u].render(e);c&&(!a&&r&&n.appendChild(l()),n.appendChild((0,p.elt)("span",{"class":v+"item"},c)),a=!0)}a&&(r=!0)}return n}function l(){return(0,p.elt)("span",{"class":v+"separator"})}var c=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.historyGroup=n.blockGroup=n.textblockMenu=n.insertMenu=n.inlineGroup=n.DropdownSubmenu=n.Dropdown=n.MenuCommandGroup=n.MenuCommand=void 0,n.resolveGroup=s,n.renderGrouped=u;var p=e("../dom"),f=e("../util/sortedinsert"),h=r(f),d=e("../util/obj"),m=e("./icons"),v="ProseMirror-menu",g=n.MenuCommand=function(){function e(t,n){o(this,e),this.command_=t,this.options=n}return c(e,[{key:"command",value:function(e){return"string"==typeof this.command_?e.commands[this.command_]:this.command_}},{key:"render",value:function(e){var t=this.command(e),n=!1;if(t){if("ignore"!=this.options.select&&!t.select(e)){if(null==this.options.select||"hide"==this.options.select)return null;"disable"==this.options.select&&(n=!0)}var r=this.options.display;if(!r)throw new RangeError("No display style defined for menu command "+t.name);var o=void 0;if(r.render)o=r.render(t,e);else if("icon"==r.type)o=(0,m.getIcon)(t.name,r),!n&&t.active(e)&&o.classList.add(v+"-active");else{if("label"!=r.type)throw new RangeError("Unsupported command display style: "+r.type);var a=e.translate(r.label||t.spec.label);o=(0,p.elt)("div",null,a)}return o.setAttribute("title",i(e,t)),this.options["class"]&&o.classList.add(this.options["class"]),n&&o.classList.add(v+"-disabled"),this.options.css&&(o.style.cssText+=this.options.css),o.addEventListener("mousedown",function(n){n.preventDefault(),n.stopPropagation(),e.signal("interaction"),t.exec(e,null,o)}),o.setAttribute("data-command",this.commandName),o}}},{key:"commandName",get:function(){return"string"==typeof this.command_?this.command_.command:this.command_.name}}]),e}(),y=n.MenuCommandGroup=function(){function e(t,n){o(this,e),this.name=t,this.options=n}return c(e,[{key:"collect",value:function(e){var t=this,n=[];for(var r in e.commands){var o=e.commands[r],i=o.spec.menu;i&&i.group==this.name&&(0,h["default"])(n,{cmd:o,rank:null==i.rank?50:i.rank},function(e,t){return e.rank-t.rank})}return n.map(function(e){var n=e.cmd.spec.menu;return t.options&&(n=(0,d.copyObj)(t.options,(0,d.copyObj)(n))),new g(e.cmd,n)})}},{key:"get",value:function(e){var t=e.mod.menuGroups||this.startGroups(e);
+return t[this.name]||(t[this.name]=this.collect(e))}},{key:"startGroups",value:function(e){var t=function n(){e.mod.menuGroups=null,e.off("commandsChanging",n)};return e.on("commandsChanging",t),e.mod.menuGroups=Object.create(null)}}]),e}(),k=n.Dropdown=function(){function e(t,n){o(this,e),this.options=t||{},this.content=n}return c(e,[{key:"render",value:function(e){var t=this,n=a(s(e,this.content),e);if(n.length){var r=this.options.activeLabel&&this.findActiveIn(this,e)||this.options.label;r=e.translate(r);var o=(0,p.elt)("div",{"class":v+"-dropdown "+(this.options["class"]||""),style:this.options.css,title:this.options.title},r),i=null;return o.addEventListener("mousedown",function(r){r.preventDefault(),r.stopPropagation(),i=i&&i()?null:t.expand(e,o,n)}),o}}},{key:"select",value:function(e){return s(e,this.content).some(function(t){return t.select(e)})}},{key:"expand",value:function(e,t,n){function r(){return s?void 0:(s=!0,e.off("interaction",r),e.wrapper.removeChild(a),!0)}var o=t.getBoundingClientRect(),i=e.wrapper.getBoundingClientRect(),a=(0,p.elt)("div",{"class":v+"-dropdown-menu "+(this.options["class"]||""),style:"left: "+(o.left-i.left)+"px; top: "+(o.bottom-i.top)+"px"},n),s=!1;return e.signal("interaction"),e.wrapper.appendChild(a),e.on("interaction",r),r}},{key:"findActiveIn",value:function(e,t){for(var n=s(t,e.content),r=0;r<n.length;r++){var o=n[r];if(o instanceof g){var i=o.command(t).active(t);if(i)return o.options.activeLabel}else if(o instanceof b){var a=this.findActiveIn(o,t);if(a)return a}}}}]),e}(),b=n.DropdownSubmenu=function(){function e(t,n){o(this,e),this.options=t||{},this.content=n}return c(e,[{key:"render",value:function(e){var t=a(s(e,this.content),e);if(t.length){var n=(0,p.elt)("div",{"class":v+"-submenu-label"},e.translate(this.options.label)),r=(0,p.elt)("div",{"class":v+"-submenu-wrap"},n,(0,p.elt)("div",{"class":v+"-submenu"},t));return n.addEventListener("mousedown",function(e){e.preventDefault(),e.stopPropagation(),r.classList.toggle(v+"-submenu-wrap-active")}),r}}}]),e}();n.inlineGroup=new y("inline"),n.insertMenu=new k({label:"Insert"},new y("insert")),n.textblockMenu=new k({label:"Type..",displayActive:!0,"class":"ProseMirror-textblock-dropdown"},[new y("textblock"),new b({label:"Heading"},new y("textblockHeading"))]),n.blockGroup=new y("block"),n.historyGroup=new y("history");(0,p.insertCSS)("\n\n.ProseMirror-textblock-dropdown {\n min-width: 3em;\n}\n\n."+v+" {\n margin: 0 -4px;\n line-height: 1;\n}\n\n.ProseMirror-tooltip ."+v+" {\n width: -webkit-fit-content;\n width: fit-content;\n white-space: pre;\n}\n\n."+v+"item {\n margin-right: 3px;\n display: inline-block;\n}\n\n."+v+"separator {\n border-right: 1px solid #ddd;\n margin-right: 3px;\n}\n\n."+v+"-dropdown, ."+v+"-dropdown-menu {\n font-size: 90%;\n white-space: nowrap;\n}\n\n."+v+"-dropdown {\n padding: 1px 14px 1px 4px;\n display: inline-block;\n vertical-align: 1px;\n position: relative;\n cursor: pointer;\n}\n\n."+v+'-dropdown:after {\n content: "";\n border-left: 4px solid transparent;\n border-right: 4px solid transparent;\n border-top: 4px solid currentColor;\n opacity: .6;\n position: absolute;\n right: 2px;\n top: calc(50% - 2px);\n}\n\n.'+v+"-dropdown-menu, ."+v+"-submenu {\n position: absolute;\n background: white;\n color: #666;\n border: 1px solid #aaa;\n padding: 2px;\n}\n\n."+v+"-dropdown-menu {\n z-index: 15;\n min-width: 6em;\n}\n\n."+v+"-dropdown-item {\n cursor: pointer;\n padding: 2px 8px 2px 4px;\n}\n\n."+v+"-dropdown-item:hover {\n background: #f2f2f2;\n}\n\n."+v+"-submenu-wrap {\n position: relative;\n margin-right: -4px;\n}\n\n."+v+'-submenu-label:after {\n content: "";\n border-top: 4px solid transparent;\n border-bottom: 4px solid transparent;\n border-left: 4px solid currentColor;\n opacity: .6;\n position: absolute;\n right: 4px;\n top: calc(50% - 4px);\n}\n\n.'+v+"-submenu {\n display: none;\n min-width: 4em;\n left: 100%;\n top: -3px;\n}\n\n."+v+"-active {\n background: #eee;\n border-radius: 4px;\n}\n\n."+v+"-active {\n background: #eee;\n border-radius: 4px;\n}\n\n."+v+"-disabled {\n opacity: .3;\n}\n\n."+v+"-submenu-wrap:hover ."+v+"-submenu, ."+v+"-submenu-wrap-active ."+v+"-submenu {\n display: block;\n}\n")},{"../dom":3,"../util/obj":58,"../util/sortedinsert":59,"./icons":32}],34:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e){for(var t=e.parentNode;t;t=t.parentNode)if(t.scrollHeight>t.clientHeight)return t}var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e},a=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),s=e("../edit"),u=e("../dom"),l=e("../ui/update"),c=e("./menu"),p="ProseMirror-menubar";(0,s.defineOption)("menuBar",!1,function(e,t){e.mod.menuBar&&e.mod.menuBar.detach(),e.mod.menuBar=t?new h(e,t):null});var f=[c.inlineGroup,c.insertMenu,[c.textblockMenu,c.blockGroup],c.historyGroup],h=function(){function e(t,n){var o=this;r(this,e),this.pm=t,this.config=n||{},this.wrapper=t.wrapper.insertBefore((0,u.elt)("div",{"class":p}),t.wrapper.firstChild),this.spacer=null,this.maxHeight=0,this.widthForMaxHeight=0,this.updater=new l.UpdateScheduler(t,"selectionChange change activeMarkChange commandsChanged",function(){return o.update()}),this.content=n.content||f,this.updater.force(),this.floating=!1,this.config["float"]&&(this.updateFloat(),this.scrollFunc=function(){document.body.contains(o.pm.wrapper)?o.updateFloat():window.removeEventListener("scroll",o.scrollFunc)},window.addEventListener("scroll",this.scrollFunc))}return a(e,[{key:"detach",value:function(){this.updater.detach(),this.wrapper.parentNode.removeChild(this.wrapper),this.scrollFunc&&window.removeEventListener("scroll",this.scrollFunc)}},{key:"update",value:function(){var e=this;return this.wrapper.textContent="",this.wrapper.appendChild((0,c.renderGrouped)(this.pm,this.content)),this.floating?this.updateScrollCursor():function(){return e.wrapper.offsetWidth!=e.widthForMaxHeight&&(e.widthForMaxHeight=e.wrapper.offsetWidth,e.maxHeight=0),e.wrapper.offsetHeight>e.maxHeight?(e.maxHeight=e.wrapper.offsetHeight,function(){e.wrapper.style.minHeight=e.maxHeight+"px"}):void 0}}},{key:"updateFloat",value:function(){var e=this.pm.wrapper.getBoundingClientRect();if(this.floating)if(e.top>=0||e.bottom<this.wrapper.offsetHeight+10)this.floating=!1,this.wrapper.style.position=this.wrapper.style.left=this.wrapper.style.width="",this.wrapper.style.display="",this.spacer.parentNode.removeChild(this.spacer),this.spacer=null;else{var t=(this.pm.wrapper.offsetWidth-this.pm.wrapper.clientWidth)/2;this.wrapper.style.left=e.left+t+"px",this.wrapper.style.display=e.top>window.innerHeight?"none":""}else if(e.top<0&&e.bottom>=this.wrapper.offsetHeight+10){this.floating=!0;var n=this.wrapper.getBoundingClientRect();this.wrapper.style.left=n.left+"px",this.wrapper.style.width=n.width+"px",this.wrapper.style.position="fixed",this.spacer=(0,u.elt)("div",{"class":p+"-spacer",style:"height: "+n.height+"px"}),this.pm.wrapper.insertBefore(this.spacer,this.wrapper)}}},{key:"updateScrollCursor",value:function(){var e=this;if(!this.floating)return null;var t=this.pm.selection.head;return t?function(){var n=e.pm.coordsAtPos(t),r=e.wrapper.getBoundingClientRect();if(n.top<r.bottom&&n.bottom>r.top){var a=function(){var t=o(e.pm.wrapper);return t?{v:function(){t.scrollTop-=r.bottom-n.top}}:void 0}();if("object"===("undefined"==typeof a?"undefined":i(a)))return a.v}}:null}}]),e}();(0,u.insertCSS)("\n."+p+" {\n border-top-left-radius: inherit;\n border-top-right-radius: inherit;\n position: relative;\n min-height: 1em;\n color: #666;\n padding: 1px 6px;\n top: 0; left: 0; right: 0;\n border-bottom: 1px solid silver;\n background: white;\n z-index: 10;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n overflow: visible;\n}\n")},{"../dom":3,"../edit":13,"../ui/update":54,"./menu":33}],35:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var a=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.defaultSchema=n.CodeMark=n.LinkMark=n.StrongMark=n.EmMark=n.HardBreak=n.Image=n.Paragraph=n.CodeBlock=n.Heading=n.HorizontalRule=n.ListItem=n.BulletList=n.OrderedList=n.BlockQuote=n.Doc=void 0;var s=e("./schema"),u=n.Doc=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),a(t,[{key:"kind",get:function(){return null}}]),t}(s.Block),l=n.BlockQuote=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),t}(s.Block);s.NodeKind.list_item=new s.NodeKind("list_item");var c=n.OrderedList=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),a(t,[{key:"contains",get:function(){return s.NodeKind.list_item}},{key:"attrs",get:function(){return{order:new s.Attribute({"default":"1"})}}}]),t}(s.Block),p=n.BulletList=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),a(t,[{key:"contains",get:function(){return s.NodeKind.list_item}}]),t}(s.Block),f=n.ListItem=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),a(t,[{key:"kind",get:function(){return s.NodeKind.list_item}}]),t}(s.Block),h=n.HorizontalRule=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),a(t,[{key:"contains",get:function(){return null}}]),t}(s.Block),d=n.Heading=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),a(t,[{key:"attrs",get:function(){return{level:new s.Attribute({"default":"1"})}}},{key:"maxLevel",get:function(){return 6}}]),t}(s.Textblock),m=n.CodeBlock=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),a(t,[{key:"contains",get:function(){return s.NodeKind.text}},{key:"containsMarks",get:function(){return!1}},{key:"isCode",get:function(){return!0}}]),t}(s.Textblock),v=n.Paragraph=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),a(t,[{key:"defaultTextblock",get:function(){return!0}}]),t}(s.Textblock),g=n.Image=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),a(t,[{key:"attrs",get:function(){return{src:new s.Attribute,alt:new s.Attribute({"default":""}),title:new s.Attribute({"default":""})}}},{key:"draggable",get:function(){return!0}}]),t}(s.Inline),y=n.HardBreak=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),a(t,[{key:"selectable",get:function(){return!1}},{key:"isBR",get:function(){return!0}}]),t}(s.Inline),k=n.EmMark=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),a(t,null,[{key:"rank",get:function(){return 31}}]),t}(s.MarkType),b=n.StrongMark=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),a(t,null,[{key:"rank",get:function(){return 32}}]),t}(s.MarkType),w=n.LinkMark=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),a(t,[{key:"attrs",get:function(){return{href:new s.Attribute,title:new s.Attribute({"default":""})}}}],[{key:"rank",get:function(){return 60}}]),t}(s.MarkType),_=n.CodeMark=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),a(t,[{key:"isCode",get:function(){return!0}}],[{key:"rank",get:function(){return 101}}]),t}(s.MarkType),x=new s.SchemaSpec({doc:u,blockquote:l,ordered_list:c,bullet_list:p,list_item:f,horizontal_rule:h,paragraph:v,heading:d,code_block:m,text:s.Text,image:g,hard_break:y},{em:k,strong:b,link:w,code:_});n.defaultSchema=new s.Schema(x)},{"./schema":43}],36:[function(e,t,n){"use strict";function r(e,t){for(var n=arguments.length<=2||void 0===arguments[2]?0:arguments[2],o=0;;o++){if(o==e.childCount||o==t.childCount)return e.childCount==t.childCount?null:n;var i=e.child(o),a=t.child(o);if(i!=a){if(!i.sameMarkup(a))return n;if(i.isText&&i.text!=a.text){for(var s=0;i.text[s]==a.text[s];s++)n++;return n}if(i.content.size||a.content.size){var u=r(i.content,a.content,n+1);if(null!=u)return u}n+=i.nodeSize}else n+=i.nodeSize}}function o(e,t){for(var n=arguments.length<=2||void 0===arguments[2]?e.size:arguments[2],r=arguments.length<=3||void 0===arguments[3]?t.size:arguments[3],i=e.childCount,a=t.childCount;;){if(0==i||0==a)return i==a?null:{a:n,b:r};var s=e.child(--i),u=t.child(--a),l=s.nodeSize;if(s!=u){if(!s.sameMarkup(u))return{a:n,b:r};if(s.isText&&s.text!=u.text){for(var c=0,p=Math.min(s.text.length,u.text.length);p>c&&s.text[s.text.length-c-1]==u.text[u.text.length-c-1];)c++,n--,r--;return{a:n,b:r}}if(s.content.size||u.content.size){var f=o(s.content,u.content,n-1,r-1);if(f)return f}n-=l,r-=l}else n-=l,r-=l}}Object.defineProperty(n,"__esModule",{value:!0}),n.findDiffStart=r,n.findDiffEnd=o},{}],37:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){return s.index=e,s.offset=t,s}var i=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0});var a=n.Fragment=function(){function e(t,n){if(r(this,e),this.content=t,this.size=n||0,null==n)for(var o=0;o<t.length;o++)this.size+=t[o].nodeSize}return i(e,[{key:"toString",value:function(){return"<"+this.toStringInner()+">"}},{key:"toStringInner",value:function(){return this.content.join(", ")}},{key:"nodesBetween",value:function(e,t,n,r,o){for(var i=0,a=0;t>a;i++){var s=this.content[i],u=a+s.nodeSize;if(u>e&&n(s,r+a,o)!==!1&&s.content.size){var l=a+1;s.nodesBetween(Math.max(0,e-l),Math.min(s.content.size,t-l),n,r+l)}a=u}}},{key:"cut",value:function(t,n){if(null==n&&(n=this.size),0==t&&n==this.size)return this;var r=[],o=0;if(n>t)for(var i=0,a=0;n>a;i++){var s=this.content[i],u=a+s.nodeSize;u>t&&((t>a||u>n)&&(s=s.isText?s.cut(Math.max(0,t-a),Math.min(s.text.length,n-a)):s.cut(Math.max(0,t-a-1),Math.min(s.content.size,n-a-1))),r.push(s),o+=s.nodeSize),a=u}return new e(r,o)}},{key:"append",value:function(t){if(!t.size)return this;if(!this.size)return t;var n=this.lastChild,r=t.firstChild,o=this.content.slice(),i=0;for(n.isText&&n.sameMarkup(r)&&(o[o.length-1]=n.copy(n.text+r.text),i=1);i<t.content.length;i++)o.push(t.content[i]);return new e(o,this.size+t.size)}},{key:"replaceChild",value:function(t,n){var r=this.content.slice(),o=this.size+n.nodeSize-r[t].nodeSize;return r[t]=n,new e(r,o)}},{key:"addToStart",value:function(t){return new e([t].concat(this.content),this.size+t.nodeSize)}},{key:"addToEnd",value:function(t){return new e(this.content.concat(t),this.size+t.nodeSize)}},{key:"toJSON",value:function(){return this.content.length?this.content.map(function(e){return e.toJSON()}):null}},{key:"eq",value:function(e){if(this.content.length!=e.content.length)return!1;for(var t=0;t<this.content.length;t++)if(!this.content[t].eq(e.content[t]))return!1;return!0}},{key:"child",value:function(e){var t=this.content[e];if(!t)throw new RangeError("Index "+e+" out of range for "+this);return t}},{key:"maybeChild",value:function(e){return this.content[e]}},{key:"forEach",value:function(e){for(var t=0,n=0;t<this.content.length;t++){var r=this.content[t];e(r,n),n+=r.nodeSize}}},{key:"leastSuperKind",value:function(){for(var e=void 0,t=this.childCount-1;t>=0;t--){var n=this.child(t).type.kind;e=e?e.sharedSuperKind(n):n}return e}},{key:"findIndex",value:function(e){var t=arguments.length<=1||void 0===arguments[1]?-1:arguments[1];if(0==e)return o(0,e);if(e==this.size)return o(this.content.length,e);if(e>this.size||0>e)throw new RangeError("Position "+e+" outside of fragment ("+this+")");for(var n=0,r=0;;n++){var i=this.child(n),a=r+i.nodeSize;if(a>=e)return a==e||t>0?o(n+1,a):o(n,r);r=a}}},{key:"textContent",get:function(){var e="";return this.content.forEach(function(t){return e+=t.textContent}),e}},{key:"firstChild",get:function(){return this.content.length?this.content[0]:null}},{key:"lastChild",get:function(){return this.content.length?this.content[this.content.length-1]:null}},{key:"childCount",get:function(){return this.content.length}}],[{key:"fromJSON",value:function(t,n){return n?new e(n.map(t.nodeFromJSON)):e.empty}},{key:"fromArray",value:function(t){if(!t.length)return e.empty;for(var n=void 0,r=0,o=0;o<t.length;o++){var i=t[o];r+=i.nodeSize,o&&i.isText&&t[o-1].sameMarkup(i)?(n||(n=t.slice(0,o)),n[n.length-1]=i.copy(n[n.length-1].text+i.text)):n&&n.push(i)}return new e(n||t,r)}},{key:"from",value:function(t){return t?t instanceof e?t:Array.isArray(t)?this.fromArray(t):new e([t],t.nodeSize):e.empty}}]),e}(),s={index:0,offset:0};a.empty=new a([],0)},{}],38:[function(e,t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r=e("./node");Object.defineProperty(n,"Node",{enumerable:!0,get:function(){return r.Node}});var o=e("./resolvedpos");Object.defineProperty(n,"ResolvedPos",{enumerable:!0,get:function(){return o.ResolvedPos}});var i=e("./fragment");Object.defineProperty(n,"Fragment",{enumerable:!0,get:function(){return i.Fragment}});var a=e("./replace");Object.defineProperty(n,"Slice",{enumerable:!0,get:function(){return a.Slice}}),Object.defineProperty(n,"ReplaceError",{enumerable:!0,get:function(){return a.ReplaceError}});var s=e("./mark");Object.defineProperty(n,"Mark",{enumerable:!0,get:function(){return s.Mark}});var u=e("./schema");Object.defineProperty(n,"SchemaSpec",{enumerable:!0,get:function(){return u.SchemaSpec}}),Object.defineProperty(n,"Schema",{enumerable:!0,get:function(){return u.Schema}}),Object.defineProperty(n,"NodeType",{enumerable:!0,get:function(){return u.NodeType}}),Object.defineProperty(n,"Block",{enumerable:!0,get:function(){return u.Block}}),Object.defineProperty(n,"Textblock",{enumerable:!0,get:function(){return u.Textblock}}),Object.defineProperty(n,"Inline",{enumerable:!0,get:function(){return u.Inline}}),Object.defineProperty(n,"Text",{enumerable:!0,get:function(){return u.Text}}),Object.defineProperty(n,"MarkType",{enumerable:!0,get:function(){return u.MarkType}}),Object.defineProperty(n,"Attribute",{enumerable:!0,get:function(){return u.Attribute}}),Object.defineProperty(n,"NodeKind",{enumerable:!0,get:function(){return u.NodeKind}});var l=e("./defaultschema");Object.defineProperty(n,"defaultSchema",{enumerable:!0,get:function(){return l.defaultSchema}}),Object.defineProperty(n,"Doc",{enumerable:!0,get:function(){return l.Doc}}),Object.defineProperty(n,"BlockQuote",{enumerable:!0,get:function(){return l.BlockQuote}}),Object.defineProperty(n,"OrderedList",{enumerable:!0,get:function(){return l.OrderedList}}),Object.defineProperty(n,"BulletList",{enumerable:!0,get:function(){return l.BulletList}}),Object.defineProperty(n,"ListItem",{enumerable:!0,get:function(){return l.ListItem}}),Object.defineProperty(n,"HorizontalRule",{enumerable:!0,get:function(){return l.HorizontalRule}}),Object.defineProperty(n,"Paragraph",{enumerable:!0,get:function(){return l.Paragraph}}),Object.defineProperty(n,"Heading",{enumerable:!0,get:function(){return l.Heading}}),Object.defineProperty(n,"CodeBlock",{enumerable:!0,get:function(){return l.CodeBlock}}),Object.defineProperty(n,"Image",{enumerable:!0,get:function(){return l.Image}}),Object.defineProperty(n,"HardBreak",{enumerable:!0,get:function(){return l.HardBreak}}),Object.defineProperty(n,"CodeMark",{enumerable:!0,get:function(){return l.CodeMark}}),Object.defineProperty(n,"EmMark",{enumerable:!0,get:function(){return l.EmMark}}),Object.defineProperty(n,"StrongMark",{enumerable:!0,get:function(){return l.StrongMark}}),Object.defineProperty(n,"LinkMark",{enumerable:!0,get:function(){return l.LinkMark}});var c=e("./diff");Object.defineProperty(n,"findDiffStart",{enumerable:!0,get:function(){return c.findDiffStart}}),Object.defineProperty(n,"findDiffEnd",{enumerable:!0,get:function(){return c.findDiffEnd}})},{"./defaultschema":35,"./diff":36,"./fragment":37,"./mark":39,"./node":40,"./replace":41,"./resolvedpos":42,"./schema":43}],39:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0});var i=(n.Mark=function(){function e(t,n){r(this,e),this.type=t,this.attrs=n}return o(e,[{key:"toJSON",value:function(){var e={_:this.type.name};for(var t in this.attrs)e[t]=this.attrs[t];return e}},{key:"addToSet",value:function(e){for(var t=0;t<e.length;t++){var n=e[t];if(n.type==this.type){if(this.eq(n))return e;var r=e.slice();return r[t]=this,r}if(n.type.rank>this.type.rank)return e.slice(0,t).concat(this).concat(e.slice(t))}return e.concat(this)}},{key:"removeFromSet",value:function(e){for(var t=0;t<e.length;t++)if(this.eq(e[t]))return e.slice(0,t).concat(e.slice(t+1));return e}},{key:"isInSet",value:function(e){for(var t=0;t<e.length;t++)if(this.eq(e[t]))return!0;return!1}},{key:"eq",value:function(e){if(this==e)return!0;if(this.type!=e.type)return!1;for(var t in this.attrs)if(e.attrs[t]!=this.attrs[t])return!1;return!0}}],[{key:"sameSet",value:function(e,t){if(e==t)return!0;if(e.length!=t.length)return!1;for(var n=0;n<e.length;n++)if(!e[n].eq(t[n]))return!1;return!0}},{key:"setFrom",value:function(t){if(!t||0==t.length)return i;if(t instanceof e)return[t];var n=t.slice();return n.sort(function(e,t){return e.type.rank-t.type.rank}),n}}]),e}(),[])},{}],40:[function(e,t,n){"use strict";function r(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function o(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){for(var n=e.length-1;n>=0;n--)t=e[n].type.name+"("+t+")";return t}var s=function v(e,t,n){null===e&&(e=Function.prototype);var r=Object.getOwnPropertyDescriptor(e,t);if(void 0===r){var o=Object.getPrototypeOf(e);return null===o?void 0:v(o,t,n)}if("value"in r)return r.value;var i=r.get;if(void 0!==i)return i.call(n)},u=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.TextNode=n.Node=void 0;var l=e("./fragment"),c=e("./mark"),p=e("./replace"),f=e("./resolvedpos"),h=[],d=Object.create(null),m=n.Node=function(){function e(t,n,r,o){i(this,e),this.type=t,this.attrs=n,this.content=r||l.Fragment.empty,this.marks=o||h}return u(e,[{key:"child",value:function(e){return this.content.child(e)}},{key:"maybeChild",value:function(e){return this.content.maybeChild(e)}},{key:"forEach",value:function(e){this.content.forEach(e)}},{key:"eq",value:function(e){return this==e||this.sameMarkup(e)&&this.content.eq(e.content)}},{key:"sameMarkup",value:function(e){return this.hasMarkup(e.type,e.attrs,e.marks)}},{key:"hasMarkup",value:function(t,n,r){return this.type==t&&e.sameAttrs(this.attrs,n||d)&&c.Mark.sameSet(this.marks,r||h)}},{key:"copy",value:function(){var e=arguments.length<=0||void 0===arguments[0]?null:arguments[0];return e==this.content?this:new this.constructor(this.type,this.attrs,e,this.marks)}},{key:"mark",value:function(e){return new this.constructor(this.type,this.attrs,this.content,e)}},{key:"cut",value:function(e,t){return 0==e&&t==this.content.size?this:this.copy(this.content.cut(e,t))}},{key:"slice",value:function(e){var t=arguments.length<=1||void 0===arguments[1]?this.content.size:arguments[1];if(e==t)return p.Slice.empty;var n=this.resolve(e),r=this.resolve(t),o=n.sameDepth(r),i=n.start(o),a=n.node(o),s=a.content.cut(n.pos-i,r.pos-i);return new p.Slice(s,n.depth-o,r.depth-o,a)}},{key:"replace",value:function(e,t,n){return(0,p.replace)(this.resolve(e),this.resolve(t),n)}},{key:"nodeAt",value:function(e){for(var t=this;;){var n=t.content.findIndex(e),r=n.index,o=n.offset;if(t=t.maybeChild(r),!t)return null;if(o==e||t.isText)return t;e-=o+1}}},{key:"childAfter",value:function(e){var t=this.content.findIndex(e),n=t.index,r=t.offset;return{node:this.content.maybeChild(n),index:n,offset:r}}},{key:"childBefore",value:function(e){if(0==e)return{node:null,index:0,offset:0};var t=this.content.findIndex(e),n=t.index,r=t.offset;if(e>r)return{node:this.content.child(n),index:n,offset:r};var o=this.content.child(n-1);return{node:o,index:n-1,offset:r-o.nodeSize}}},{key:"nodesBetween",value:function(e,t,n){var r=arguments.length<=3||void 0===arguments[3]?0:arguments[3];this.content.nodesBetween(e,t,n,r,this)}},{key:"descendants",value:function(e){this.nodesBetween(0,this.content.size,e)}},{key:"resolve",value:function(e){return f.ResolvedPos.resolveCached(this,e)}},{key:"resolveNoCache",value:function(e){return f.ResolvedPos.resolve(this,e)}},{key:"marksAt",value:function(e){var t=this.resolve(e),n=t.parent,r=t.index(t.depth);if(0==n.content.size)return h;if(0==r||!t.atNodeBoundary)return n.child(r).marks;for(var o=n.child(r-1).marks,i=0;i<o.length;i++)o[i].type.inclusiveRight||(o=o[i--].removeFromSet(o));return o}},{key:"rangeHasMark",value:function(e,t,n){var r=!1;return this.nodesBetween(e,t,function(e){return n.isInSet(e.marks)&&(r=!0),!r}),r}},{key:"toString",value:function(){var e=this.type.name;return this.content.size&&(e+="("+this.content.toStringInner()+")"),a(this.marks,e)}},{key:"toJSON",value:function(){var e={type:this.type.name};for(var t in this.attrs){e.attrs=this.attrs;break}return this.content.size&&(e.content=this.content.toJSON()),this.marks.length&&(e.marks=this.marks.map(function(e){return e.toJSON()})),e}},{key:"nodeSize",get:function(){return this.type.contains?2+this.content.size:1}},{key:"childCount",get:function(){return this.content.childCount}},{key:"textContent",get:function(){return this.content.textContent}},{key:"firstChild",get:function(){return this.content.firstChild}},{key:"lastChild",get:function(){return this.content.lastChild}},{key:"isBlock",get:function(){return this.type.isBlock}},{key:"isTextblock",get:function(){return this.type.isTextblock}},{key:"isInline",get:function(){return this.type.isInline}},{key:"isText",get:function(){return this.type.isText}},{key:"value",get:function(){return this}}],[{key:"sameAttrs",value:function(e,t){if(e==t)return!0;for(var n in e)if(e[n]!==t[n])return!1;return!0}},{key:"fromJSON",value:function(e,t){var n=e.nodeType(t.type),r=null!=t.text?t.text:l.Fragment.fromJSON(e,t.content);return n.create(t.attrs,r,t.marks&&t.marks.map(e.markFromJSON))}}]),e}();n.TextNode=function(e){function t(e,n,o,a){i(this,t);var s=r(this,Object.getPrototypeOf(t).call(this,e,n,null,a));if(!o)throw new RangeError("Empty text nodes are not allowed");return s.text=o,s}return o(t,e),u(t,[{key:"toString",value:function(){return a(this.marks,JSON.stringify(this.text))}},{key:"mark",value:function(e){return new t(this.type,this.attrs,this.text,e)}},{key:"cut",value:function(){var e=arguments.length<=0||void 0===arguments[0]?0:arguments[0],t=arguments.length<=1||void 0===arguments[1]?this.text.length:arguments[1];return 0==e&&t==this.text.length?this:this.copy(this.text.slice(e,t))}},{key:"eq",value:function(e){return this.sameMarkup(e)&&this.text==e.text}},{key:"toJSON",value:function(){var e=s(Object.getPrototypeOf(t.prototype),"toJSON",this).call(this);return e.text=this.text,e}},{key:"textContent",get:function(){return this.text}},{key:"nodeSize",get:function(){return this.text.length}}]),t}(m)},{"./fragment":37,"./mark":39,"./replace":41,"./resolvedpos":42}],41:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function a(e,t,n){if(n.openLeft>e.depth)throw new k("Inserted content deeper than insertion position");if(e.depth-n.openLeft!=t.depth-n.openRight)throw new k("Inconsistent open depths");return s(e,t,n,0)}function s(e,t,n,r){var o=e.index(r),i=e.node(r);if(o==t.index(r)&&r<e.depth-n.openLeft){var a=s(e,t,n,r+1);return i.copy(i.content.replaceChild(o,a))}if(n.content.size){var u=m(n,e),l=u.start,c=u.end;return f(i,h(e,l,c,t,r))}return f(i,d(e,t,r))}function u(e,t){if(!e.type.canContainContent(t.type))throw new k("Cannot join "+t.type.name+" onto "+e.type.name)}function l(e,t,n){var r=e.node(n);return u(r,t.node(n)),r}function c(e,t){var n=t.length-1;n>=0&&e.isText&&e.sameMarkup(t[n])?t[n]=e.copy(t[n].text+e.text):t.push(e)}function p(e,t,n,r){var o=(t||e).node(n),i=0,a=t?t.index(n):o.childCount;e&&(i=e.index(n),e.depth>n?i++:e.atNodeBoundary||(c(e.nodeAfter,r),i++));for(var s=i;a>s;s++)c(o.child(s),r);t&&t.depth==n&&!t.atNodeBoundary&&c(t.nodeBefore,r)}function f(e,t){if(!e.type.checkContent(t,e.attrs))throw new k("Invalid content for node "+e.type.name);return e.copy(t)}function h(e,t,n,r,o){var i=e.depth>o&&l(e,t,o+1),a=r.depth>o&&l(n,r,o+1),s=[];return p(null,e,o,s),i&&a&&t.index(o)==n.index(o)?(u(i,a),c(f(i,h(e,t,n,r,o+1)),s)):(i&&c(f(i,d(e,t,o+1)),s),p(t,n,o,s),a&&c(f(a,d(n,r,o+1)),s)),p(r,null,o,s),new y.Fragment(s)}function d(e,t,n){var r=[];if(p(null,e,n,r),e.depth>n){var o=l(e,t,n+1);c(f(o,d(e,t,n+1)),r)}return p(t,null,n,r),new y.Fragment(r)}function m(e,t){var n=t.depth-e.openLeft,r=t.node(n);if(!r.type.canContainFragment(e.content))throw new k("Content "+e.content+" cannot be placed in "+r.type.name);for(var o=r.copy(e.content),i=n-1;i>=0;i--)o=t.node(i).copy(y.Fragment.from(o));return{start:o.resolveNoCache(e.openLeft+n),end:o.resolveNoCache(o.content.size-e.openRight-n)
+}}var v=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.Slice=n.ReplaceError=void 0,n.replace=a;var g=e("../util/error"),y=e("./fragment"),k=n.ReplaceError=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),t}(g.ProseMirrorError),b=n.Slice=function(){function e(t,n,o,i){r(this,e),this.content=t,this.openLeft=n,this.openRight=o,this.possibleParent=i}return v(e,[{key:"toJSON",value:function(){return this.content.size?{content:this.content.toJSON(),openLeft:this.openLeft,openRight:this.openRight}:null}},{key:"size",get:function(){return this.content.size-this.openLeft-this.openRight}}],[{key:"fromJSON",value:function(t,n){return n?new e(y.Fragment.fromJSON(t,n.content),n.openLeft,n.openRight):e.empty}}]),e}();b.empty=new b(y.Fragment.empty,0,0)},{"../util/error":55,"./fragment":37}],42:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0});var i=(n.ResolvedPos=function(){function e(t,n,o){r(this,e),this.pos=t,this.path=n,this.depth=n.length/3-1,this.parentOffset=o}return o(e,[{key:"node",value:function(e){return this.path[3*e]}},{key:"index",value:function(e){return this.path[3*e+1]}},{key:"start",value:function(e){return 0==e?0:this.path[3*e-1]+1}},{key:"end",value:function(e){return this.start(e)+this.node(e).content.size}},{key:"before",value:function(e){if(!e)throw new RangeError("There is no position before the top-level node");return e==this.depth+1?this.pos:this.path[3*e-1]}},{key:"after",value:function(e){if(!e)throw new RangeError("There is no position after the top-level node");return e==this.depth+1?this.pos:this.path[3*e-1]+this.path[3*e].nodeSize}},{key:"sameDepth",value:function(e){for(var t=0,n=Math.min(this.depth,e.depth);n>t&&this.index(t)==e.index(t);)++t;return t}},{key:"sameParent",value:function(e){return this.pos-this.parentOffset==e.pos-e.parentOffset}},{key:"toString",value:function(){for(var e="",t=1;t<=this.depth;t++)e+=(e?"/":"")+this.node(t).type.name+"_"+this.index(t-1);return e+":"+this.parentOffset}},{key:"parent",get:function(){return this.node(this.depth)}},{key:"atNodeBoundary",get:function(){return this.path[this.path.length-1]==this.pos}},{key:"nodeAfter",get:function(){var e=this.parent,t=this.index(this.depth);if(t==e.childCount)return null;var n=this.pos-this.path[this.path.length-1],r=e.child(t);return n?e.child(t).cut(n):r}},{key:"nodeBefore",get:function(){var e=this.index(this.depth),t=this.pos-this.path[this.path.length-1];return t?this.parent.child(e).cut(0,t):0==e?null:this.parent.child(e-1)}}],[{key:"resolve",value:function(t,n){if(!(n>=0&&n<=t.content.size))throw new RangeError("Position "+n+" out of range");for(var r=[],o=0,i=n,a=t;;){var s=a.content.findIndex(i),u=s.index,l=s.offset,c=i-l;if(r.push(a,u,o+l),!c)break;if(a=a.child(u),a.isText)break;i=c-1,o+=l+1}return new e(n,r,i)}},{key:"resolveCached",value:function(t,n){for(var r=0;r<i.length;r++){var o=i[r];if(o.pos==n&&o.node(0)==t)return o}var u=i[a]=e.resolve(t,n);return a=(a+1)%s,u}}]),e}(),[]),a=0,s=6},{}],43:[function(e,t,n){"use strict";function r(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function o(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){var n=(0,f.copyObj)(e);for(var r in t){var o=t[r];null==o?delete n[r]:n[r]=o}return n}var s=function b(e,t,n){null===e&&(e=Function.prototype);var r=Object.getOwnPropertyDescriptor(e,t);if(void 0===r){var o=Object.getPrototypeOf(e);return null===o?void 0:b(o,t,n)}if("value"in r)return r.value;var i=r.get;if(void 0!==i)return i.call(n)},u=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.Schema=n.SchemaSpec=n.MarkType=n.Attribute=n.Text=n.Inline=n.Textblock=n.Block=n.NodeKind=n.NodeType=void 0;var l=e("./node"),c=e("./fragment"),p=e("./mark"),f=e("../util/obj"),h=function(){function e(){i(this,e)}return u(e,[{key:"getDefaultAttrs",value:function(){var e=Object.create(null);for(var t in this.attrs){var n=this.attrs[t];if(null==n["default"])return null;e[t]=n["default"]}return e}},{key:"computeAttrs",value:function(e,t){var n=Object.create(null);for(var r in this.attrs){var o=e&&e[r];if(null==o){var i=this.attrs[r];if(null!=i["default"])o=i["default"];else{if(!i.compute)throw new RangeError("No value supplied for attribute "+r);o=i.compute(this,t)}}n[r]=o}return n}},{key:"freezeAttrs",value:function(){var e=Object.create(null);for(var t in this.attrs)e[t]=this.attrs[t];Object.defineProperty(this,"attrs",{value:e})}},{key:"attrs",get:function(){return{}}}],[{key:"updateAttrs",value:function(e){Object.defineProperty(this.prototype,"attrs",{value:a(this.prototype.attrs,e)})}},{key:"getRegistry",value:function(){return this==e?null:(this.prototype.hasOwnProperty("registry")||(this.prototype.registry=Object.create(Object.getPrototypeOf(this).getRegistry())),this.prototype.registry)}},{key:"getNamespace",value:function(t){if(this==e)return null;var n=this.getRegistry();return Object.prototype.hasOwnProperty.call(n,t)||(n[t]=Object.create(Object.getPrototypeOf(this).getNamespace(t))),n[t]}},{key:"register",value:function(e,t,n){this.getNamespace(e)[t]=function(){return n}}},{key:"registerComputed",value:function(e,t,n){this.getNamespace(e)[t]=n}},{key:"cleanNamespace",value:function(e){this.getNamespace(e).__proto__=null}}]),e}(),d=n.NodeType=function(e){function t(e,n){i(this,t);var o=r(this,Object.getPrototypeOf(t).call(this));return o.name=e,o.freezeAttrs(),o.defaultAttrs=o.getDefaultAttrs(),o.schema=n,o}return o(t,e),u(t,[{key:"canContainFragment",value:function(e){for(var t=0;t<e.childCount;t++)if(!this.canContain(e.child(t)))return!1;return!0}},{key:"canContain",value:function(e){if(!this.canContainType(e.type))return!1;for(var t=0;t<e.marks.length;t++)if(!this.canContainMark(e.marks[t]))return!1;return!0}},{key:"canContainMark",value:function(e){var t=this.containsMarks;if(t===!0)return!0;if(t)for(var n=0;n<t.length;n++)if(t[n]==e.name)return!0;return!1}},{key:"canContainType",value:function(e){return e.kind&&e.kind.isSubKind(this.contains)}},{key:"canContainContent",value:function(e){return e.contains&&e.contains.isSubKind(this.contains)}},{key:"findConnection",value:function(e){return e.kind&&this.findConnectionToKind(e.kind)}},{key:"findConnectionToKind",value:function(e){var t=this.schema.cached.connections,n=this.name+"-"+e.id;return n in t?t[n]:t[n]=this.findConnectionToKindInner(e)}},{key:"findConnectionToKindInner",value:function(e){if(e.isSubKind(this.contains))return[];for(var t=Object.create(null),n=[{from:this,via:[]}];n.length;){var r=n.shift();for(var o in this.schema.nodes){var i=this.schema.nodes[o];if(i.contains&&i.defaultAttrs&&!(i.contains.id in t)&&r.from.canContainType(i)){var a=r.via.concat(i);if(e.isSubKind(i.contains))return a;n.push({from:i,via:a}),t[i.contains.id]=!0}}}}},{key:"computeAttrs",value:function(e,n){return!e&&this.defaultAttrs?this.defaultAttrs:s(Object.getPrototypeOf(t.prototype),"computeAttrs",this).call(this,e,n)}},{key:"create",value:function(e,t,n){return new l.Node(this,this.computeAttrs(e,t),c.Fragment.from(t),p.Mark.setFrom(n))}},{key:"checkContent",value:function(e,t){if(0==e.size)return this.canBeEmpty;for(var n=0;n<e.childCount;n++)if(!this.canContain(e.child(n)))return!1;return!0}},{key:"fixContent",value:function(e,t){return this.defaultContent()}},{key:"isBlock",get:function(){return!1}},{key:"isTextblock",get:function(){return!1}},{key:"isInline",get:function(){return!1}},{key:"isText",get:function(){return!1}},{key:"selectable",get:function(){return!0}},{key:"draggable",get:function(){return!1}},{key:"locked",get:function(){return!1}},{key:"contains",get:function(){return null}},{key:"kind",get:function(){return null}},{key:"canBeEmpty",get:function(){return!0}},{key:"containsMarks",get:function(){return!1}}],[{key:"compile",value:function(e,t){var n=Object.create(null);for(var r in e)n[r]=new e[r](r,t);if(!n.doc)throw new RangeError("Every schema needs a 'doc' type");if(!n.text)throw new RangeError("Every schema needs a 'text' type");return n}}]),t}(h),m=n.NodeKind=function(){function e(t,n,r){var o=this;i(this,e),this.name=t,this.id=++e.nextID,this.supers=Object.create(null),this.supers[this.id]=this,this.subs=r||[],n&&n.forEach(function(e){return o.addSuper(e)}),r&&r.forEach(function(e){return o.addSub(e)})}return u(e,[{key:"sharedSuperKind",value:function(e){if(this.isSubKind(e))return e;if(e.isSubKind(this))return this;var t=void 0;for(var n in this.supers){var r=e.supers[n];!r||t&&!r.isSupKind(t)||(t=r)}return t}},{key:"addSuper",value:function(e){for(var t in e.supers)this.supers[t]=e.supers[t],e.subs.push(this)}},{key:"addSub",value:function(e){var t=this;if(this.supers[e.id])throw new RangeError("Circular subkind relation");e.supers[this.id]=!0,e.subs.forEach(function(e){return t.addSub(e)})}},{key:"isSubKind",value:function(e){return e&&e.id in this.supers||!1}}]),e}();m.nextID=0,m.block=new m("block"),m.inline=new m("inline"),m.text=new m("text",[m.inline]);var v=n.Block=function(e){function t(){return i(this,t),r(this,Object.getPrototypeOf(t).apply(this,arguments))}return o(t,e),u(t,[{key:"defaultContent",value:function(){var e=this.schema.defaultTextblockType().create(),t=this.findConnection(e.type);if(!t)throw new RangeError("Can't create default content for "+this.name);for(var n=t.length-1;n>=0;n--)e=t[n].create(null,e);return c.Fragment.from(e)}},{key:"contains",get:function(){return m.block}},{key:"kind",get:function(){return m.block}},{key:"isBlock",get:function(){return!0}},{key:"canBeEmpty",get:function(){return null==this.contains}}]),t}(d),g=(n.Textblock=function(e){function t(){return i(this,t),r(this,Object.getPrototypeOf(t).apply(this,arguments))}return o(t,e),u(t,[{key:"contains",get:function(){return m.inline}},{key:"containsMarks",get:function(){return!0}},{key:"isTextblock",get:function(){return!0}},{key:"canBeEmpty",get:function(){return!0}}]),t}(v),n.Inline=function(e){function t(){return i(this,t),r(this,Object.getPrototypeOf(t).apply(this,arguments))}return o(t,e),u(t,[{key:"kind",get:function(){return m.inline}},{key:"isInline",get:function(){return!0}}]),t}(d)),y=(n.Text=function(e){function t(){return i(this,t),r(this,Object.getPrototypeOf(t).apply(this,arguments))}return o(t,e),u(t,[{key:"create",value:function(e,t,n){return new l.TextNode(this,this.computeAttrs(e,t),t,n)}},{key:"selectable",get:function(){return!1}},{key:"isText",get:function(){return!0}},{key:"kind",get:function(){return m.text}}]),t}(g),n.Attribute=function w(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];i(this,w),this["default"]=e["default"],this.compute=e.compute,this.label=e.label},n.MarkType=function(e){function t(e,n,o){i(this,t);var a=r(this,Object.getPrototypeOf(t).call(this));a.name=e,a.freezeAttrs(),a.rank=n,a.schema=o;var s=a.getDefaultAttrs();return a.instance=s&&new p.Mark(a,s),a}return o(t,e),u(t,[{key:"create",value:function(e){return!e&&this.instance?this.instance:new p.Mark(this,this.computeAttrs(e))}},{key:"removeFromSet",value:function(e){for(var t=0;t<e.length;t++)if(e[t].type==this)return e.slice(0,t).concat(e.slice(t+1));return e}},{key:"isInSet",value:function(e){for(var t=0;t<e.length;t++)if(e[t].type==this)return e[t]}},{key:"inclusiveRight",get:function(){return!0}}],[{key:"getOrder",value:function(e){var t=[];for(var n in e)t.push({name:n,rank:e[n].rank});t.sort(function(e,t){return e.rank-t.rank});for(var r=Object.create(null),o=0;o<t.length;o++)r[t[o].name]=o;return r}},{key:"compile",value:function(e,t){var n=this.getOrder(e),r=Object.create(null);for(var o in e)r[o]=new e[o](o,n[o],t);return r}},{key:"rank",get:function(){return 50}}]),t}(h)),k=(n.SchemaSpec=function(){function e(t,n){i(this,e),this.nodes=t||{},this.marks=n||{}}return u(e,[{key:"update",value:function(t,n){return new e(t?a(this.nodes,t):this.nodes,n?a(this.marks,n):this.marks)}}]),e}(),function(){function e(t){i(this,e),this.spec=t,this.nodes=d.compile(t.nodes,this),this.marks=y.compile(t.marks,this);for(var n in this.nodes)if(n in this.marks)throw new RangeError(n+" can not be both a node and a mark");this.cached=Object.create(null),this.cached.connections=Object.create(null),this.node=this.node.bind(this),this.text=this.text.bind(this),this.nodeFromJSON=this.nodeFromJSON.bind(this),this.markFromJSON=this.markFromJSON.bind(this)}return u(e,[{key:"node",value:function(e,t,n,r){if("string"==typeof e)e=this.nodeType(e);else{if(!(e instanceof d))throw new RangeError("Invalid node type: "+e);if(e.schema!=this)throw new RangeError("Node type from different schema used ("+e.name+")")}return e.create(t,n,r)}},{key:"text",value:function(e,t){return this.nodes.text.create(null,e,p.Mark.setFrom(t))}},{key:"defaultTextblockType",value:function(){var e=this.cached.defaultTextblockType;if(void 0!==e)return e;for(var t in this.nodes)if(this.nodes[t].defaultTextblock)return this.cached.defaultTextblockType=this.nodes[t];return this.cached.defaultTextblockType=null}},{key:"mark",value:function(e,t){var n=this.marks[e];if(!n)throw new RangeError("No mark named "+e);return n.create(t)}},{key:"nodeFromJSON",value:function(e){return l.Node.fromJSON(this,e)}},{key:"markFromJSON",value:function(e){var t=this.marks[e._],n=null;for(var r in e)"_"!=r&&(n||(n=Object.create(null)),n[r]=e[r]);return n?t.create(n):t.instance}},{key:"nodeType",value:function(e){var t=this.nodes[e];if(!t)throw new RangeError("Unknown node type: "+e);return t}},{key:"registry",value:function t(e,n){for(var r=0;2>r;r++){var o=r?this.marks:this.nodes;for(var i in o){var a=o[i],t=a.registry,s=t&&t[e];if(s)for(var u in s){var l=s[u](a);null!=l&&n(u,l,a,i)}}}}}]),e}());n.Schema=k},{"../util/obj":58,"./fragment":37,"./mark":39,"./node":40}],44:[function(e,t,n){"use strict";function r(e,t){if(e.depth!=t.depth)return!1;for(var n=0;n<e.depth;n++)if(e.index(n)!=t.index(n))return!1;return e.parentOffset<=t.parentOffset}function o(e,t,n){return!!a(e.resolve(t),e.resolve(null==n?t:n))}function i(e,t){var n=e.sameDepth(t);return e.node(n).isTextblock&&--n,n&&e.before(n)>=t.after(n)?null:n}function a(e,t){var n=i(e,t);if(null==n)return null;for(var r=e.node(n),o=n-1;o>=0;--o)if(e.node(o).type.canContainContent(r.type))return{depth:o,shared:n,unwrap:!1};if(r.isBlock)for(var o=n-1;o>=0;--o){for(var a=e.node(o),s=e.index(n),u=Math.min(t.index(n)+1,r.childCount);u>s;s++)a.type.canContainContent(r.child(s).type);return{depth:o,shared:n,unwrap:!0}}}function s(e,t,n,r){return!!u(e.resolve(t),e.resolve(null==n?t:n),r)}function u(e,t,n){var r=i(e,t);if(null==r)return null;var o=e.node(r),a=o.type.findConnection(n),s=n.findConnection(o.child(e.index(r)).type);return a&&s?{shared:r,around:a,inside:s}:void 0}Object.defineProperty(n,"__esModule",{value:!0}),n.canLift=o,n.canWrap=s;var l=e("../model"),c=e("./transform"),p=e("./step"),f=e("./map");p.Step.define("ancestor",{apply:function(e,t){var n=e.resolve(t.from),o=e.resolve(t.to);if(!r(n,o))return p.StepResult.fail("Not a flat range");var i=t.param,a=i.depth,s=void 0===a?0:a,u=i.types,c=void 0===u?[]:u,f=i.attrs,h=void 0===f?[]:f;if(0==s&&0==c.length)return p.StepResult.ok(e);for(var d=0,m=n.depth;s>d;d++,m--)if(n.start(m)!=n.pos-d||o.end(m)!=o.pos+d)return p.StepResult.fail("Parent at depth "+m+" not fully covered");var v=n.parent,g=void 0;if(c.length){var y=c[c.length-1];if(!y.contains)throw new RangeError("Can not wrap content in node type "+y.name);var k=v.content.cut(n.parentOffset,o.parentOffset);if(!y.checkContent(k,h[c.length-1]))return p.StepResult.fail("Content can not be wrapped in ancestor "+y.name);for(var d=c.length-1;d>=0;d--)k=l.Fragment.from(c[d].create(h[d],k));g=new l.Slice(k,0,0)}else g=new l.Slice(v.content,0,0);return p.StepResult.fromReplace(e,n.pos-s,o.pos+s,g)},posMap:function(e){var t=e.param.depth||0,n=e.param.types?e.param.types.length:0;return t==n&&2>t?f.PosMap.empty:new f.PosMap([e.from-t,t,n,e.to,t,n])},invert:function(e,t){for(var n=[],r=[],o=t.resolve(e.from),i=e.param.depth||0,a=e.param.types?e.param.types.length:0,s=0;i>s;s++){var u=o.node(o.depth-s);n.unshift(u.type),r.unshift(u.attrs)}var l=a-i;return new p.Step("ancestor",e.from+l,e.to+l,{depth:a,types:n,attrs:r})},paramToJSON:function(e){return{depth:e.depth,types:e.types&&e.types.map(function(e){return e.name}),attrs:e.attrs}},paramFromJSON:function(e,t){return{depth:t.depth,types:t.types&&t.types.map(function(t){return e.nodeType(t)}),attrs:t.attrs}}}),c.Transform.prototype.lift=function(e){var t=arguments.length<=1||void 0===arguments[1]?e:arguments[1],n=arguments.length<=2||void 0===arguments[2]?!1:arguments[2],r=this.doc.resolve(e),o=this.doc.resolve(t),i=a(r,o);if(!i){if(!n)throw new RangeError("No valid lift target");return this}for(var s=i.depth,u=i.shared,l=i.unwrap,c=r.before(u+1),p=o.after(u+1),f=u;f>s;f--)if(o.index(f)+1<o.node(f).childCount){this.split(o.after(f+1),f-s);break}for(var f=u;f>s;f--)if(r.index(f)>0){var h=f-s;this.split(r.before(f+1),h),c+=2*h,p+=2*h;break}if(l){for(var d=c,m=r.node(u),v=r.index(u),g=o.index(u)+1,y=!0;g>v;v++,y=!1)y||(this.join(d),p-=2),d+=m.child(v).nodeSize-(y?0:2);u++,c++,p--}return this.step("ancestor",c,p,{depth:u-s})},c.Transform.prototype.wrap=function(e){var t=arguments.length<=1||void 0===arguments[1]?e:arguments[1],n=arguments[2],r=arguments[3],o=this.doc.resolve(e),i=this.doc.resolve(t),a=u(o,i,n);if(!a)throw new RangeError("Wrap not possible");var s=a.shared,l=a.around,c=a.inside,p=l.concat(n).concat(c),f=l.map(function(){return null}).concat(r).concat(c.map(function(){return null})),h=o.before(s+1);if(this.step("ancestor",h,i.after(s+1),{types:p,attrs:f}),c.length)for(var d=h+p.length,m=o.node(s),v=o.index(s),g=i.index(s)+1,y=!0;g>v;v++,y=!1)y||this.split(d,c.length),d+=m.child(v).nodeSize+(y?0:2*c.length);return this},c.Transform.prototype.setBlockType=function(e){var t=arguments.length<=1||void 0===arguments[1]?e:arguments[1],n=this,r=arguments[2],o=arguments[3];if(!r.isTextblock)throw new RangeError("Type given to setBlockType should be a textblock");return this.doc.nodesBetween(e,t,function(e,t){if(e.isTextblock&&!e.hasMarkup(r,o)){var i=t+1,a=i+e.content.size;return n.clearMarkup(i,a,r),n.step("ancestor",i,a,{depth:1,types:[r],attrs:[o]}),!1}}),this},c.Transform.prototype.setNodeType=function(e,t,n){var r=this.doc.nodeAt(e);if(!r)throw new RangeError("No node at given position");return t||(t=r.type),r.type.contains?this.step("ancestor",e+1,e+1+r.content.size,{depth:1,types:[t],attrs:[n]}):this.replaceWith(e,e+r.nodeSize,t.create(n,null,r.marks))}},{"../model":38,"./map":47,"./step":51,"./transform":52}],45:[function(e,t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.Remapping=n.MapResult=n.PosMap=n.joinable=n.joinPoint=n.canWrap=n.canLift=n.StepResult=n.Step=n.TransformError=n.Transform=void 0;var r=e("./transform");Object.defineProperty(n,"Transform",{enumerable:!0,get:function(){return r.Transform}}),Object.defineProperty(n,"TransformError",{enumerable:!0,get:function(){return r.TransformError}});var o=e("./step");Object.defineProperty(n,"Step",{enumerable:!0,get:function(){return o.Step}}),Object.defineProperty(n,"StepResult",{enumerable:!0,get:function(){return o.StepResult}});var i=e("./ancestor");Object.defineProperty(n,"canLift",{enumerable:!0,get:function(){return i.canLift}}),Object.defineProperty(n,"canWrap",{enumerable:!0,get:function(){return i.canWrap}});var a=e("./join");Object.defineProperty(n,"joinPoint",{enumerable:!0,get:function(){return a.joinPoint}}),Object.defineProperty(n,"joinable",{enumerable:!0,get:function(){return a.joinable}});var s=e("./map");Object.defineProperty(n,"PosMap",{enumerable:!0,get:function(){return s.PosMap}}),Object.defineProperty(n,"MapResult",{enumerable:!0,get:function(){return s.MapResult}}),Object.defineProperty(n,"Remapping",{enumerable:!0,get:function(){return s.Remapping}}),e("./mark"),e("./split"),e("./replace")},{"./ancestor":44,"./join":46,"./map":47,"./mark":48,"./replace":49,"./split":50,"./step":51,"./transform":52}],46:[function(e,t,n){"use strict";function r(e,t){var n=e.resolve(t);return o(n.nodeBefore,n.nodeAfter)}function o(e,t){return e&&t&&!e.isText&&e.type.contains&&e.type.canContainContent(t.type)}function i(e,t){for(var n=arguments.length<=2||void 0===arguments[2]?-1:arguments[2],r=e.resolve(t),i=r.depth;;i--){var a=void 0,s=void 0;if(i==r.depth?(a=r.nodeBefore,s=r.nodeAfter):n>0?(a=r.node(i+1),s=r.node(i).maybeChild(r.index(i)+1)):(a=r.node(i).maybeChild(r.index(i)-1),s=r.node(i+1)),a&&!a.isTextblock&&o(a,s))return t;if(0==i)break;t=0>n?r.before(i):r.after(i)}}Object.defineProperty(n,"__esModule",{value:!0}),n.joinable=r,n.joinPoint=i;var a=e("../model"),s=e("./transform"),u=e("./step"),l=e("./map");u.Step.define("join",{apply:function(e,t){var n=e.resolve(t.from),r=e.resolve(t.to);return n.parentOffset<n.parent.content.size||r.parentOffset>0||r.pos-n.pos!=2?u.StepResult.fail("Join positions not around a split"):u.StepResult.fromReplace(e,n.pos,r.pos,a.Slice.empty)},posMap:function(e){return new l.PosMap([e.from,2,0])},invert:function(e,t){var n=t.resolve(e.from),r=n.depth-1,o=n.node(r).child(n.index(r)+1),i=null;return n.parent.sameMarkup(o)||(i={type:o.type,attrs:o.attrs}),new u.Step("split",e.from,e.from,i)}}),s.Transform.prototype.join=function(e){for(var t=arguments.length<=1||void 0===arguments[1]?1:arguments[1],n=arguments.length<=2||void 0===arguments[2]?!1:arguments[2],r=0;t>r;r++){var o=this.doc.resolve(e);if(0==o.parentOffset||o.parentOffset==o.parent.content.size||!o.nodeBefore.type.canContainContent(o.nodeAfter.type)){if(!n)throw new RangeError("Nothing to join at "+e);break}this.step("join",e-1,e+1),e--}return this}},{"../model":38,"./map":47,"./step":51,"./transform":52}],47:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){return e+t*p}function i(e){return e&c}function a(e){return(e-(e&c))/p}function s(e,t,n){for(var r=0;r<e.length;r++)t=e[r].map(t,n);return t}function u(e,t,n){for(var r=!1,o=0;o<e.length;o++){var i=e[o].mapResult(t,n);t=i.pos,i.deleted&&(r=!0)}return new f(t,r)}var l=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.mapThrough=s,n.mapThroughResult=u;var c=65535,p=Math.pow(2,16),f=n.MapResult=function d(e){var t=arguments.length<=1||void 0===arguments[1]?!1:arguments[1],n=arguments.length<=2||void 0===arguments[2]?null:arguments[2];r(this,d),this.pos=e,this.deleted=t,this.recover=n},h=n.PosMap=function(){function e(t,n){r(this,e),this.ranges=t,this.inverted=n||!1}return l(e,[{key:"recover",value:function(e){var t=0,n=i(e);if(!this.inverted)for(var r=0;n>r;r++)t+=this.ranges[3*r+2]-this.ranges[3*r+1];return this.ranges[3*n]+t+a(e)}},{key:"mapResult",value:function(e,t){return this._map(e,t,!1)}},{key:"map",value:function(e,t){return this._map(e,t,!0)}},{key:"_map",value:function(e,t,n){for(var r=0,i=this.inverted?2:1,a=this.inverted?1:2,s=0;s<this.ranges.length;s+=3){var u=this.ranges[s]-(this.inverted?r:0);if(u>e)break;var l=this.ranges[s+i],c=this.ranges[s+a],p=u+l;if(p>=e){var h=l?e==u?-1:e==p?1:t:t,d=u+r+(0>h?0:c);if(n)return d;var m=o(s/3,e-u);return new f(d,e!=u&&e!=p,m)}r+=c-l}return n?e+r:new f(e+r)}},{key:"touches",value:function(e,t){for(var n=0,r=i(t),o=this.inverted?2:1,a=this.inverted?1:2,s=0;s<this.ranges.length;s+=3){var u=this.ranges[s]-(this.inverted?n:0);if(u>e)break;var l=this.ranges[s+o],c=u+l;if(c>=e&&s==3*r)return!0;n+=this.ranges[s+a]-l}return!1}},{key:"invert",value:function(){return new e(this.ranges,!this.inverted)}},{key:"toString",value:function(){return(this.inverted?"-":"")+JSON.stringify(this.ranges)}}]),e}();h.empty=new h([]);n.Remapping=function(){function e(){var t=arguments.length<=0||void 0===arguments[0]?[]:arguments[0],n=arguments.length<=1||void 0===arguments[1]?[]:arguments[1];r(this,e),this.head=t,this.tail=n,this.mirror=Object.create(null)}return l(e,[{key:"addToFront",value:function(e,t){this.head.push(e);var n=-this.head.length;return null!=t&&(this.mirror[n]=t),n}},{key:"addToBack",value:function(e,t){this.tail.push(e);var n=this.tail.length-1;return null!=t&&(this.mirror[t]=n),n}},{key:"get",value:function(e){return 0>e?this.head[-e-1]:this.tail[e]}},{key:"mapResult",value:function(e,t){return this._map(e,t,!1)}},{key:"map",value:function(e,t){return this._map(e,t,!0)}},{key:"_map",value:function(e,t,n){for(var r=!1,o=null,i=-this.head.length;i<this.tail.length;i++){var a=this.get(i),s=void 0;if(null!=(s=o&&o[i])&&a.touches(e,s))e=a.recover(s);else{var u=a.mapResult(e,t);if(null!=u.recover){var l=this.mirror[i];if(null!=l){if(u.deleted){i=l,e=this.get(l).recover(u.recover);continue}(o||(o=Object.create(null)))[l]=u.recover}}u.deleted&&(r=!0),e=u.pos}}return n?e:new f(e,r)}},{key:"toString",value:function(){for(var e=[],t=-this.head.length;t<this.tail.length;t++)e.push(t+":"+this.get(t)+(null!=this.mirror[t]?"->"+this.mirror[t]:""));return e.join("\n")}}]),e}()},{}],48:[function(e,t,n){"use strict";function r(e,t,n){return e.content.size&&(e=e.copy(o(e.content,t,e))),e.isInline&&(e=t(e,n)),e}function o(e,t,n){for(var o=[],a=0;a<e.childCount;a++)o.push(r(e.child(a),t,n));return i.Fragment.fromArray(o)}var i=e("../model"),a=e("./transform"),s=e("./step");s.Step.define("addMark",{apply:function(e,t){var n=e.slice(t.from,t.to),r=e.resolve(t.from);return n.content=o(n.content,function(e,n){return n.type.canContainMark(t.param.type)?e.mark(t.param.addToSet(e.marks)):e},r.node(r.depth-n.openLeft)),s.StepResult.fromReplace(e,t.from,t.to,n)},invert:function(e){return new s.Step("removeMark",e.from,e.to,e.param)},paramToJSON:function(e){return e.toJSON()},paramFromJSON:function(e,t){return e.markFromJSON(t)}}),a.Transform.prototype.addMark=function(e,t,n){var r=this,o=[],i=[],a=null,u=null;return this.doc.nodesBetween(e,t,function(r,l,c){if(r.isInline){var p=r.marks;if(n.isInSet(p)||!c.type.canContainMark(n.type))u=a=null;else{var f=Math.max(l,e),h=Math.min(l+r.nodeSize,t),d=n.type.isInSet(p);d?a&&a.param.eq(d)?a.to=h:o.push(a=new s.Step("removeMark",f,h,d)):a=null,u?u.to=h:i.push(u=new s.Step("addMark",f,h,n))}}}),o.forEach(function(e){return r.step(e)}),i.forEach(function(e){return r.step(e)}),this},s.Step.define("removeMark",{apply:function(e,t){var n=e.slice(t.from,t.to);return n.content=o(n.content,function(e){return e.mark(t.param.removeFromSet(e.marks))}),s.StepResult.fromReplace(e,t.from,t.to,n)},invert:function(e){return new s.Step("addMark",e.from,e.to,e.param)},paramToJSON:function(e){return e.toJSON()},paramFromJSON:function(e,t){return e.markFromJSON(t)}}),a.Transform.prototype.removeMark=function(e,t){var n=this,r=arguments.length<=2||void 0===arguments[2]?null:arguments[2],o=[],a=0;return this.doc.nodesBetween(e,t,function(n,s){if(n.isInline){a++;var u=null;if(r instanceof i.MarkType){var l=r.isInSet(n.marks);l&&(u=[l])}else r?r.isInSet(n.marks)&&(u=[r]):u=n.marks;if(u&&u.length)for(var c=Math.min(s+n.nodeSize,t),p=0;p<u.length;p++){for(var f=u[p],l=void 0,h=0;h<o.length;h++){var d=o[h];d.step==a-1&&f.eq(o[h].style)&&(l=d)}l?(l.to=c,l.step=a):o.push({style:f,from:Math.max(s,e),to:c,step:a})}}}),o.forEach(function(e){return n.step("removeMark",e.from,e.to,e.style)}),this},a.Transform.prototype.clearMarkup=function(e,t,n){var r=this,o=[];this.doc.nodesBetween(e,t,function(i,a){if(i.isInline){if(n?!n.canContainType(i.type):!i.type.isText)return void o.push(new s.Step("replace",a,a+i.nodeSize));for(var u=0;u<i.marks.length;u++){var l=i.marks[u];n&&n.canContainMark(l.type)||r.step("removeMark",Math.max(a,e),Math.min(a+i.nodeSize,t),l)}}});for(var i=o.length-1;i>=0;i--)this.step(o[i]);return this}},{"../model":38,"./step":51,"./transform":52}],49:[function(e,t,n){"use strict";function r(e,t,n){var r=e.sameDepth(t),a=d(e,n),s=o(a);s&&(r=Math.min(s.depth,r)),w=-1e10;var u=p(e.node(r).type,i(e,t,r,a),e,t,r);return{fitted:new g.Slice(u,e.depth-r,t.depth-r),distAfter:w-(t.depth-r)}}function o(e){for(var t=0;t<e.length;t++)if(e[t])return e[t]}function i(e,t,n,r){var o=e.depth>n&&e.node(n+1),u=t.depth>n&&t.node(n+1),p=r[n];if(o&&u&&o.type.canContainContent(u.type)&&!p)return g.Fragment.from(f(o,i(e,t,n+1,r),e,t,n+1));var h=g.Fragment.empty;return p&&(h=c(p.content,p.openLeft),p.isEnd&&(w=p.openRight)),w--,o&&(h=h.addToStart(f(o,a(e,n+1,r),e,null,n+1))),u?h=s(h,t,n+1,p?p.openRight:0):p&&(h=l(h,p.openRight)),w++,h}function a(e,t,n){var r=n[t],o=g.Fragment.empty;return r&&(o=l(r.content,r.openRight),r.isEnd&&(w=r.openRight)),w--,e.depth>t&&(o=o.addToStart(f(e.node(t+1),a(e,t+1,n),e,null,t+1))),w++,o}function s(e,t,n,r){var o=t.node(n);if(0==r||!o.type.canContainContent(e.lastChild.type)){var i=f(o,u(t,n),null,t,n);return w+=i.nodeSize,l(e,r).addToEnd(i)}var a=e.lastChild.content;return n<t.depth&&(a=s(a,t,n+1,r-1)),e.replaceChild(e.childCount-1,o.copy(a))}function u(e,t){return e.depth==t?g.Fragment.empty:g.Fragment.from(f(e.node(t+1),u(e,t+1),null,e,t+1))}function l(e,t){if(0==t)return e;var n=e.lastChild,r=f(n,l(n.content,t-1));return r==n?e:e.replaceChild(e.childCount-1,r)}function c(e,t){if(0==t)return e;var n=e.firstChild,r=f(n,n.content);return r==n?e:e.replaceChild(0,r)}function p(e,t,n,r,o){if(e.canBeEmpty)return t;var i=t.size||n&&(n.depth>o||n.index(o))||r&&(r.depth>o||r.index(o)<r.node(o).childCount);return i?t:e.defaultContent()}function f(e,t,n,r,o){return e.copy(p(e.type,t,n,r,o))}function h(e,t){for(var n=e.content,r=1;t>r;r++)n=n.firstChild.content;return n.firstChild}function d(e,t){for(var n=e.depth,r=null,o=0,i=[],a=null,s=t.openLeft;;--s){var u=void 0,l=void 0,c=void 0;if(s>=0){if(s>0){var p=h(t,s);u=p.type,l=p.attrs,c=p.content}else 0==s&&(c=t.content);s<t.openLeft&&(c=c.cut(c.firstChild.nodeSize))}else c=g.Fragment.empty,u=a[a.length+s-1];if(r&&(c=c.addToStart(r)),0==c.size&&0>=s)break;var f=m(u,c,e,n);if(f>-1){if(c.size>0&&(i[f]={content:c,openLeft:o,openRight:s>0?0:t.openRight-s,isEnd:0>=s,depth:f}),0>=s)break;r=null,o=0,n=Math.max(0,f-1)}else{if(0==s){if(a=e.node(0).type.findConnectionToKind(c.leastSuperKind()),!a)break;a.unshift(e.node(0).type),u=a[a.length-1]}r=u.create(l,c),o++}}return i}function m(e,t,n,r){for(var o=r;o>=0;o--){var i=n.node(o).type;if(e?i.canContainContent(e):i.canContainFragment(t))return o}return-1}function v(e,t,n){for(var r=t.sameDepth(n),o=n.end(n.depth),i=o+1,a=n.depth-1;a>r&&n.index(a)+1==n.node(a).childCount;)--a,++i;a>r&&e.split(i,a-r);for(var s=[],u=[],l=r+1;l<=t.depth;l++){var c=t.node(l);s.push(c.type),u.push(c.attrs)}e.step("ancestor",n.pos,o,{depth:n.depth-r,types:s,attrs:u}),e.join(n.pos-(n.depth-r),t.depth-r)}var g=e("../model"),y=e("./transform"),k=e("./step"),b=e("./map");
+k.Step.define("replace",{apply:function(e,t){return k.StepResult.fromReplace(e,t.from,t.to,t.param)},posMap:function(e){return new b.PosMap([e.from,e.to-e.from,e.param.size])},invert:function(e,t){return new k.Step("replace",e.from,e.from+e.param.size,t.slice(e.from,e.to))},paramToJSON:function(e){return e.toJSON()},paramFromJSON:function(e,t){return g.Slice.fromJSON(e,t)}}),y.Transform.prototype["delete"]=function(e,t){return e!=t&&this.replace(e,t,g.Slice.empty),this},y.Transform.prototype.replace=function(e){var t=arguments.length<=1||void 0===arguments[1]?e:arguments[1],n=arguments.length<=2||void 0===arguments[2]?g.Slice.empty:arguments[2],o=this.doc.resolve(e),i=this.doc.resolve(t),a=r(o,i,n),s=a.fitted,u=a.distAfter,l=s.size;if(e==t&&!l)return this;if(this.step("replace",e,t,s),!l||!i.parent.isTextblock)return this;var c=e+l,p=n.size?0>u?-1:c-u:e,f=void 0;return-1!=p&&p!=c&&(f=this.doc.resolve(p)).parent.isTextblock&&f.parent.type.canContainFragment(i.parent.content)?(v(this,f,this.doc.resolve(c)),this):this},y.Transform.prototype.replaceWith=function(e,t,n){return this.replace(e,t,new g.Slice(g.Fragment.from(n),0,0))},y.Transform.prototype.insert=function(e,t){return this.replaceWith(e,e,t)},y.Transform.prototype.insertText=function(e,t){return this.insert(e,this.doc.type.schema.text(t,this.doc.marksAt(e)))},y.Transform.prototype.insertInline=function(e,t){return this.insert(e,t.mark(this.doc.marksAt(e)))};var w=0},{"../model":38,"./map":47,"./step":51,"./transform":52}],50:[function(e,t,n){"use strict";var r=e("../model"),o=e("./transform"),i=e("./step"),a=e("./map");i.Step.define("split",{apply:function(e,t){var n=e.resolve(t.from),o=n.parent,a=[o.copy(),t.param?t.param.type.create(t.attrs):o.copy()];return i.StepResult.fromReplace(e,n.pos,n.pos,new r.Slice(r.Fragment.fromArray(a),1,1))},posMap:function(e){return new a.PosMap([e.from,0,2])},invert:function(e){return new i.Step("join",e.from,e.from+2)},paramToJSON:function(e){return e&&{type:e.type.name,attrs:e.attrs}},paramFromJSON:function(e,t){return t&&{type:e.nodeType(t.type),attrs:t.attrs}}}),o.Transform.prototype.split=function(e){for(var t=arguments.length<=1||void 0===arguments[1]?1:arguments[1],n=arguments[2],r=arguments[3],o=0;t>o;o++)this.step("split",e+o,e+o,0==o&&n?{type:n,attrs:r}:null);return this},o.Transform.prototype.splitIfNeeded=function(e){for(var t=arguments.length<=1||void 0===arguments[1]?1:arguments[1],n=this.doc.resolve(e),r=!0,o=0;t>o;o++){var i=n.depth-o,a=0==o?n.pos:r?n.before(i+1):n.after(i+1);if(a==n.start(i))r=!0;else{if(a!=n.end(i))return this.split(a,t-o);r=!1}}return this}},{"../model":38,"./map":47,"./step":51,"./transform":52}],51:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.StepResult=n.Step=void 0;var i=e("../model"),a=e("./map"),s=(n.Step=function(){function e(t,n,o){var i=arguments.length<=3||void 0===arguments[3]?null:arguments[3];if(r(this,e),!(t in s))throw new RangeError("Unknown step type: "+t);this.type=t,this.from=n,this.to=o,this.param=i}return o(e,[{key:"apply",value:function(e){return s[this.type].apply(e,this)}},{key:"posMap",value:function(){var e=s[this.type];return e.posMap?e.posMap(this):a.PosMap.empty}},{key:"invert",value:function(e){return s[this.type].invert(this,e)}},{key:"map",value:function(t){var n=t.mapResult(this.from,1),r=this.to==this.from?n:t.mapResult(this.to,-1);return n.deleted&&r.deleted?null:new e(this.type,n.pos,Math.max(n.pos,r.pos),this.param)}},{key:"toJSON",value:function(){var e=s[this.type];return{type:this.type,from:this.from,to:this.to,param:e.paramToJSON?e.paramToJSON(this.param):this.param}}},{key:"toString",value:function(){return this.type+"@"+this.from+"-"+this.to}}],[{key:"fromJSON",value:function(t,n){var r=s[n.type];return new e(n.type,n.from,n.to,r.paramFromJSON?r.paramFromJSON(t,n.param):n.param)}},{key:"define",value:function(e,t){s[e]=t}}]),e}(),Object.create(null));n.StepResult=function(){function e(t,n){r(this,e),this.doc=t,this.failed=n}return o(e,null,[{key:"ok",value:function(t){return new e(t,null)}},{key:"fail",value:function(t){return new e(null,t)}},{key:"fromReplace",value:function(t,n,r,o){try{return e.ok(t.replace(n,r,o))}catch(a){if(a instanceof i.ReplaceError)return e.fail(a.message);throw a}}}]),e}()},{"../model":38,"./map":47}],52:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var a=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.Transform=n.TransformError=void 0;var s=e("../util/error"),u=e("./step"),l=e("./map"),c=n.TransformError=function(e){function t(){return r(this,t),o(this,Object.getPrototypeOf(t).apply(this,arguments))}return i(t,e),t}(s.ProseMirrorError),p=function(){function e(t){r(this,e),this.doc=t,this.docs=[],this.steps=[],this.maps=[]}return a(e,[{key:"step",value:function(e,t,n,r){"string"==typeof e&&(e=new u.Step(e,t,n,r));var o=this.maybeStep(e);if(o.failed)throw new c(o.failed);return this}},{key:"maybeStep",value:function(e){var t=e.apply(this.doc);return t.failed||(this.docs.push(this.doc),this.steps.push(e),this.maps.push(e.posMap()),this.doc=t.doc),t}},{key:"mapResult",value:function(e,t){return(0,l.mapThroughResult)(this.maps,e,t)}},{key:"map",value:function(e,t){return(0,l.mapThrough)(this.maps,e,t)}},{key:"before",get:function(){return this.docs.length?this.docs[0]:this.doc}}]),e}();n.Transform=p},{"../util/error":55,"./map":47,"./step":51}],53:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t,n){var r=(0,a.elt)("button",{"class":"ProseMirror-prompt-close"}),o=(0,a.elt)("div",{"class":"ProseMirror-prompt"},t,r),i=e.wrapper.getBoundingClientRect();if(e.wrapper.appendChild(o),n&&n.pos)o.style.left=n.pos.left-i.left+"px",o.style.top=n.pos.top-i.top+"px";else{var s=o.getBoundingClientRect(),u=Math.max(0,i.left)+Math.min(window.innerWidth,i.right)-s.width,l=Math.max(0,i.top)+Math.min(window.innerHeight,i.bottom)-s.height;o.style.left=u/2-i.left+"px",o.style.top=l/2-i.top+"px"}var c=function p(){e.off("interaction",p),o.parentNode&&(o.parentNode.removeChild(o),n&&n.onClose&&n.onClose())};return r.addEventListener("click",c),e.on("interaction",c),{close:c}}var i=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.ParamPrompt=void 0,n.openPrompt=o;var a=e("../dom"),s=n.ParamPrompt=function(){function e(t,n){var o=this;r(this,e),this.pm=t,this.command=n,this.doClose=null,this.fields=n.params.map(function(e){if(!(e.type in o.paramTypes))throw new RangeError("Unsupported parameter type: "+e.type);return o.paramTypes[e.type].render.call(o.pm,e,o.defaultValue(e))});var i=(0,a.elt)("h5",{},n.spec&&n.spec.label?t.translate(n.spec.label):""),s=(0,a.elt)("button",{type:"submit","class":"ProseMirror-prompt-submit"},"Ok"),u=(0,a.elt)("button",{type:"button","class":"ProseMirror-prompt-cancel"},"Cancel");u.addEventListener("click",function(){return o.close()}),this.form=(0,a.elt)("form",null,i,this.fields.map(function(e){return(0,a.elt)("div",null,e)}),(0,a.elt)("div",{"class":"ProseMirror-prompt-buttons"},s," ",u))}return i(e,[{key:"close",value:function(){this.doClose&&(this.doClose(),this.doClose=null)}},{key:"open",value:function(){var e=this;this.close();var t=this.prompt(),n=this.pm.hasFocus();this.doClose=function(){t.close(),n&&setTimeout(function(){return e.pm.focus()},50)};var r=function(){var t=e.values();t&&(e.close(),e.command.exec(e.pm,t))};this.form.addEventListener("submit",function(e){e.preventDefault(),r()}),this.form.addEventListener("keydown",function(e){27==e.keyCode?(e.preventDefault(),t.close()):13!=e.keyCode||e.ctrlKey||e.metaKey||e.shiftKey||(e.preventDefault(),r())});var o=this.form.querySelector("input, textarea");o&&o.focus()}},{key:"values",value:function(){for(var e=[],t=0;t<this.command.params.length;t++){var n=this.command.params[t],r=this.fields[t],o=this.paramTypes[n.type],i=void 0,a=void 0;if(o.validate&&(a=o.validate(r)),a||(i=o.read.call(this.pm,r),n.validate?a=n.validate(i):i||null!=n["default"]||(a="No default value available")),a)return o.reportInvalid?o.reportInvalid.call(this.pm,r,a):this.reportInvalid(r,a),null;e.push(i)}return e}},{key:"defaultValue",value:function(e){if(e.prefill){var t=e.prefill.call(this.command.self,this.pm);if(null!=t)return t}return e["default"]}},{key:"prompt",value:function(){var e=this;return o(this.pm,this.form,{onClose:function(){return e.close()}})}},{key:"reportInvalid",value:function(e,t){var n=e.parentNode,r="left: "+(e.offsetLeft+e.offsetWidth+2)+"px; top: "+(e.offsetTop-5)+"px",o=n.appendChild((0,a.elt)("div",{"class":"ProseMirror-invalid",style:r},t));setTimeout(function(){return n.removeChild(o)},1500)}}]),e}();s.prototype.paramTypes=Object.create(null),s.prototype.paramTypes.text={render:function(e,t){return(0,a.elt)("input",{type:"text",placeholder:this.translate(e.label),value:t,autocomplete:"off"})},read:function(e){return e.value}},s.prototype.paramTypes.select={render:function(e,t){var n=this,r=e.options.call?e.options(this):e.options;return(0,a.elt)("select",null,r.map(function(e){return(0,a.elt)("option",{value:e.value,selected:e.value==t?"true":null},n.translate(e.label))}))},read:function(e){return e.value}},(0,a.insertCSS)('\n.ProseMirror-prompt {\n background: white;\n padding: 2px 6px 2px 15px;\n border: 1px solid silver;\n position: absolute;\n border-radius: 3px;\n z-index: 11;\n}\n\n.ProseMirror-prompt h5 {\n margin: 0;\n font-weight: normal;\n font-size: 100%;\n color: #444;\n}\n\n.ProseMirror-prompt input[type="text"],\n.ProseMirror-prompt textarea {\n background: #eee;\n border: none;\n outline: none;\n}\n\n.ProseMirror-prompt input[type="text"] {\n padding: 0 4px;\n}\n\n.ProseMirror-prompt-close {\n position: absolute;\n left: 2px; top: 1px;\n color: #666;\n border: none; background: transparent; padding: 0;\n}\n\n.ProseMirror-prompt-close:after {\n content: "✕";\n font-size: 12px;\n}\n\n.ProseMirror-invalid {\n background: #ffc;\n border: 1px solid #cc7;\n border-radius: 4px;\n padding: 5px 10px;\n position: absolute;\n min-width: 10em;\n}\n\n.ProseMirror-prompt-buttons {\n margin-top: 5px;\n display: none;\n}\n\n')},{"../dom":3}],54:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){l.get(e).set(t)}function i(e,t){l.get(e).unset(t)}var a=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0}),n.scheduleDOMUpdate=o,n.unscheduleDOMUpdate=i;var s=50,u=100,l=function(){function e(t){var n=this;r(this,e),this.waiting=[],this.timeout=null,this.lastForce=0,this.pm=t,this.timedOut=function(){n.pm.operation?n.timeout=setTimeout(n.timedOut,s):n.force()},t.on("flush",this.onFlush.bind(this))}return a(e,[{key:"set",value:function(e){0==this.waiting.length&&(this.timeout=setTimeout(this.timedOut,s)),-1==this.waiting.indexOf(e)&&this.waiting.push(e)}},{key:"unset",value:function(e){var t=this.waiting.indexOf(e);t>-1&&this.waiting.splice(t,1)}},{key:"force",value:function(){for(clearTimeout(this.timeout),this.lastForce=Date.now();this.waiting.length;)for(var e=0;e<this.waiting.length;e++){var t=this.waiting[e]();t?this.waiting[e]=t:this.waiting.splice(e--,1)}}},{key:"onFlush",value:function(){this.waiting.length&&Date.now()-this.lastForce>u&&this.force()}}],[{key:"get",value:function(e){return e.mod.centralScheduler||(e.mod.centralScheduler=new this(e))}}]),e}();n.UpdateScheduler=function(){function e(t,n,o){var i=this;r(this,e),this.pm=t,this.start=o,this.events=n.split(" "),this.onEvent=this.onEvent.bind(this),this.events.forEach(function(e){return t.on(e,i.onEvent)})}return a(e,[{key:"detach",value:function(){var e=this;i(this.pm,this.start),this.events.forEach(function(t){return e.pm.off(t,e.onEvent)})}},{key:"onEvent",value:function(){o(this.pm,this.start)}},{key:"force",value:function(){if(this.pm.operation)this.onEvent();else{i(this.pm,this.start);for(var e=this.start;e;e=e());}}}]),e}()},{}],55:[function(e,t,n){"use strict";function r(e){Error.call(this,e),this.message!=e&&(this.message=e,Error.captureStackTrace?Error.captureStackTrace(this,this.name):this.stack=new Error(e).stack)}function o(e){var t=/^function (\w+)/.exec(e.toString());return t&&t[1]}Object.defineProperty(n,"__esModule",{value:!0}),n.ProseMirrorError=r,r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,Object.defineProperty(r.prototype,"name",{get:function(){return this.constructor.name||o(this.constructor)||"ProseMirrorError"}})},{}],56:[function(e,t,n){"use strict";function r(e,t){return e._handlers&&e._handlers[t]||i}function o(e){var t=e.prototype;for(var n in a)a.hasOwnProperty(n)&&(t[n]=a[n])}Object.defineProperty(n,"__esModule",{value:!0}),n.eventMixin=o;var i=[],a={on:function(e,t){var n=this._handlers||(this._handlers=Object.create(null));n[e]=e in n?n[e].concat(t):[t]},off:function(e,t){var n=this._handlers,r=n&&n[e];if(r)for(var o=0;o<r.length;++o)if(r[o]==t){n[e]=r.slice(0,o).concat(r.slice(o+1));break}},signal:function(e){for(var t=r(this,e),n=arguments.length,o=Array(n>1?n-1:0),i=1;n>i;i++)o[i-1]=arguments[i];for(var a=0;a<t.length;++a)t[a].apply(t,o)},signalHandleable:function(e){for(var t=r(this,e),n=arguments.length,o=Array(n>1?n-1:0),i=1;n>i;i++)o[i-1]=arguments[i];for(var a=0;a<t.length;++a){var s=t[a].apply(t,o);if(null!=s)return s}},signalPipelined:function(e,t){for(var n=r(this,e),o=0;o<n.length;++o)t=n[o](t);return t},signalDOM:function(e,t){for(var n=r(this,t||e.type),o=0;o<n.length;++o)if(n[o](e)||e.defaultPrevented)return!0;return!1},hasHandler:function(e){return r(this,e).length>0}}},{}],57:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0});n.Map=window.Map||function(){function e(){r(this,e),this.content=[]}return o(e,[{key:"set",value:function(e,t){var n=this.find(e);n>-1?this.content[n+1]=t:this.content.push(e,t)}},{key:"get",value:function(e){var t=this.find(e);return-1==t?void 0:this.content[t+1]}},{key:"has",value:function(e){return this.find(e)>-1}},{key:"find",value:function(e){for(var t=0;t<this.content.length;t+=2)if(this.content[t]===e)return t}},{key:"clear",value:function(){this.content.length=0}},{key:"size",get:function(){return this.content.length/2}}]),e}()},{}],58:[function(e,t,n){"use strict";function r(e,t){var n=t||Object.create(null);for(var r in e)n[r]=e[r];return n}Object.defineProperty(n,"__esModule",{value:!0}),n.copyObj=r},{}],59:[function(e,t,n){"use strict";function r(e,t,n){for(var r=0;r<e.length&&!(n(e[r],t)>0);r++);e.splice(r,0,t)}Object.defineProperty(n,"__esModule",{value:!0}),n["default"]=r},{}],60:[function(e,t,n){!function(e){if("object"==typeof n&&"object"==typeof t)t.exports=e();else{if("function"==typeof define&&define.amd)return define([],e);(this||window).browserKeymap=e()}}(function(){"use strict";function e(e){if("keypress"==e.type)return"'"+String.fromCharCode(e.charCode)+"'";var t=i[e.keyCode],n=t;return null==n||e.altGraphKey?null:(e.altKey&&"Alt"!=t&&(n="Alt-"+n),e.ctrlKey&&"Ctrl"!=t&&(n="Ctrl-"+n),e.metaKey&&"Cmd"!=t&&(n="Cmd-"+n),e.shiftKey&&"Shift"!=t&&(n="Shift-"+n),n)}function t(e){return e=/[^-]*$/.exec(e)[0],"Ctrl"==e||"Alt"==e||"Shift"==e||"Mod"==e}function n(e){for(var t,n,r,i,a=e.split(/-(?!'?$)/),s=a[a.length-1],u=0;u<a.length-1;u++){var l=a[u];if(/^(cmd|meta|m)$/i.test(l))i=!0;else if(/^a(lt)?$/i.test(l))t=!0;else if(/^(c|ctrl|control)$/i.test(l))n=!0;else if(/^s(hift)$/i.test(l))r=!0;else{if(!/^mod$/i.test(l))throw new Error("Unrecognized modifier name: "+l);o?i=!0:n=!0}}return t&&(s="Alt-"+s),n&&(s="Ctrl-"+s),i&&(s="Cmd-"+s),r&&(s="Shift-"+s),s}function r(e,t){if(this.options=t||{},this.bindings=Object.create(null),e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&this.addBinding(n,e[n])}for(var o="undefined"!=typeof navigator?/Mac/.test(navigator.platform):"undefined"!=typeof os?"darwin"==os.platform():!1,i={3:"Enter",8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause",20:"CapsLock",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",44:"PrintScrn",45:"Insert",46:"Delete",59:";",61:"=",91:"Mod",92:"Mod",93:"Mod",106:"*",107:"=",109:"-",110:".",111:"/",127:"Delete",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'",63232:"Up",63233:"Down",63234:"Left",63235:"Right",63272:"Delete",63273:"Home",63275:"End",63276:"PageUp",63277:"PageDown",63302:"Insert"},a=0;10>a;a++)i[a+48]=i[a+96]=String(a);for(var a=65;90>=a;a++)i[a]=String.fromCharCode(a);for(var a=1;12>=a;a++)i[a+111]=i[a+63235]="F"+a;return r.prototype={normalize:function(e){return this.options.multi!==!1?e.split(/ +(?!\'$)/).map(n):[n(e)]},addBinding:function(e,t){for(var n=this.normalize(e),r=0;r<n.length;r++){var o=n.slice(0,r+1).join(" "),i=r==n.length-1?t:"...",a=this.bindings[o];if(a){if(a!=i)throw new Error("Inconsistent bindings for "+o)}else this.bindings[o]=i}},removeBinding:function(e){for(var t=this.normalize(e),n=t.length-1;n>=0;n--){var r=t.slice(0,n).join(" "),o=this.bindings[r];if("..."==o&&!this.unusedMulti(r))break;o&&delete this.bindings[r]}},unusedMulti:function(e){for(var t in this.bindings)if(t.length>e&&0==t.indexOf(e)&&" "==t.charAt(e.length))return!1;return!0},lookup:function(e,t){return this.options.call?this.options.call(e,t):this.bindings[e]},constructor:r},r.keyName=e,r.isModifierKey=t,r.normalizeKeyName=n,r})},{}],61:[function(e,t,n){t.exports={Aacute:"Á",aacute:"á",Abreve:"Ă",abreve:"ă",ac:"∾",acd:"∿",acE:"∾̳",Acirc:"Â",acirc:"â",acute:"´",Acy:"А",acy:"а",AElig:"Æ",aelig:"æ",af:"",Afr:"𝔄",afr:"𝔞",Agrave:"À",agrave:"à",alefsym:"ℵ",aleph:"ℵ",Alpha:"Α",alpha:"α",Amacr:"Ā",amacr:"ā",amalg:"⨿",amp:"&",AMP:"&",andand:"⩕",And:"⩓",and:"∧",andd:"⩜",andslope:"⩘",andv:"⩚",ang:"∠",ange:"⦤",angle:"∠",angmsdaa:"⦨",angmsdab:"⦩",angmsdac:"⦪",angmsdad:"⦫",angmsdae:"⦬",angmsdaf:"⦭",angmsdag:"⦮",angmsdah:"⦯",angmsd:"∡",angrt:"∟",angrtvb:"⊾",angrtvbd:"⦝",angsph:"∢",angst:"Å",angzarr:"⍼",Aogon:"Ą",aogon:"ą",Aopf:"𝔸",aopf:"𝕒",apacir:"⩯",ap:"≈",apE:"⩰",ape:"≊",apid:"≋",apos:"'",ApplyFunction:"",approx:"≈",approxeq:"≊",Aring:"Å",aring:"å",Ascr:"𝒜",ascr:"𝒶",Assign:"≔",ast:"*",asymp:"≈",asympeq:"≍",Atilde:"Ã",atilde:"ã",Auml:"Ä",auml:"ä",awconint:"∳",awint:"⨑",backcong:"≌",backepsilon:"϶",backprime:"‵",backsim:"∽",backsimeq:"⋍",Backslash:"∖",Barv:"⫧",barvee:"⊽",barwed:"⌅",Barwed:"⌆",barwedge:"⌅",bbrk:"⎵",bbrktbrk:"⎶",bcong:"≌",Bcy:"Б",bcy:"б",bdquo:"„",becaus:"∵",because:"∵",Because:"∵",bemptyv:"⦰",bepsi:"϶",bernou:"ℬ",Bernoullis:"ℬ",Beta:"Β",beta:"β",beth:"ℶ",between:"≬",Bfr:"𝔅",bfr:"𝔟",bigcap:"⋂",bigcirc:"◯",bigcup:"⋃",bigodot:"⨀",bigoplus:"⨁",bigotimes:"⨂",bigsqcup:"⨆",bigstar:"★",bigtriangledown:"▽",bigtriangleup:"△",biguplus:"⨄",bigvee:"⋁",bigwedge:"⋀",bkarow:"⤍",blacklozenge:"⧫",blacksquare:"▪",blacktriangle:"▴",blacktriangledown:"▾",blacktriangleleft:"◂",blacktriangleright:"▸",blank:"␣",blk12:"▒",blk14:"░",blk34:"▓",block:"█",bne:"=⃥",bnequiv:"≡⃥",bNot:"⫭",bnot:"⌐",Bopf:"𝔹",bopf:"𝕓",bot:"⊥",bottom:"⊥",bowtie:"⋈",boxbox:"⧉",boxdl:"┐",boxdL:"╕",boxDl:"╖",boxDL:"╗",boxdr:"┌",boxdR:"╒",boxDr:"╓",boxDR:"╔",boxh:"─",boxH:"═",boxhd:"┬",boxHd:"╤",boxhD:"╥",boxHD:"╦",boxhu:"┴",boxHu:"╧",boxhU:"╨",boxHU:"╩",boxminus:"⊟",boxplus:"⊞",boxtimes:"⊠",boxul:"┘",boxuL:"╛",boxUl:"╜",boxUL:"╝",boxur:"└",boxuR:"╘",boxUr:"╙",boxUR:"╚",boxv:"│",boxV:"║",boxvh:"┼",boxvH:"╪",boxVh:"╫",boxVH:"╬",boxvl:"┤",boxvL:"╡",boxVl:"╢",boxVL:"╣",boxvr:"├",boxvR:"╞",boxVr:"╟",boxVR:"╠",bprime:"‵",breve:"˘",Breve:"˘",brvbar:"¦",bscr:"𝒷",Bscr:"ℬ",bsemi:"⁏",bsim:"∽",bsime:"⋍",bsolb:"⧅",bsol:"\\",bsolhsub:"⟈",bull:"•",bullet:"•",bump:"≎",bumpE:"⪮",bumpe:"≏",Bumpeq:"≎",bumpeq:"≏",Cacute:"Ć",cacute:"ć",capand:"⩄",capbrcup:"⩉",capcap:"⩋",cap:"∩",Cap:"⋒",capcup:"⩇",capdot:"⩀",CapitalDifferentialD:"ⅅ",caps:"∩︀",caret:"⁁",caron:"ˇ",Cayleys:"ℭ",ccaps:"⩍",Ccaron:"Č",ccaron:"č",Ccedil:"Ç",ccedil:"ç",Ccirc:"Ĉ",ccirc:"ĉ",Cconint:"∰",ccups:"⩌",ccupssm:"⩐",Cdot:"Ċ",cdot:"ċ",cedil:"¸",Cedilla:"¸",cemptyv:"⦲",cent:"¢",centerdot:"·",CenterDot:"·",cfr:"𝔠",Cfr:"ℭ",CHcy:"Ч",chcy:"ч",check:"✓",checkmark:"✓",Chi:"Χ",chi:"χ",circ:"ˆ",circeq:"≗",circlearrowleft:"↺",circlearrowright:"↻",circledast:"⊛",circledcirc:"⊚",circleddash:"⊝",CircleDot:"⊙",circledR:"®",circledS:"Ⓢ",CircleMinus:"⊖",CirclePlus:"⊕",CircleTimes:"⊗",cir:"○",cirE:"⧃",cire:"≗",cirfnint:"⨐",cirmid:"⫯",cirscir:"⧂",ClockwiseContourIntegral:"∲",CloseCurlyDoubleQuote:"”",CloseCurlyQuote:"’",clubs:"♣",clubsuit:"♣",colon:":",Colon:"∷",Colone:"⩴",colone:"≔",coloneq:"≔",comma:",",commat:"@",comp:"∁",compfn:"∘",complement:"∁",complexes:"ℂ",cong:"≅",congdot:"⩭",Congruent:"≡",conint:"∮",Conint:"∯",ContourIntegral:"∮",copf:"𝕔",Copf:"ℂ",coprod:"∐",Coproduct:"∐",copy:"©",COPY:"©",copysr:"℗",CounterClockwiseContourIntegral:"∳",crarr:"↵",cross:"✗",Cross:"⨯",Cscr:"𝒞",cscr:"𝒸",csub:"⫏",csube:"⫑",csup:"⫐",csupe:"⫒",ctdot:"⋯",cudarrl:"⤸",cudarrr:"⤵",cuepr:"⋞",cuesc:"⋟",cularr:"↶",cularrp:"⤽",cupbrcap:"⩈",cupcap:"⩆",CupCap:"≍",cup:"∪",Cup:"⋓",cupcup:"⩊",cupdot:"⊍",cupor:"⩅",cups:"∪︀",curarr:"↷",curarrm:"⤼",curlyeqprec:"⋞",curlyeqsucc:"⋟",curlyvee:"⋎",curlywedge:"⋏",curren:"¤",curvearrowleft:"↶",curvearrowright:"↷",cuvee:"⋎",cuwed:"⋏",cwconint:"∲",cwint:"∱",cylcty:"⌭",dagger:"†",Dagger:"‡",daleth:"ℸ",darr:"↓",Darr:"↡",dArr:"⇓",dash:"‐",Dashv:"⫤",dashv:"⊣",dbkarow:"⤏",dblac:"˝",Dcaron:"Ď",dcaron:"ď",Dcy:"Д",dcy:"д",ddagger:"‡",ddarr:"⇊",DD:"ⅅ",dd:"ⅆ",DDotrahd:"⤑",ddotseq:"⩷",deg:"°",Del:"∇",Delta:"Δ",delta:"δ",demptyv:"⦱",dfisht:"⥿",Dfr:"𝔇",dfr:"𝔡",dHar:"⥥",dharl:"⇃",dharr:"⇂",DiacriticalAcute:"´",DiacriticalDot:"˙",DiacriticalDoubleAcute:"˝",DiacriticalGrave:"`",DiacriticalTilde:"˜",diam:"⋄",diamond:"⋄",Diamond:"⋄",diamondsuit:"♦",diams:"♦",die:"¨",DifferentialD:"ⅆ",digamma:"ϝ",disin:"⋲",div:"÷",divide:"÷",divideontimes:"⋇",divonx:"⋇",DJcy:"Ђ",djcy:"ђ",dlcorn:"⌞",dlcrop:"⌍",dollar:"$",Dopf:"𝔻",dopf:"𝕕",Dot:"¨",dot:"˙",DotDot:"⃜",doteq:"≐",doteqdot:"≑",DotEqual:"≐",dotminus:"∸",dotplus:"∔",dotsquare:"⊡",doublebarwedge:"⌆",DoubleContourIntegral:"∯",DoubleDot:"¨",DoubleDownArrow:"⇓",DoubleLeftArrow:"⇐",DoubleLeftRightArrow:"⇔",DoubleLeftTee:"⫤",DoubleLongLeftArrow:"⟸",DoubleLongLeftRightArrow:"⟺",DoubleLongRightArrow:"⟹",DoubleRightArrow:"⇒",DoubleRightTee:"⊨",DoubleUpArrow:"⇑",DoubleUpDownArrow:"⇕",DoubleVerticalBar:"∥",DownArrowBar:"⤓",downarrow:"↓",DownArrow:"↓",Downarrow:"⇓",DownArrowUpArrow:"⇵",DownBreve:"̑",downdownarrows:"⇊",downharpoonleft:"⇃",downharpoonright:"⇂",DownLeftRightVector:"⥐",DownLeftTeeVector:"⥞",DownLeftVectorBar:"⥖",DownLeftVector:"↽",DownRightTeeVector:"⥟",DownRightVectorBar:"⥗",DownRightVector:"⇁",DownTeeArrow:"↧",DownTee:"⊤",drbkarow:"⤐",drcorn:"⌟",drcrop:"⌌",Dscr:"𝒟",dscr:"𝒹",DScy:"Ѕ",dscy:"ѕ",dsol:"⧶",Dstrok:"Đ",dstrok:"đ",dtdot:"⋱",dtri:"▿",dtrif:"▾",duarr:"⇵",duhar:"⥯",dwangle:"⦦",DZcy:"Џ",dzcy:"џ",dzigrarr:"⟿",Eacute:"É",eacute:"é",easter:"⩮",Ecaron:"Ě",ecaron:"ě",Ecirc:"Ê",ecirc:"ê",ecir:"≖",ecolon:"≕",Ecy:"Э",ecy:"э",eDDot:"⩷",Edot:"Ė",edot:"ė",eDot:"≑",ee:"ⅇ",efDot:"≒",Efr:"𝔈",efr:"𝔢",eg:"⪚",Egrave:"È",egrave:"è",egs:"⪖",egsdot:"⪘",el:"⪙",Element:"∈",elinters:"⏧",ell:"ℓ",els:"⪕",elsdot:"⪗",Emacr:"Ē",emacr:"ē",empty:"∅",emptyset:"∅",EmptySmallSquare:"◻",emptyv:"∅",EmptyVerySmallSquare:"▫",emsp13:" ",emsp14:" ",emsp:" ",ENG:"Ŋ",eng:"ŋ",ensp:" ",Eogon:"Ę",eogon:"ę",Eopf:"𝔼",eopf:"𝕖",epar:"⋕",eparsl:"⧣",eplus:"⩱",epsi:"ε",Epsilon:"Ε",epsilon:"ε",epsiv:"ϵ",eqcirc:"≖",eqcolon:"≕",eqsim:"≂",eqslantgtr:"⪖",eqslantless:"⪕",Equal:"⩵",equals:"=",EqualTilde:"≂",equest:"≟",Equilibrium:"⇌",equiv:"≡",equivDD:"⩸",eqvparsl:"⧥",erarr:"⥱",erDot:"≓",escr:"ℯ",Escr:"ℰ",esdot:"≐",Esim:"⩳",esim:"≂",Eta:"Η",eta:"η",ETH:"Ð",eth:"ð",Euml:"Ë",euml:"ë",euro:"€",excl:"!",exist:"∃",Exists:"∃",expectation:"ℰ",exponentiale:"ⅇ",ExponentialE:"ⅇ",fallingdotseq:"≒",Fcy:"Ф",fcy:"ф",female:"♀",ffilig:"ffi",fflig:"ff",ffllig:"ffl",Ffr:"𝔉",ffr:"𝔣",filig:"fi",FilledSmallSquare:"◼",FilledVerySmallSquare:"▪",fjlig:"fj",flat:"♭",fllig:"fl",fltns:"▱",fnof:"ƒ",Fopf:"𝔽",fopf:"𝕗",forall:"∀",ForAll:"∀",fork:"⋔",forkv:"⫙",Fouriertrf:"ℱ",fpartint:"⨍",frac12:"½",frac13:"⅓",frac14:"¼",frac15:"⅕",frac16:"⅙",frac18:"⅛",frac23:"⅔",frac25:"⅖",frac34:"¾",frac35:"⅗",frac38:"⅜",frac45:"⅘",frac56:"⅚",frac58:"⅝",frac78:"⅞",frasl:"⁄",frown:"⌢",fscr:"𝒻",Fscr:"ℱ",gacute:"ǵ",Gamma:"Γ",gamma:"γ",Gammad:"Ϝ",gammad:"ϝ",gap:"⪆",Gbreve:"Ğ",gbreve:"ğ",Gcedil:"Ģ",Gcirc:"Ĝ",gcirc:"ĝ",Gcy:"Г",gcy:"г",Gdot:"Ġ",gdot:"ġ",ge:"≥",gE:"≧",gEl:"⪌",gel:"⋛",geq:"≥",geqq:"≧",geqslant:"⩾",gescc:"⪩",ges:"⩾",gesdot:"⪀",gesdoto:"⪂",gesdotol:"⪄",gesl:"⋛︀",gesles:"⪔",Gfr:"𝔊",gfr:"𝔤",gg:"≫",Gg:"⋙",ggg:"⋙",gimel:"ℷ",GJcy:"Ѓ",gjcy:"ѓ",gla:"⪥",gl:"≷",glE:"⪒",glj:"⪤",gnap:"⪊",gnapprox:"⪊",gne:"⪈",gnE:"≩",gneq:"⪈",gneqq:"≩",gnsim:"⋧",Gopf:"𝔾",gopf:"𝕘",grave:"`",GreaterEqual:"≥",GreaterEqualLess:"⋛",GreaterFullEqual:"≧",GreaterGreater:"⪢",GreaterLess:"≷",GreaterSlantEqual:"⩾",GreaterTilde:"≳",Gscr:"𝒢",gscr:"ℊ",gsim:"≳",gsime:"⪎",gsiml:"⪐",gtcc:"⪧",gtcir:"⩺",gt:">",GT:">",Gt:"≫",gtdot:"⋗",gtlPar:"⦕",gtquest:"⩼",gtrapprox:"⪆",gtrarr:"⥸",gtrdot:"⋗",gtreqless:"⋛",gtreqqless:"⪌",gtrless:"≷",gtrsim:"≳",gvertneqq:"≩︀",gvnE:"≩︀",Hacek:"ˇ",hairsp:" ",half:"½",hamilt:"ℋ",HARDcy:"Ъ",hardcy:"ъ",harrcir:"⥈",harr:"↔",hArr:"⇔",harrw:"↭",Hat:"^",hbar:"ℏ",Hcirc:"Ĥ",hcirc:"ĥ",hearts:"♥",heartsuit:"♥",hellip:"…",hercon:"⊹",hfr:"𝔥",Hfr:"ℌ",HilbertSpace:"ℋ",hksearow:"⤥",hkswarow:"⤦",hoarr:"⇿",homtht:"∻",hookleftarrow:"↩",hookrightarrow:"↪",hopf:"𝕙",Hopf:"ℍ",horbar:"―",HorizontalLine:"─",hscr:"𝒽",Hscr:"ℋ",hslash:"ℏ",Hstrok:"Ħ",hstrok:"ħ",HumpDownHump:"≎",HumpEqual:"≏",hybull:"⁃",hyphen:"‐",Iacute:"Í",iacute:"í",ic:"",Icirc:"Î",icirc:"î",Icy:"И",icy:"и",Idot:"İ",IEcy:"Е",iecy:"е",iexcl:"¡",iff:"⇔",ifr:"𝔦",Ifr:"ℑ",Igrave:"Ì",igrave:"ì",ii:"ⅈ",iiiint:"⨌",iiint:"∭",iinfin:"⧜",iiota:"℩",IJlig:"IJ",ijlig:"ij",Imacr:"Ī",imacr:"ī",image:"ℑ",ImaginaryI:"ⅈ",imagline:"ℐ",imagpart:"ℑ",imath:"ı",Im:"ℑ",imof:"⊷",imped:"Ƶ",Implies:"⇒",incare:"℅","in":"∈",infin:"∞",infintie:"⧝",inodot:"ı",intcal:"⊺","int":"∫",Int:"∬",integers:"ℤ",Integral:"∫",intercal:"⊺",Intersection:"⋂",intlarhk:"⨗",intprod:"⨼",InvisibleComma:"",InvisibleTimes:"",IOcy:"Ё",iocy:"ё",Iogon:"Į",iogon:"į",Iopf:"𝕀",iopf:"𝕚",Iota:"Ι",iota:"ι",iprod:"⨼",iquest:"¿",iscr:"𝒾",Iscr:"ℐ",isin:"∈",isindot:"⋵",isinE:"⋹",isins:"⋴",isinsv:"⋳",isinv:"∈",it:"",Itilde:"Ĩ",itilde:"ĩ",Iukcy:"І",iukcy:"і",Iuml:"Ï",iuml:"ï",Jcirc:"Ĵ",jcirc:"ĵ",Jcy:"Й",jcy:"й",Jfr:"𝔍",jfr:"𝔧",jmath:"ȷ",Jopf:"𝕁",jopf:"𝕛",Jscr:"𝒥",jscr:"𝒿",Jsercy:"Ј",jsercy:"ј",Jukcy:"Є",jukcy:"є",Kappa:"Κ",kappa:"κ",kappav:"ϰ",Kcedil:"Ķ",kcedil:"ķ",Kcy:"К",kcy:"к",Kfr:"𝔎",kfr:"𝔨",kgreen:"ĸ",KHcy:"Х",khcy:"х",KJcy:"Ќ",kjcy:"ќ",Kopf:"𝕂",kopf:"𝕜",Kscr:"𝒦",kscr:"𝓀",lAarr:"⇚",Lacute:"Ĺ",lacute:"ĺ",laemptyv:"⦴",lagran:"ℒ",Lambda:"Λ",lambda:"λ",lang:"⟨",Lang:"⟪",langd:"⦑",langle:"⟨",lap:"⪅",Laplacetrf:"ℒ",laquo:"«",larrb:"⇤",larrbfs:"⤟",larr:"←",Larr:"↞",lArr:"⇐",larrfs:"⤝",larrhk:"↩",larrlp:"↫",larrpl:"⤹",larrsim:"⥳",larrtl:"↢",latail:"⤙",lAtail:"⤛",lat:"⪫",late:"⪭",lates:"⪭︀",lbarr:"⤌",lBarr:"⤎",lbbrk:"❲",lbrace:"{",lbrack:"[",lbrke:"⦋",lbrksld:"⦏",lbrkslu:"⦍",Lcaron:"Ľ",lcaron:"ľ",Lcedil:"Ļ",lcedil:"ļ",lceil:"⌈",lcub:"{",Lcy:"Л",lcy:"л",ldca:"⤶",ldquo:"“",ldquor:"„",ldrdhar:"⥧",ldrushar:"⥋",ldsh:"↲",le:"≤",lE:"≦",LeftAngleBracket:"⟨",LeftArrowBar:"⇤",leftarrow:"←",LeftArrow:"←",Leftarrow:"⇐",LeftArrowRightArrow:"⇆",leftarrowtail:"↢",LeftCeiling:"⌈",LeftDoubleBracket:"⟦",LeftDownTeeVector:"⥡",LeftDownVectorBar:"⥙",LeftDownVector:"⇃",LeftFloor:"⌊",leftharpoondown:"↽",leftharpoonup:"↼",leftleftarrows:"⇇",leftrightarrow:"↔",LeftRightArrow:"↔",Leftrightarrow:"⇔",leftrightarrows:"⇆",leftrightharpoons:"⇋",leftrightsquigarrow:"↭",LeftRightVector:"⥎",LeftTeeArrow:"↤",LeftTee:"⊣",LeftTeeVector:"⥚",leftthreetimes:"⋋",LeftTriangleBar:"⧏",LeftTriangle:"⊲",LeftTriangleEqual:"⊴",LeftUpDownVector:"⥑",LeftUpTeeVector:"⥠",LeftUpVectorBar:"⥘",LeftUpVector:"↿",LeftVectorBar:"⥒",LeftVector:"↼",lEg:"⪋",leg:"⋚",leq:"≤",leqq:"≦",leqslant:"⩽",lescc:"⪨",les:"⩽",lesdot:"⩿",lesdoto:"⪁",lesdotor:"⪃",lesg:"⋚︀",lesges:"⪓",lessapprox:"⪅",lessdot:"⋖",lesseqgtr:"⋚",lesseqqgtr:"⪋",LessEqualGreater:"⋚",LessFullEqual:"≦",LessGreater:"≶",lessgtr:"≶",LessLess:"⪡",lesssim:"≲",LessSlantEqual:"⩽",LessTilde:"≲",lfisht:"⥼",lfloor:"⌊",Lfr:"𝔏",lfr:"𝔩",lg:"≶",lgE:"⪑",lHar:"⥢",lhard:"↽",lharu:"↼",lharul:"⥪",lhblk:"▄",LJcy:"Љ",ljcy:"љ",llarr:"⇇",ll:"≪",Ll:"⋘",llcorner:"⌞",Lleftarrow:"⇚",llhard:"⥫",lltri:"◺",Lmidot:"Ŀ",lmidot:"ŀ",lmoustache:"⎰",lmoust:"⎰",lnap:"⪉",lnapprox:"⪉",lne:"⪇",lnE:"≨",lneq:"⪇",lneqq:"≨",lnsim:"⋦",loang:"⟬",loarr:"⇽",lobrk:"⟦",longleftarrow:"⟵",LongLeftArrow:"⟵",Longleftarrow:"⟸",longleftrightarrow:"⟷",LongLeftRightArrow:"⟷",Longleftrightarrow:"⟺",longmapsto:"⟼",longrightarrow:"⟶",LongRightArrow:"⟶",Longrightarrow:"⟹",looparrowleft:"↫",looparrowright:"↬",lopar:"⦅",Lopf:"𝕃",lopf:"𝕝",loplus:"⨭",lotimes:"⨴",lowast:"∗",lowbar:"_",LowerLeftArrow:"↙",LowerRightArrow:"↘",loz:"◊",lozenge:"◊",lozf:"⧫",lpar:"(",lparlt:"⦓",lrarr:"⇆",lrcorner:"⌟",lrhar:"⇋",lrhard:"⥭",lrm:"",lrtri:"⊿",lsaquo:"‹",lscr:"𝓁",Lscr:"ℒ",lsh:"↰",Lsh:"↰",lsim:"≲",lsime:"⪍",lsimg:"⪏",lsqb:"[",lsquo:"‘",lsquor:"‚",Lstrok:"Ł",lstrok:"ł",ltcc:"⪦",ltcir:"⩹",lt:"<",LT:"<",Lt:"≪",ltdot:"⋖",lthree:"⋋",ltimes:"⋉",ltlarr:"⥶",ltquest:"⩻",ltri:"◃",ltrie:"⊴",ltrif:"◂",ltrPar:"⦖",lurdshar:"⥊",luruhar:"⥦",lvertneqq:"≨︀",lvnE:"≨︀",macr:"¯",male:"♂",malt:"✠",maltese:"✠",Map:"⤅",map:"↦",mapsto:"↦",mapstodown:"↧",mapstoleft:"↤",mapstoup:"↥",marker:"▮",mcomma:"⨩",Mcy:"М",mcy:"м",mdash:"—",mDDot:"∺",measuredangle:"∡",MediumSpace:" ",Mellintrf:"ℳ",Mfr:"𝔐",mfr:"𝔪",mho:"℧",micro:"µ",midast:"*",midcir:"⫰",mid:"∣",middot:"·",minusb:"⊟",minus:"−",minusd:"∸",minusdu:"⨪",MinusPlus:"∓",mlcp:"⫛",mldr:"…",mnplus:"∓",models:"⊧",Mopf:"𝕄",mopf:"𝕞",mp:"∓",mscr:"𝓂",Mscr:"ℳ",mstpos:"∾",Mu:"Μ",mu:"μ",multimap:"⊸",mumap:"⊸",nabla:"∇",Nacute:"Ń",nacute:"ń",nang:"∠⃒",nap:"≉",napE:"⩰̸",napid:"≋̸",napos:"ʼn",napprox:"≉",natural:"♮",naturals:"ℕ",natur:"♮",nbsp:" ",nbump:"≎̸",nbumpe:"≏̸",ncap:"⩃",Ncaron:"Ň",ncaron:"ň",Ncedil:"Ņ",ncedil:"ņ",ncong:"≇",ncongdot:"⩭̸",ncup:"⩂",Ncy:"Н",ncy:"н",ndash:"–",nearhk:"⤤",nearr:"↗",neArr:"⇗",
+nearrow:"↗",ne:"≠",nedot:"≐̸",NegativeMediumSpace:"",NegativeThickSpace:"",NegativeThinSpace:"",NegativeVeryThinSpace:"",nequiv:"≢",nesear:"⤨",nesim:"≂̸",NestedGreaterGreater:"≫",NestedLessLess:"≪",NewLine:"\n",nexist:"∄",nexists:"∄",Nfr:"𝔑",nfr:"𝔫",ngE:"≧̸",nge:"≱",ngeq:"≱",ngeqq:"≧̸",ngeqslant:"⩾̸",nges:"⩾̸",nGg:"⋙̸",ngsim:"≵",nGt:"≫⃒",ngt:"≯",ngtr:"≯",nGtv:"≫̸",nharr:"↮",nhArr:"⇎",nhpar:"⫲",ni:"∋",nis:"⋼",nisd:"⋺",niv:"∋",NJcy:"Њ",njcy:"њ",nlarr:"↚",nlArr:"⇍",nldr:"‥",nlE:"≦̸",nle:"≰",nleftarrow:"↚",nLeftarrow:"⇍",nleftrightarrow:"↮",nLeftrightarrow:"⇎",nleq:"≰",nleqq:"≦̸",nleqslant:"⩽̸",nles:"⩽̸",nless:"≮",nLl:"⋘̸",nlsim:"≴",nLt:"≪⃒",nlt:"≮",nltri:"⋪",nltrie:"⋬",nLtv:"≪̸",nmid:"∤",NoBreak:"",NonBreakingSpace:" ",nopf:"𝕟",Nopf:"ℕ",Not:"⫬",not:"¬",NotCongruent:"≢",NotCupCap:"≭",NotDoubleVerticalBar:"∦",NotElement:"∉",NotEqual:"≠",NotEqualTilde:"≂̸",NotExists:"∄",NotGreater:"≯",NotGreaterEqual:"≱",NotGreaterFullEqual:"≧̸",NotGreaterGreater:"≫̸",NotGreaterLess:"≹",NotGreaterSlantEqual:"⩾̸",NotGreaterTilde:"≵",NotHumpDownHump:"≎̸",NotHumpEqual:"≏̸",notin:"∉",notindot:"⋵̸",notinE:"⋹̸",notinva:"∉",notinvb:"⋷",notinvc:"⋶",NotLeftTriangleBar:"⧏̸",NotLeftTriangle:"⋪",NotLeftTriangleEqual:"⋬",NotLess:"≮",NotLessEqual:"≰",NotLessGreater:"≸",NotLessLess:"≪̸",NotLessSlantEqual:"⩽̸",NotLessTilde:"≴",NotNestedGreaterGreater:"⪢̸",NotNestedLessLess:"⪡̸",notni:"∌",notniva:"∌",notnivb:"⋾",notnivc:"⋽",NotPrecedes:"⊀",NotPrecedesEqual:"⪯̸",NotPrecedesSlantEqual:"⋠",NotReverseElement:"∌",NotRightTriangleBar:"⧐̸",NotRightTriangle:"⋫",NotRightTriangleEqual:"⋭",NotSquareSubset:"⊏̸",NotSquareSubsetEqual:"⋢",NotSquareSuperset:"⊐̸",NotSquareSupersetEqual:"⋣",NotSubset:"⊂⃒",NotSubsetEqual:"⊈",NotSucceeds:"⊁",NotSucceedsEqual:"⪰̸",NotSucceedsSlantEqual:"⋡",NotSucceedsTilde:"≿̸",NotSuperset:"⊃⃒",NotSupersetEqual:"⊉",NotTilde:"≁",NotTildeEqual:"≄",NotTildeFullEqual:"≇",NotTildeTilde:"≉",NotVerticalBar:"∤",nparallel:"∦",npar:"∦",nparsl:"⫽⃥",npart:"∂̸",npolint:"⨔",npr:"⊀",nprcue:"⋠",nprec:"⊀",npreceq:"⪯̸",npre:"⪯̸",nrarrc:"⤳̸",nrarr:"↛",nrArr:"⇏",nrarrw:"↝̸",nrightarrow:"↛",nRightarrow:"⇏",nrtri:"⋫",nrtrie:"⋭",nsc:"⊁",nsccue:"⋡",nsce:"⪰̸",Nscr:"𝒩",nscr:"𝓃",nshortmid:"∤",nshortparallel:"∦",nsim:"≁",nsime:"≄",nsimeq:"≄",nsmid:"∤",nspar:"∦",nsqsube:"⋢",nsqsupe:"⋣",nsub:"⊄",nsubE:"⫅̸",nsube:"⊈",nsubset:"⊂⃒",nsubseteq:"⊈",nsubseteqq:"⫅̸",nsucc:"⊁",nsucceq:"⪰̸",nsup:"⊅",nsupE:"⫆̸",nsupe:"⊉",nsupset:"⊃⃒",nsupseteq:"⊉",nsupseteqq:"⫆̸",ntgl:"≹",Ntilde:"Ñ",ntilde:"ñ",ntlg:"≸",ntriangleleft:"⋪",ntrianglelefteq:"⋬",ntriangleright:"⋫",ntrianglerighteq:"⋭",Nu:"Ν",nu:"ν",num:"#",numero:"№",numsp:" ",nvap:"≍⃒",nvdash:"⊬",nvDash:"⊭",nVdash:"⊮",nVDash:"⊯",nvge:"≥⃒",nvgt:">⃒",nvHarr:"⤄",nvinfin:"⧞",nvlArr:"⤂",nvle:"≤⃒",nvlt:"<⃒",nvltrie:"⊴⃒",nvrArr:"⤃",nvrtrie:"⊵⃒",nvsim:"∼⃒",nwarhk:"⤣",nwarr:"↖",nwArr:"⇖",nwarrow:"↖",nwnear:"⤧",Oacute:"Ó",oacute:"ó",oast:"⊛",Ocirc:"Ô",ocirc:"ô",ocir:"⊚",Ocy:"О",ocy:"о",odash:"⊝",Odblac:"Ő",odblac:"ő",odiv:"⨸",odot:"⊙",odsold:"⦼",OElig:"Œ",oelig:"œ",ofcir:"⦿",Ofr:"𝔒",ofr:"𝔬",ogon:"˛",Ograve:"Ò",ograve:"ò",ogt:"⧁",ohbar:"⦵",ohm:"Ω",oint:"∮",olarr:"↺",olcir:"⦾",olcross:"⦻",oline:"‾",olt:"⧀",Omacr:"Ō",omacr:"ō",Omega:"Ω",omega:"ω",Omicron:"Ο",omicron:"ο",omid:"⦶",ominus:"⊖",Oopf:"𝕆",oopf:"𝕠",opar:"⦷",OpenCurlyDoubleQuote:"“",OpenCurlyQuote:"‘",operp:"⦹",oplus:"⊕",orarr:"↻",Or:"⩔",or:"∨",ord:"⩝",order:"ℴ",orderof:"ℴ",ordf:"ª",ordm:"º",origof:"⊶",oror:"⩖",orslope:"⩗",orv:"⩛",oS:"Ⓢ",Oscr:"𝒪",oscr:"ℴ",Oslash:"Ø",oslash:"ø",osol:"⊘",Otilde:"Õ",otilde:"õ",otimesas:"⨶",Otimes:"⨷",otimes:"⊗",Ouml:"Ö",ouml:"ö",ovbar:"⌽",OverBar:"‾",OverBrace:"⏞",OverBracket:"⎴",OverParenthesis:"⏜",para:"¶",parallel:"∥",par:"∥",parsim:"⫳",parsl:"⫽",part:"∂",PartialD:"∂",Pcy:"П",pcy:"п",percnt:"%",period:".",permil:"‰",perp:"⊥",pertenk:"‱",Pfr:"𝔓",pfr:"𝔭",Phi:"Φ",phi:"φ",phiv:"ϕ",phmmat:"ℳ",phone:"☎",Pi:"Π",pi:"π",pitchfork:"⋔",piv:"ϖ",planck:"ℏ",planckh:"ℎ",plankv:"ℏ",plusacir:"⨣",plusb:"⊞",pluscir:"⨢",plus:"+",plusdo:"∔",plusdu:"⨥",pluse:"⩲",PlusMinus:"±",plusmn:"±",plussim:"⨦",plustwo:"⨧",pm:"±",Poincareplane:"ℌ",pointint:"⨕",popf:"𝕡",Popf:"ℙ",pound:"£",prap:"⪷",Pr:"⪻",pr:"≺",prcue:"≼",precapprox:"⪷",prec:"≺",preccurlyeq:"≼",Precedes:"≺",PrecedesEqual:"⪯",PrecedesSlantEqual:"≼",PrecedesTilde:"≾",preceq:"⪯",precnapprox:"⪹",precneqq:"⪵",precnsim:"⋨",pre:"⪯",prE:"⪳",precsim:"≾",prime:"′",Prime:"″",primes:"ℙ",prnap:"⪹",prnE:"⪵",prnsim:"⋨",prod:"∏",Product:"∏",profalar:"⌮",profline:"⌒",profsurf:"⌓",prop:"∝",Proportional:"∝",Proportion:"∷",propto:"∝",prsim:"≾",prurel:"⊰",Pscr:"𝒫",pscr:"𝓅",Psi:"Ψ",psi:"ψ",puncsp:" ",Qfr:"𝔔",qfr:"𝔮",qint:"⨌",qopf:"𝕢",Qopf:"ℚ",qprime:"⁗",Qscr:"𝒬",qscr:"𝓆",quaternions:"ℍ",quatint:"⨖",quest:"?",questeq:"≟",quot:'"',QUOT:'"',rAarr:"⇛",race:"∽̱",Racute:"Ŕ",racute:"ŕ",radic:"√",raemptyv:"⦳",rang:"⟩",Rang:"⟫",rangd:"⦒",range:"⦥",rangle:"⟩",raquo:"»",rarrap:"⥵",rarrb:"⇥",rarrbfs:"⤠",rarrc:"⤳",rarr:"→",Rarr:"↠",rArr:"⇒",rarrfs:"⤞",rarrhk:"↪",rarrlp:"↬",rarrpl:"⥅",rarrsim:"⥴",Rarrtl:"⤖",rarrtl:"↣",rarrw:"↝",ratail:"⤚",rAtail:"⤜",ratio:"∶",rationals:"ℚ",rbarr:"⤍",rBarr:"⤏",RBarr:"⤐",rbbrk:"❳",rbrace:"}",rbrack:"]",rbrke:"⦌",rbrksld:"⦎",rbrkslu:"⦐",Rcaron:"Ř",rcaron:"ř",Rcedil:"Ŗ",rcedil:"ŗ",rceil:"⌉",rcub:"}",Rcy:"Р",rcy:"р",rdca:"⤷",rdldhar:"⥩",rdquo:"”",rdquor:"”",rdsh:"↳",real:"ℜ",realine:"ℛ",realpart:"ℜ",reals:"ℝ",Re:"ℜ",rect:"▭",reg:"®",REG:"®",ReverseElement:"∋",ReverseEquilibrium:"⇋",ReverseUpEquilibrium:"⥯",rfisht:"⥽",rfloor:"⌋",rfr:"𝔯",Rfr:"ℜ",rHar:"⥤",rhard:"⇁",rharu:"⇀",rharul:"⥬",Rho:"Ρ",rho:"ρ",rhov:"ϱ",RightAngleBracket:"⟩",RightArrowBar:"⇥",rightarrow:"→",RightArrow:"→",Rightarrow:"⇒",RightArrowLeftArrow:"⇄",rightarrowtail:"↣",RightCeiling:"⌉",RightDoubleBracket:"⟧",RightDownTeeVector:"⥝",RightDownVectorBar:"⥕",RightDownVector:"⇂",RightFloor:"⌋",rightharpoondown:"⇁",rightharpoonup:"⇀",rightleftarrows:"⇄",rightleftharpoons:"⇌",rightrightarrows:"⇉",rightsquigarrow:"↝",RightTeeArrow:"↦",RightTee:"⊢",RightTeeVector:"⥛",rightthreetimes:"⋌",RightTriangleBar:"⧐",RightTriangle:"⊳",RightTriangleEqual:"⊵",RightUpDownVector:"⥏",RightUpTeeVector:"⥜",RightUpVectorBar:"⥔",RightUpVector:"↾",RightVectorBar:"⥓",RightVector:"⇀",ring:"˚",risingdotseq:"≓",rlarr:"⇄",rlhar:"⇌",rlm:"",rmoustache:"⎱",rmoust:"⎱",rnmid:"⫮",roang:"⟭",roarr:"⇾",robrk:"⟧",ropar:"⦆",ropf:"𝕣",Ropf:"ℝ",roplus:"⨮",rotimes:"⨵",RoundImplies:"⥰",rpar:")",rpargt:"⦔",rppolint:"⨒",rrarr:"⇉",Rrightarrow:"⇛",rsaquo:"›",rscr:"𝓇",Rscr:"ℛ",rsh:"↱",Rsh:"↱",rsqb:"]",rsquo:"’",rsquor:"’",rthree:"⋌",rtimes:"⋊",rtri:"▹",rtrie:"⊵",rtrif:"▸",rtriltri:"⧎",RuleDelayed:"⧴",ruluhar:"⥨",rx:"℞",Sacute:"Ś",sacute:"ś",sbquo:"‚",scap:"⪸",Scaron:"Š",scaron:"š",Sc:"⪼",sc:"≻",sccue:"≽",sce:"⪰",scE:"⪴",Scedil:"Ş",scedil:"ş",Scirc:"Ŝ",scirc:"ŝ",scnap:"⪺",scnE:"⪶",scnsim:"⋩",scpolint:"⨓",scsim:"≿",Scy:"С",scy:"с",sdotb:"⊡",sdot:"⋅",sdote:"⩦",searhk:"⤥",searr:"↘",seArr:"⇘",searrow:"↘",sect:"§",semi:";",seswar:"⤩",setminus:"∖",setmn:"∖",sext:"✶",Sfr:"𝔖",sfr:"𝔰",sfrown:"⌢",sharp:"♯",SHCHcy:"Щ",shchcy:"щ",SHcy:"Ш",shcy:"ш",ShortDownArrow:"↓",ShortLeftArrow:"←",shortmid:"∣",shortparallel:"∥",ShortRightArrow:"→",ShortUpArrow:"↑",shy:"",Sigma:"Σ",sigma:"σ",sigmaf:"ς",sigmav:"ς",sim:"∼",simdot:"⩪",sime:"≃",simeq:"≃",simg:"⪞",simgE:"⪠",siml:"⪝",simlE:"⪟",simne:"≆",simplus:"⨤",simrarr:"⥲",slarr:"←",SmallCircle:"∘",smallsetminus:"∖",smashp:"⨳",smeparsl:"⧤",smid:"∣",smile:"⌣",smt:"⪪",smte:"⪬",smtes:"⪬︀",SOFTcy:"Ь",softcy:"ь",solbar:"⌿",solb:"⧄",sol:"/",Sopf:"𝕊",sopf:"𝕤",spades:"♠",spadesuit:"♠",spar:"∥",sqcap:"⊓",sqcaps:"⊓︀",sqcup:"⊔",sqcups:"⊔︀",Sqrt:"√",sqsub:"⊏",sqsube:"⊑",sqsubset:"⊏",sqsubseteq:"⊑",sqsup:"⊐",sqsupe:"⊒",sqsupset:"⊐",sqsupseteq:"⊒",square:"□",Square:"□",SquareIntersection:"⊓",SquareSubset:"⊏",SquareSubsetEqual:"⊑",SquareSuperset:"⊐",SquareSupersetEqual:"⊒",SquareUnion:"⊔",squarf:"▪",squ:"□",squf:"▪",srarr:"→",Sscr:"𝒮",sscr:"𝓈",ssetmn:"∖",ssmile:"⌣",sstarf:"⋆",Star:"⋆",star:"☆",starf:"★",straightepsilon:"ϵ",straightphi:"ϕ",strns:"¯",sub:"⊂",Sub:"⋐",subdot:"⪽",subE:"⫅",sube:"⊆",subedot:"⫃",submult:"⫁",subnE:"⫋",subne:"⊊",subplus:"⪿",subrarr:"⥹",subset:"⊂",Subset:"⋐",subseteq:"⊆",subseteqq:"⫅",SubsetEqual:"⊆",subsetneq:"⊊",subsetneqq:"⫋",subsim:"⫇",subsub:"⫕",subsup:"⫓",succapprox:"⪸",succ:"≻",succcurlyeq:"≽",Succeeds:"≻",SucceedsEqual:"⪰",SucceedsSlantEqual:"≽",SucceedsTilde:"≿",succeq:"⪰",succnapprox:"⪺",succneqq:"⪶",succnsim:"⋩",succsim:"≿",SuchThat:"∋",sum:"∑",Sum:"∑",sung:"♪",sup1:"¹",sup2:"²",sup3:"³",sup:"⊃",Sup:"⋑",supdot:"⪾",supdsub:"⫘",supE:"⫆",supe:"⊇",supedot:"⫄",Superset:"⊃",SupersetEqual:"⊇",suphsol:"⟉",suphsub:"⫗",suplarr:"⥻",supmult:"⫂",supnE:"⫌",supne:"⊋",supplus:"⫀",supset:"⊃",Supset:"⋑",supseteq:"⊇",supseteqq:"⫆",supsetneq:"⊋",supsetneqq:"⫌",supsim:"⫈",supsub:"⫔",supsup:"⫖",swarhk:"⤦",swarr:"↙",swArr:"⇙",swarrow:"↙",swnwar:"⤪",szlig:"ß",Tab:" ",target:"⌖",Tau:"Τ",tau:"τ",tbrk:"⎴",Tcaron:"Ť",tcaron:"ť",Tcedil:"Ţ",tcedil:"ţ",Tcy:"Т",tcy:"т",tdot:"⃛",telrec:"⌕",Tfr:"𝔗",tfr:"𝔱",there4:"∴",therefore:"∴",Therefore:"∴",Theta:"Θ",theta:"θ",thetasym:"ϑ",thetav:"ϑ",thickapprox:"≈",thicksim:"∼",ThickSpace:" ",ThinSpace:" ",thinsp:" ",thkap:"≈",thksim:"∼",THORN:"Þ",thorn:"þ",tilde:"˜",Tilde:"∼",TildeEqual:"≃",TildeFullEqual:"≅",TildeTilde:"≈",timesbar:"⨱",timesb:"⊠",times:"×",timesd:"⨰",tint:"∭",toea:"⤨",topbot:"⌶",topcir:"⫱",top:"⊤",Topf:"𝕋",topf:"𝕥",topfork:"⫚",tosa:"⤩",tprime:"‴",trade:"™",TRADE:"™",triangle:"▵",triangledown:"▿",triangleleft:"◃",trianglelefteq:"⊴",triangleq:"≜",triangleright:"▹",trianglerighteq:"⊵",tridot:"◬",trie:"≜",triminus:"⨺",TripleDot:"⃛",triplus:"⨹",trisb:"⧍",tritime:"⨻",trpezium:"⏢",Tscr:"𝒯",tscr:"𝓉",TScy:"Ц",tscy:"ц",TSHcy:"Ћ",tshcy:"ћ",Tstrok:"Ŧ",tstrok:"ŧ",twixt:"≬",twoheadleftarrow:"↞",twoheadrightarrow:"↠",Uacute:"Ú",uacute:"ú",uarr:"↑",Uarr:"↟",uArr:"⇑",Uarrocir:"⥉",Ubrcy:"Ў",ubrcy:"ў",Ubreve:"Ŭ",ubreve:"ŭ",Ucirc:"Û",ucirc:"û",Ucy:"У",ucy:"у",udarr:"⇅",Udblac:"Ű",udblac:"ű",udhar:"⥮",ufisht:"⥾",Ufr:"𝔘",ufr:"𝔲",Ugrave:"Ù",ugrave:"ù",uHar:"⥣",uharl:"↿",uharr:"↾",uhblk:"▀",ulcorn:"⌜",ulcorner:"⌜",ulcrop:"⌏",ultri:"◸",Umacr:"Ū",umacr:"ū",uml:"¨",UnderBar:"_",UnderBrace:"⏟",UnderBracket:"⎵",UnderParenthesis:"⏝",Union:"⋃",UnionPlus:"⊎",Uogon:"Ų",uogon:"ų",Uopf:"𝕌",uopf:"𝕦",UpArrowBar:"⤒",uparrow:"↑",UpArrow:"↑",Uparrow:"⇑",UpArrowDownArrow:"⇅",updownarrow:"↕",UpDownArrow:"↕",Updownarrow:"⇕",UpEquilibrium:"⥮",upharpoonleft:"↿",upharpoonright:"↾",uplus:"⊎",UpperLeftArrow:"↖",UpperRightArrow:"↗",upsi:"υ",Upsi:"ϒ",upsih:"ϒ",Upsilon:"Υ",upsilon:"υ",UpTeeArrow:"↥",UpTee:"⊥",upuparrows:"⇈",urcorn:"⌝",urcorner:"⌝",urcrop:"⌎",Uring:"Ů",uring:"ů",urtri:"◹",Uscr:"𝒰",uscr:"𝓊",utdot:"⋰",Utilde:"Ũ",utilde:"ũ",utri:"▵",utrif:"▴",uuarr:"⇈",Uuml:"Ü",uuml:"ü",uwangle:"⦧",vangrt:"⦜",varepsilon:"ϵ",varkappa:"ϰ",varnothing:"∅",varphi:"ϕ",varpi:"ϖ",varpropto:"∝",varr:"↕",vArr:"⇕",varrho:"ϱ",varsigma:"ς",varsubsetneq:"⊊︀",varsubsetneqq:"⫋︀",varsupsetneq:"⊋︀",varsupsetneqq:"⫌︀",vartheta:"ϑ",vartriangleleft:"⊲",vartriangleright:"⊳",vBar:"⫨",Vbar:"⫫",vBarv:"⫩",Vcy:"В",vcy:"в",vdash:"⊢",vDash:"⊨",Vdash:"⊩",VDash:"⊫",Vdashl:"⫦",veebar:"⊻",vee:"∨",Vee:"⋁",veeeq:"≚",vellip:"⋮",verbar:"|",Verbar:"‖",vert:"|",Vert:"‖",VerticalBar:"∣",VerticalLine:"|",VerticalSeparator:"❘",VerticalTilde:"≀",VeryThinSpace:" ",Vfr:"𝔙",vfr:"𝔳",vltri:"⊲",vnsub:"⊂⃒",vnsup:"⊃⃒",Vopf:"𝕍",vopf:"𝕧",vprop:"∝",vrtri:"⊳",Vscr:"𝒱",vscr:"𝓋",vsubnE:"⫋︀",vsubne:"⊊︀",vsupnE:"⫌︀",vsupne:"⊋︀",Vvdash:"⊪",vzigzag:"⦚",Wcirc:"Ŵ",wcirc:"ŵ",wedbar:"⩟",wedge:"∧",Wedge:"⋀",wedgeq:"≙",weierp:"℘",Wfr:"𝔚",wfr:"𝔴",Wopf:"𝕎",wopf:"𝕨",wp:"℘",wr:"≀",wreath:"≀",Wscr:"𝒲",wscr:"𝓌",xcap:"⋂",xcirc:"◯",xcup:"⋃",xdtri:"▽",Xfr:"𝔛",xfr:"𝔵",xharr:"⟷",xhArr:"⟺",Xi:"Ξ",xi:"ξ",xlarr:"⟵",xlArr:"⟸",xmap:"⟼",xnis:"⋻",xodot:"⨀",Xopf:"𝕏",xopf:"𝕩",xoplus:"⨁",xotime:"⨂",xrarr:"⟶",xrArr:"⟹",Xscr:"𝒳",xscr:"𝓍",xsqcup:"⨆",xuplus:"⨄",xutri:"△",xvee:"⋁",xwedge:"⋀",Yacute:"Ý",yacute:"ý",YAcy:"Я",yacy:"я",Ycirc:"Ŷ",ycirc:"ŷ",Ycy:"Ы",ycy:"ы",yen:"¥",Yfr:"𝔜",yfr:"𝔶",YIcy:"Ї",yicy:"ї",Yopf:"𝕐",yopf:"𝕪",Yscr:"𝒴",yscr:"𝓎",YUcy:"Ю",yucy:"ю",yuml:"ÿ",Yuml:"Ÿ",Zacute:"Ź",zacute:"ź",Zcaron:"Ž",zcaron:"ž",Zcy:"З",zcy:"з",Zdot:"Ż",zdot:"ż",zeetrf:"ℨ",ZeroWidthSpace:"",Zeta:"Ζ",zeta:"ζ",zfr:"𝔷",Zfr:"ℨ",ZHcy:"Ж",zhcy:"ж",zigrarr:"⇝",zopf:"𝕫",Zopf:"ℤ",Zscr:"𝒵",zscr:"𝓏",zwj:"",zwnj:""}},{}],62:[function(e,t,n){"use strict";function r(e){var t=Array.prototype.slice.call(arguments,1);return t.forEach(function(t){t&&Object.keys(t).forEach(function(n){e[n]=t[n]})}),e}function o(e){return Object.prototype.toString.call(e)}function i(e){return"[object String]"===o(e)}function a(e){return"[object Object]"===o(e)}function s(e){return"[object RegExp]"===o(e)}function u(e){return"[object Function]"===o(e)}function l(e){return e.replace(/[.?*+^$[\]\\(){}|-]/g,"\\$&")}function c(e){return Object.keys(e||{}).reduce(function(e,t){return e||y.hasOwnProperty(t)},!1)}function p(e){e.__index__=-1,e.__text_cache__=""}function f(e){return function(t,n){var r=t.slice(n);return e.test(r)?r.match(e)[0].length:0}}function h(){return function(e,t){t.normalize(e)}}function d(t){function n(e){return e.replace("%TLDS%",c.src_tlds)}function o(e,t){throw new Error('(LinkifyIt) Invalid schema "'+e+'": '+t)}var c=t.re=r({},e("./lib/re")),d=t.__tlds__.slice();t.__tlds_replaced__||d.push(b),d.push(c.src_xn),c.src_tlds=d.join("|"),c.email_fuzzy=RegExp(n(c.tpl_email_fuzzy),"i"),c.link_fuzzy=RegExp(n(c.tpl_link_fuzzy),"i"),c.link_no_ip_fuzzy=RegExp(n(c.tpl_link_no_ip_fuzzy),"i"),c.host_fuzzy_test=RegExp(n(c.tpl_host_fuzzy_test),"i");var m=[];t.__compiled__={},Object.keys(t.__schemas__).forEach(function(e){var n=t.__schemas__[e];if(null!==n){var r={validate:null,link:null};return t.__compiled__[e]=r,a(n)?(s(n.validate)?r.validate=f(n.validate):u(n.validate)?r.validate=n.validate:o(e,n),void(u(n.normalize)?r.normalize=n.normalize:n.normalize?o(e,n):r.normalize=h())):i(n)?void m.push(e):void o(e,n)}}),m.forEach(function(e){t.__compiled__[t.__schemas__[e]]&&(t.__compiled__[e].validate=t.__compiled__[t.__schemas__[e]].validate,t.__compiled__[e].normalize=t.__compiled__[t.__schemas__[e]].normalize)}),t.__compiled__[""]={validate:null,normalize:h()};var v=Object.keys(t.__compiled__).filter(function(e){return e.length>0&&t.__compiled__[e]}).map(l).join("|");t.re.schema_test=RegExp("(^|(?!_)(?:>|"+c.src_ZPCc+"))("+v+")","i"),t.re.schema_search=RegExp("(^|(?!_)(?:>|"+c.src_ZPCc+"))("+v+")","ig"),t.re.pretest=RegExp("("+t.re.schema_test.source+")|("+t.re.host_fuzzy_test.source+")|@","i"),p(t)}function m(e,t){var n=e.__index__,r=e.__last_index__,o=e.__text_cache__.slice(n,r);this.schema=e.__schema__.toLowerCase(),this.index=n+t,this.lastIndex=r+t,this.raw=o,this.text=o,this.url=o}function v(e,t){var n=new m(e,t);return e.__compiled__[n.schema].normalize(n,e),n}function g(e,t){return this instanceof g?(t||c(e)&&(t=e,e={}),this.__opts__=r({},y,t),this.__index__=-1,this.__last_index__=-1,this.__schema__="",this.__text_cache__="",this.__schemas__=r({},k,e),this.__compiled__={},this.__tlds__=w,this.__tlds_replaced__=!1,this.re={},void d(this)):new g(e,t)}var y={fuzzyLink:!0,fuzzyEmail:!0,fuzzyIP:!1},k={"http:":{validate:function(e,t,n){var r=e.slice(t);return n.re.http||(n.re.http=new RegExp("^\\/\\/"+n.re.src_auth+n.re.src_host_port_strict+n.re.src_path,"i")),n.re.http.test(r)?r.match(n.re.http)[0].length:0}},"https:":"http:","ftp:":"http:","//":{validate:function(e,t,n){var r=e.slice(t);return n.re.no_http||(n.re.no_http=new RegExp("^"+n.re.src_auth+n.re.src_host_port_strict+n.re.src_path,"i")),n.re.no_http.test(r)?t>=3&&":"===e[t-3]?0:r.match(n.re.no_http)[0].length:0}},"mailto:":{validate:function(e,t,n){var r=e.slice(t);return n.re.mailto||(n.re.mailto=new RegExp("^"+n.re.src_email_name+"@"+n.re.src_host_strict,"i")),n.re.mailto.test(r)?r.match(n.re.mailto)[0].length:0}}},b="a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]",w="biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф".split("|");g.prototype.add=function(e,t){return this.__schemas__[e]=t,d(this),this},g.prototype.set=function(e){return this.__opts__=r(this.__opts__,e),this},g.prototype.test=function(e){if(this.__text_cache__=e,this.__index__=-1,!e.length)return!1;var t,n,r,o,i,a,s,u,l;if(this.re.schema_test.test(e))for(s=this.re.schema_search,s.lastIndex=0;null!==(t=s.exec(e));)if(o=this.testSchemaAt(e,t[2],s.lastIndex)){this.__schema__=t[2],this.__index__=t.index+t[1].length,this.__last_index__=t.index+t[0].length+o;break}return this.__opts__.fuzzyLink&&this.__compiled__["http:"]&&(u=e.search(this.re.host_fuzzy_test),u>=0&&(this.__index__<0||u<this.__index__)&&null!==(n=e.match(this.__opts__.fuzzyIP?this.re.link_fuzzy:this.re.link_no_ip_fuzzy))&&(i=n.index+n[1].length,(this.__index__<0||i<this.__index__)&&(this.__schema__="",this.__index__=i,this.__last_index__=n.index+n[0].length))),this.__opts__.fuzzyEmail&&this.__compiled__["mailto:"]&&(l=e.indexOf("@"),l>=0&&null!==(r=e.match(this.re.email_fuzzy))&&(i=r.index+r[1].length,a=r.index+r[0].length,(this.__index__<0||i<this.__index__||i===this.__index__&&a>this.__last_index__)&&(this.__schema__="mailto:",this.__index__=i,this.__last_index__=a))),this.__index__>=0},g.prototype.pretest=function(e){return this.re.pretest.test(e)},g.prototype.testSchemaAt=function(e,t,n){return this.__compiled__[t.toLowerCase()]?this.__compiled__[t.toLowerCase()].validate(e,n,this):0},g.prototype.match=function(e){var t=0,n=[];this.__index__>=0&&this.__text_cache__===e&&(n.push(v(this,t)),t=this.__last_index__);for(var r=t?e.slice(t):e;this.test(r);)n.push(v(this,t)),r=r.slice(this.__last_index__),t+=this.__last_index__;return n.length?n:null},g.prototype.tlds=function(e,t){return e=Array.isArray(e)?e:[e],t?(this.__tlds__=this.__tlds__.concat(e).sort().filter(function(e,t,n){return e!==n[t-1]}).reverse(),d(this),this):(this.__tlds__=e.slice(),this.__tlds_replaced__=!0,d(this),this)},g.prototype.normalize=function(e){e.schema||(e.url="http://"+e.url),"mailto:"!==e.schema||/^mailto:/i.test(e.url)||(e.url="mailto:"+e.url)},t.exports=g},{"./lib/re":63}],63:[function(e,t,n){"use strict";var r=n.src_Any=e("uc.micro/properties/Any/regex").source,o=n.src_Cc=e("uc.micro/categories/Cc/regex").source,i=n.src_Z=e("uc.micro/categories/Z/regex").source,a=n.src_P=e("uc.micro/categories/P/regex").source,s=n.src_ZPCc=[i,a,o].join("|"),u=n.src_ZCc=[i,o].join("|"),l="(?:(?!"+s+")"+r+")",c="(?:(?![0-9]|"+s+")"+r+")",p=n.src_ip4="(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";n.src_auth="(?:(?:(?!"+u+").)+@)?";var f=n.src_port="(?::(?:6(?:[0-4]\\d{3}|5(?:[0-4]\\d{2}|5(?:[0-2]\\d|3[0-5])))|[1-5]?\\d{1,4}))?",h=n.src_host_terminator="(?=$|"+s+")(?!-|_|:\\d|\\.-|\\.(?!$|"+s+"))",d=n.src_path="(?:[/?#](?:(?!"+u+"|[()[\\]{}.,\"'?!\\-]).|\\[(?:(?!"+u+"|\\]).)*\\]|\\((?:(?!"+u+"|[)]).)*\\)|\\{(?:(?!"+u+'|[}]).)*\\}|\\"(?:(?!'+u+'|["]).)+\\"|\\\'(?:(?!'+u+"|[']).)+\\'|\\'(?="+l+").|\\.{2,3}[a-zA-Z0-9%/]|\\.(?!"+u+"|[.]).|\\-(?!--(?:[^-]|$))(?:-*)|\\,(?!"+u+").|\\!(?!"+u+"|[!]).|\\?(?!"+u+"|[?]).)+|\\/)?",m=n.src_email_name='[\\-;:&=\\+\\$,\\"\\.a-zA-Z0-9_]+',v=n.src_xn="xn--[a-z0-9\\-]{1,59}",g=n.src_domain_root="(?:"+v+"|"+c+"{1,63})",y=n.src_domain="(?:"+v+"|(?:"+l+")|(?:"+l+"(?:-(?!-)|"+l+"){0,61}"+l+"))",k=n.src_host="(?:"+p+"|(?:(?:(?:"+y+")\\.)*"+g+"))",b=n.tpl_host_fuzzy="(?:"+p+"|(?:(?:(?:"+y+")\\.)+(?:%TLDS%)))",w=n.tpl_host_no_ip_fuzzy="(?:(?:(?:"+y+")\\.)+(?:%TLDS%))";n.src_host_strict=k+h;var _=n.tpl_host_fuzzy_strict=b+h;n.src_host_port_strict=k+f+h;var x=n.tpl_host_port_fuzzy_strict=b+f+h,C=n.tpl_host_port_no_ip_fuzzy_strict=w+f+h;n.tpl_host_fuzzy_test="localhost|\\.\\d{1,3}\\.|(?:\\.(?:%TLDS%)(?:"+s+"|$))",n.tpl_email_fuzzy="(^|>|"+u+")("+m+"@"+_+")",n.tpl_link_fuzzy="(^|(?![.:/\\-_@])(?:[$+<=>^`|]|"+s+"))((?![$+<=>^`|])"+x+d+")",n.tpl_link_no_ip_fuzzy="(^|(?![.:/\\-_@])(?:[$+<=>^`|]|"+s+"))((?![$+<=>^`|])"+C+d+")"},{"uc.micro/categories/Cc/regex":120,"uc.micro/categories/P/regex":122,"uc.micro/categories/Z/regex":123,"uc.micro/properties/Any/regex":125}],64:[function(e,t,n){"use strict";t.exports=e("./lib/")},{"./lib/":74}],65:[function(e,t,n){"use strict";t.exports=e("entities/maps/entities.json")},{"entities/maps/entities.json":61}],66:[function(e,t,n){"use strict";t.exports=["address","article","aside","base","basefont","blockquote","body","caption","center","col","colgroup","dd","details","dialog","dir","div","dl","dt","fieldset","figcaption","figure","footer","form","frame","frameset","h1","head","header","hr","html","legend","li","link","main","menu","menuitem","meta","nav","noframes","ol","optgroup","option","p","param","pre","section","source","title","summary","table","tbody","td","tfoot","th","thead","title","tr","track","ul"]},{}],67:[function(e,t,n){"use strict";var r="[a-zA-Z_:][a-zA-Z0-9:._-]*",o="[^\"'=<>`\\x00-\\x20]+",i="'[^']*'",a='"[^"]*"',s="(?:"+o+"|"+i+"|"+a+")",u="(?:\\s+"+r+"(?:\\s*=\\s*"+s+")?)",l="<[A-Za-z][A-Za-z0-9\\-]*"+u+"*\\s*\\/?>",c="<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>",p="<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->",f="<[?].*?[?]>",h="<![A-Z]+\\s+[^>]*>",d="<!\\[CDATA\\[[\\s\\S]*?\\]\\]>",m=new RegExp("^(?:"+l+"|"+c+"|"+p+"|"+f+"|"+h+"|"+d+")"),v=new RegExp("^(?:"+l+"|"+c+")");t.exports.HTML_TAG_RE=m,t.exports.HTML_OPEN_CLOSE_TAG_RE=v},{}],68:[function(e,t,n){"use strict";t.exports=["coap","doi","javascript","aaa","aaas","about","acap","cap","cid","crid","data","dav","dict","dns","file","ftp","geo","go","gopher","h323","http","https","iax","icap","im","imap","info","ipp","iris","iris.beep","iris.xpc","iris.xpcs","iris.lwz","ldap","mailto","mid","msrp","msrps","mtqp","mupdate","news","nfs","ni","nih","nntp","opaquelocktoken","pop","pres","rtsp","service","session","shttp","sieve","sip","sips","sms","snmp","soap.beep","soap.beeps","tag","tel","telnet","tftp","thismessage","tn3270","tip","tv","urn","vemmi","ws","wss","xcon","xcon-userid","xmlrpc.beep","xmlrpc.beeps","xmpp","z39.50r","z39.50s","adiumxtra","afp","afs","aim","apt","attachment","aw","beshare","bitcoin","bolo","callto","chrome","chrome-extension","com-eventbrite-attendee","content","cvs","dlna-playsingle","dlna-playcontainer","dtn","dvb","ed2k","facetime","feed","finger","fish","gg","git","gizmoproject","gtalk","hcp","icon","ipn","irc","irc6","ircs","itms","jar","jms","keyparc","lastfm","ldaps","magnet","maps","market","message","mms","ms-help","msnim","mumble","mvn","notes","oid","palm","paparazzi","platform","proxy","psyc","query","res","resource","rmi","rsync","rtmp","secondlife","sftp","sgn","skype","smb","soldat","spotify","ssh","steam","svn","teamspeak","things","udp","unreal","ut2004","ventrilo","view-source","webcal","wtai","wyciwyg","xfire","xri","ymsgr"]},{}],69:[function(e,t,n){"use strict";function r(e){return Object.prototype.toString.call(e)}function o(e){return"[object String]"===r(e)}function i(e,t){return b.call(e,t)}function a(e){var t=Array.prototype.slice.call(arguments,1);return t.forEach(function(t){if(t){if("object"!=typeof t)throw new TypeError(t+"must be object");Object.keys(t).forEach(function(n){e[n]=t[n]})}}),e}function s(e,t,n){return[].concat(e.slice(0,t),n,e.slice(t+1))}function u(e){return e>=55296&&57343>=e?!1:e>=64976&&65007>=e?!1:65535===(65535&e)||65534===(65535&e)?!1:e>=0&&8>=e?!1:11===e?!1:e>=14&&31>=e?!1:e>=127&&159>=e?!1:e>1114111?!1:!0}function l(e){if(e>65535){e-=65536;var t=55296+(e>>10),n=56320+(1023&e);return String.fromCharCode(t,n)}return String.fromCharCode(e)}function c(e,t){var n=0;return i(S,t)?S[t]:35===t.charCodeAt(0)&&C.test(t)&&(n="x"===t[1].toLowerCase()?parseInt(t.slice(2),16):parseInt(t.slice(1),10),u(n))?l(n):e}function p(e){return e.indexOf("\\")<0?e:e.replace(w,"$1")}function f(e){return e.indexOf("\\")<0&&e.indexOf("&")<0?e:e.replace(x,function(e,t,n){return t?t:c(e,n)})}function h(e){return A[e]}function d(e){return M.test(e)?e.replace(O,h):e}function m(e){return e.replace(T,"\\$&")}function v(e){if(e>=8192&&8202>=e)return!0;switch(e){case 9:case 10:case 11:case 12:case 13:case 32:case 160:case 5760:case 8239:case 8287:case 12288:return!0}return!1}function g(e){return E.test(e)}function y(e){switch(e){case 33:case 34:case 35:case 36:case 37:case 38:case 39:case 40:case 41:case 42:case 43:case 44:case 45:case 46:case 47:case 58:case 59:case 60:case 61:case 62:case 63:case 64:case 91:case 92:case 93:case 94:case 95:case 96:case 123:case 124:case 125:case 126:return!0;default:return!1}}function k(e){return e.trim().replace(/\s+/g," ").toUpperCase()}var b=Object.prototype.hasOwnProperty,w=/\\([!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])/g,_=/&([a-z#][a-z0-9]{1,31});/gi,x=new RegExp(w.source+"|"+_.source,"gi"),C=/^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i,S=e("./entities"),M=/[&<>"]/,O=/[&<>"]/g,A={"&":"&","<":"<",">":">",'"':"""},T=/[.?*+^$[\]\\(){}|-]/g,E=e("uc.micro/categories/P/regex");n.lib={},n.lib.mdurl=e("mdurl"),n.lib.ucmicro=e("uc.micro"),n.assign=a,n.isString=o,n.has=i,n.unescapeMd=p,n.unescapeAll=f,n.isValidEntityCode=u,n.fromCodePoint=l,n.escapeHtml=d,n.arrayReplaceAt=s,n.isWhiteSpace=v,n.isMdAsciiPunct=y,n.isPunctChar=g,n.escapeRE=m,n.normalizeReference=k},{"./entities":65,mdurl:118,"uc.micro":124,"uc.micro/categories/P/regex":122}],70:[function(e,t,n){"use strict";n.parseLinkLabel=e("./parse_link_label"),n.parseLinkDestination=e("./parse_link_destination"),n.parseLinkTitle=e("./parse_link_title")},{"./parse_link_destination":71,"./parse_link_label":72,"./parse_link_title":73}],71:[function(e,t,n){"use strict";var r=e("../common/utils").unescapeAll;t.exports=function(e,t,n){var o,i,a=0,s=t,u={ok:!1,pos:0,lines:0,str:""};if(60===e.charCodeAt(t)){for(t++;n>t;){if(o=e.charCodeAt(t),10===o)return u;if(62===o)return u.pos=t+1,u.str=r(e.slice(s+1,t)),u.ok=!0,u;92===o&&n>t+1?t+=2:t++}return u}for(i=0;n>t&&(o=e.charCodeAt(t),32!==o)&&!(32>o||127===o);)if(92===o&&n>t+1)t+=2;else{if(40===o&&(i++,i>1))break;if(41===o&&(i--,0>i))break;t++}return s===t?u:(u.str=r(e.slice(s,t)),u.lines=a,u.pos=t,u.ok=!0,u)}},{"../common/utils":69}],72:[function(e,t,n){"use strict";t.exports=function(e,t,n){var r,o,i,a,s=-1,u=e.posMax,l=e.pos;for(e.pos=t+1,r=1;e.pos<u;){if(i=e.src.charCodeAt(e.pos),93===i&&(r--,0===r)){o=!0;break}if(a=e.pos,e.md.inline.skipToken(e),91===i)if(a===e.pos-1)r++;else if(n)return e.pos=l,-1}return o&&(s=e.pos),e.pos=l,s}},{}],73:[function(e,t,n){"use strict";var r=e("../common/utils").unescapeAll;t.exports=function(e,t,n){var o,i,a=0,s=t,u={ok:!1,pos:0,lines:0,str:""};if(t>=n)return u;if(i=e.charCodeAt(t),34!==i&&39!==i&&40!==i)return u;for(t++,40===i&&(i=41);n>t;){if(o=e.charCodeAt(t),o===i)return u.pos=t+1,u.lines=a,u.str=r(e.slice(s+1,t)),u.ok=!0,u;10===o?a++:92===o&&n>t+1&&(t++,10===e.charCodeAt(t)&&a++),t++}return u}},{"../common/utils":69}],74:[function(e,t,n){"use strict";function r(e){var t=e.trim().toLowerCase();return g.test(t)?y.test(t)?!0:!1:!0}function o(e){var t=d.parse(e,!0);if(t.hostname&&(!t.protocol||k.indexOf(t.protocol)>=0))try{t.hostname=m.toASCII(t.hostname)}catch(n){}return d.encode(d.format(t))}function i(e){var t=d.parse(e,!0);if(t.hostname&&(!t.protocol||k.indexOf(t.protocol)>=0))try{t.hostname=m.toUnicode(t.hostname)}catch(n){}return d.decode(d.format(t))}function a(e,t){return this instanceof a?(t||s.isString(e)||(t=e||{},e="default"),this.inline=new f,this.block=new p,this.core=new c,this.renderer=new l,this.linkify=new h,this.validateLink=r,this.normalizeLink=o,this.normalizeLinkText=i,this.utils=s,this.helpers=u,this.options={},this.configure(e),void(t&&this.set(t))):new a(e,t)}var s=e("./common/utils"),u=e("./helpers"),l=e("./renderer"),c=e("./parser_core"),p=e("./parser_block"),f=e("./parser_inline"),h=e("linkify-it"),d=e("mdurl"),m=e("punycode"),v={"default":e("./presets/default"),zero:e("./presets/zero"),commonmark:e("./presets/commonmark")},g=/^(vbscript|javascript|file|data):/,y=/^data:image\/(gif|png|jpeg|webp);/,k=["http:","https:","mailto:"];a.prototype.set=function(e){return s.assign(this.options,e),this},a.prototype.configure=function(e){var t,n=this;if(s.isString(e)&&(t=e,e=v[t],!e))throw new Error('Wrong `markdown-it` preset "'+t+'", check name');if(!e)throw new Error("Wrong `markdown-it` preset, can't be empty");return e.options&&n.set(e.options),e.components&&Object.keys(e.components).forEach(function(t){e.components[t].rules&&n[t].ruler.enableOnly(e.components[t].rules)}),this},a.prototype.enable=function(e,t){var n=[];Array.isArray(e)||(e=[e]),["core","block","inline"].forEach(function(t){n=n.concat(this[t].ruler.enable(e,!0))},this);var r=e.filter(function(e){return n.indexOf(e)<0});if(r.length&&!t)throw new Error("MarkdownIt. Failed to enable unknown rule(s): "+r);return this},a.prototype.disable=function(e,t){var n=[];Array.isArray(e)||(e=[e]),["core","block","inline"].forEach(function(t){n=n.concat(this[t].ruler.disable(e,!0))},this);var r=e.filter(function(e){return n.indexOf(e)<0});if(r.length&&!t)throw new Error("MarkdownIt. Failed to disable unknown rule(s): "+r);return this},a.prototype.use=function(e){var t=[this].concat(Array.prototype.slice.call(arguments,1));return e.apply(e,t),this},a.prototype.parse=function(e,t){var n=new this.core.State(e,this,t);return this.core.process(n),n.tokens},a.prototype.render=function(e,t){return t=t||{},this.renderer.render(this.parse(e,t),this.options,t)},a.prototype.parseInline=function(e,t){var n=new this.core.State(e,this,t);return n.inlineMode=!0,this.core.process(n),n.tokens},a.prototype.renderInline=function(e,t){return t=t||{},this.renderer.render(this.parseInline(e,t),this.options,t)},t.exports=a},{"./common/utils":69,"./helpers":70,"./parser_block":75,"./parser_core":76,"./parser_inline":77,"./presets/commonmark":78,"./presets/default":79,"./presets/zero":80,"./renderer":81,"linkify-it":62,mdurl:118,punycode:2}],75:[function(e,t,n){"use strict";function r(){this.ruler=new o;for(var e=0;e<i.length;e++)this.ruler.push(i[e][0],i[e][1],{alt:(i[e][2]||[]).slice()})}var o=e("./ruler"),i=[["code",e("./rules_block/code")],["fence",e("./rules_block/fence"),["paragraph","reference","blockquote","list"]],["blockquote",e("./rules_block/blockquote"),["paragraph","reference","list"]],["hr",e("./rules_block/hr"),["paragraph","reference","blockquote","list"]],["list",e("./rules_block/list"),["paragraph","reference","blockquote"]],["reference",e("./rules_block/reference")],["heading",e("./rules_block/heading"),["paragraph","reference","blockquote"]],["lheading",e("./rules_block/lheading")],["html_block",e("./rules_block/html_block"),["paragraph","reference","blockquote"]],["table",e("./rules_block/table"),["paragraph","reference"]],["paragraph",e("./rules_block/paragraph")]];r.prototype.tokenize=function(e,t,n){for(var r,o,i=this.ruler.getRules(""),a=i.length,s=t,u=!1,l=e.md.options.maxNesting;n>s&&(e.line=s=e.skipEmptyLines(s),!(s>=n))&&!(e.tShift[s]<e.blkIndent);){if(e.level>=l){e.line=n;break}for(o=0;a>o&&!(r=i[o](e,s,n,!1));o++);if(e.tight=!u,e.isEmpty(e.line-1)&&(u=!0),s=e.line,n>s&&e.isEmpty(s)){if(u=!0,s++,n>s&&"list"===e.parentType&&e.isEmpty(s))break;e.line=s}}},r.prototype.parse=function(e,t,n,r){var o;return e?(o=new this.State(e,t,n,r),void this.tokenize(o,o.line,o.lineMax)):[]},r.prototype.State=e("./rules_block/state_block"),t.exports=r},{"./ruler":82,"./rules_block/blockquote":83,"./rules_block/code":84,"./rules_block/fence":85,"./rules_block/heading":86,"./rules_block/hr":87,"./rules_block/html_block":88,"./rules_block/lheading":89,"./rules_block/list":90,"./rules_block/paragraph":91,"./rules_block/reference":92,"./rules_block/state_block":93,"./rules_block/table":94}],76:[function(e,t,n){"use strict";function r(){this.ruler=new o;for(var e=0;e<i.length;e++)this.ruler.push(i[e][0],i[e][1])}var o=e("./ruler"),i=[["normalize",e("./rules_core/normalize")],["block",e("./rules_core/block")],["inline",e("./rules_core/inline")],["linkify",e("./rules_core/linkify")],["replacements",e("./rules_core/replacements")],["smartquotes",e("./rules_core/smartquotes")]];
+r.prototype.process=function(e){var t,n,r;for(r=this.ruler.getRules(""),t=0,n=r.length;n>t;t++)r[t](e)},r.prototype.State=e("./rules_core/state_core"),t.exports=r},{"./ruler":82,"./rules_core/block":95,"./rules_core/inline":96,"./rules_core/linkify":97,"./rules_core/normalize":98,"./rules_core/replacements":99,"./rules_core/smartquotes":100,"./rules_core/state_core":101}],77:[function(e,t,n){"use strict";function r(){this.ruler=new o;for(var e=0;e<i.length;e++)this.ruler.push(i[e][0],i[e][1])}var o=e("./ruler"),i=[["text",e("./rules_inline/text")],["newline",e("./rules_inline/newline")],["escape",e("./rules_inline/escape")],["backticks",e("./rules_inline/backticks")],["strikethrough",e("./rules_inline/strikethrough")],["emphasis",e("./rules_inline/emphasis")],["link",e("./rules_inline/link")],["image",e("./rules_inline/image")],["autolink",e("./rules_inline/autolink")],["html_inline",e("./rules_inline/html_inline")],["entity",e("./rules_inline/entity")]];r.prototype.skipToken=function(e){var t,n=e.pos,r=this.ruler.getRules(""),o=r.length,i=e.md.options.maxNesting,a=e.cache;if("undefined"!=typeof a[n])return void(e.pos=a[n]);if(e.level<i)for(t=0;o>t;t++)if(r[t](e,!0))return void(a[n]=e.pos);e.pos++,a[n]=e.pos},r.prototype.tokenize=function(e){for(var t,n,r=this.ruler.getRules(""),o=r.length,i=e.posMax,a=e.md.options.maxNesting;e.pos<i;){if(e.level<a)for(n=0;o>n&&!(t=r[n](e,!1));n++);if(t){if(e.pos>=i)break}else e.pending+=e.src[e.pos++]}e.pending&&e.pushPending()},r.prototype.parse=function(e,t,n,r){var o=new this.State(e,t,n,r);this.tokenize(o)},r.prototype.State=e("./rules_inline/state_inline"),t.exports=r},{"./ruler":82,"./rules_inline/autolink":102,"./rules_inline/backticks":103,"./rules_inline/emphasis":104,"./rules_inline/entity":105,"./rules_inline/escape":106,"./rules_inline/html_inline":107,"./rules_inline/image":108,"./rules_inline/link":109,"./rules_inline/newline":110,"./rules_inline/state_inline":111,"./rules_inline/strikethrough":112,"./rules_inline/text":113}],78:[function(e,t,n){"use strict";t.exports={options:{html:!0,xhtmlOut:!0,breaks:!1,langPrefix:"language-",linkify:!1,typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{rules:["normalize","block","inline"]},block:{rules:["blockquote","code","fence","heading","hr","html_block","lheading","list","reference","paragraph"]},inline:{rules:["autolink","backticks","emphasis","entity","escape","html_inline","image","link","newline","text"]}}}},{}],79:[function(e,t,n){"use strict";t.exports={options:{html:!1,xhtmlOut:!1,breaks:!1,langPrefix:"language-",linkify:!1,typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{},block:{},inline:{}}}},{}],80:[function(e,t,n){"use strict";t.exports={options:{html:!1,xhtmlOut:!1,breaks:!1,langPrefix:"language-",linkify:!1,typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{rules:["normalize","block","inline"]},block:{rules:["paragraph"]},inline:{rules:["text"]}}}},{}],81:[function(e,t,n){"use strict";function r(){this.rules=o({},s)}var o=e("./common/utils").assign,i=e("./common/utils").unescapeAll,a=e("./common/utils").escapeHtml,s={};s.code_inline=function(e,t){return"<code>"+a(e[t].content)+"</code>"},s.code_block=function(e,t){return"<pre><code>"+a(e[t].content)+"</code></pre>\n"},s.fence=function(e,t,n,r,o){var s,u=e[t],l=u.info?i(u.info).trim():"",c="";return l&&(c=l.split(/\s+/g)[0],u.attrPush(["class",n.langPrefix+c])),s=n.highlight?n.highlight(u.content,c)||a(u.content):a(u.content),"<pre><code"+o.renderAttrs(u)+">"+s+"</code></pre>\n"},s.image=function(e,t,n,r,o){var i=e[t];return i.attrs[i.attrIndex("alt")][1]=o.renderInlineAsText(i.children,n,r),o.renderToken(e,t,n)},s.hardbreak=function(e,t,n){return n.xhtmlOut?"<br />\n":"<br>\n"},s.softbreak=function(e,t,n){return n.breaks?n.xhtmlOut?"<br />\n":"<br>\n":"\n"},s.text=function(e,t){return a(e[t].content)},s.html_block=function(e,t){return e[t].content},s.html_inline=function(e,t){return e[t].content},r.prototype.renderAttrs=function(e){var t,n,r;if(!e.attrs)return"";for(r="",t=0,n=e.attrs.length;n>t;t++)r+=" "+a(e.attrs[t][0])+'="'+a(e.attrs[t][1])+'"';return r},r.prototype.renderToken=function(e,t,n){var r,o="",i=!1,a=e[t];return a.hidden?"":(a.block&&-1!==a.nesting&&t&&e[t-1].hidden&&(o+="\n"),o+=(-1===a.nesting?"</":"<")+a.tag,o+=this.renderAttrs(a),0===a.nesting&&n.xhtmlOut&&(o+=" /"),a.block&&(i=!0,1===a.nesting&&t+1<e.length&&(r=e[t+1],"inline"===r.type||r.hidden?i=!1:-1===r.nesting&&r.tag===a.tag&&(i=!1))),o+=i?">\n":">")},r.prototype.renderInline=function(e,t,n){for(var r,o="",i=this.rules,a=0,s=e.length;s>a;a++)r=e[a].type,o+="undefined"!=typeof i[r]?i[r](e,a,t,n,this):this.renderToken(e,a,t);return o},r.prototype.renderInlineAsText=function(e,t,n){for(var r="",o=this.rules,i=0,a=e.length;a>i;i++)"text"===e[i].type?r+=o.text(e,i,t,n,this):"image"===e[i].type&&(r+=this.renderInlineAsText(e[i].children,t,n));return r},r.prototype.render=function(e,t,n){var r,o,i,a="",s=this.rules;for(r=0,o=e.length;o>r;r++)i=e[r].type,a+="inline"===i?this.renderInline(e[r].children,t,n):"undefined"!=typeof s[i]?s[e[r].type](e,r,t,n,this):this.renderToken(e,r,t,n);return a},t.exports=r},{"./common/utils":69}],82:[function(e,t,n){"use strict";function r(){this.__rules__=[],this.__cache__=null}r.prototype.__find__=function(e){for(var t=0;t<this.__rules__.length;t++)if(this.__rules__[t].name===e)return t;return-1},r.prototype.__compile__=function(){var e=this,t=[""];e.__rules__.forEach(function(e){e.enabled&&e.alt.forEach(function(e){t.indexOf(e)<0&&t.push(e)})}),e.__cache__={},t.forEach(function(t){e.__cache__[t]=[],e.__rules__.forEach(function(n){n.enabled&&(t&&n.alt.indexOf(t)<0||e.__cache__[t].push(n.fn))})})},r.prototype.at=function(e,t,n){var r=this.__find__(e),o=n||{};if(-1===r)throw new Error("Parser rule not found: "+e);this.__rules__[r].fn=t,this.__rules__[r].alt=o.alt||[],this.__cache__=null},r.prototype.before=function(e,t,n,r){var o=this.__find__(e),i=r||{};if(-1===o)throw new Error("Parser rule not found: "+e);this.__rules__.splice(o,0,{name:t,enabled:!0,fn:n,alt:i.alt||[]}),this.__cache__=null},r.prototype.after=function(e,t,n,r){var o=this.__find__(e),i=r||{};if(-1===o)throw new Error("Parser rule not found: "+e);this.__rules__.splice(o+1,0,{name:t,enabled:!0,fn:n,alt:i.alt||[]}),this.__cache__=null},r.prototype.push=function(e,t,n){var r=n||{};this.__rules__.push({name:e,enabled:!0,fn:t,alt:r.alt||[]}),this.__cache__=null},r.prototype.enable=function(e,t){Array.isArray(e)||(e=[e]);var n=[];return e.forEach(function(e){var r=this.__find__(e);if(0>r){if(t)return;throw new Error("Rules manager: invalid rule name "+e)}this.__rules__[r].enabled=!0,n.push(e)},this),this.__cache__=null,n},r.prototype.enableOnly=function(e,t){Array.isArray(e)||(e=[e]),this.__rules__.forEach(function(e){e.enabled=!1}),this.enable(e,t)},r.prototype.disable=function(e,t){Array.isArray(e)||(e=[e]);var n=[];return e.forEach(function(e){var r=this.__find__(e);if(0>r){if(t)return;throw new Error("Rules manager: invalid rule name "+e)}this.__rules__[r].enabled=!1,n.push(e)},this),this.__cache__=null,n},r.prototype.getRules=function(e){return null===this.__cache__&&this.__compile__(),this.__cache__[e]||[]},t.exports=r},{}],83:[function(e,t,n){"use strict";t.exports=function(e,t,n,r){var o,i,a,s,u,l,c,p,f,h,d,m,v=e.bMarks[t]+e.tShift[t],g=e.eMarks[t];if(62!==e.src.charCodeAt(v++))return!1;if(r)return!0;for(32===e.src.charCodeAt(v)&&v++,u=e.blkIndent,e.blkIndent=0,s=[e.bMarks[t]],e.bMarks[t]=v,v=g>v?e.skipSpaces(v):v,i=v>=g,a=[e.tShift[t]],e.tShift[t]=v-e.bMarks[t],p=e.md.block.ruler.getRules("blockquote"),o=t+1;n>o&&!(e.tShift[o]<u)&&(v=e.bMarks[o]+e.tShift[o],g=e.eMarks[o],!(v>=g));o++)if(62!==e.src.charCodeAt(v++)){if(i)break;for(m=!1,h=0,d=p.length;d>h;h++)if(p[h](e,o,n,!0)){m=!0;break}if(m)break;s.push(e.bMarks[o]),a.push(e.tShift[o]),e.tShift[o]=-1}else 32===e.src.charCodeAt(v)&&v++,s.push(e.bMarks[o]),e.bMarks[o]=v,v=g>v?e.skipSpaces(v):v,i=v>=g,a.push(e.tShift[o]),e.tShift[o]=v-e.bMarks[o];for(l=e.parentType,e.parentType="blockquote",f=e.push("blockquote_open","blockquote",1),f.markup=">",f.map=c=[t,0],e.md.block.tokenize(e,t,o),f=e.push("blockquote_close","blockquote",-1),f.markup=">",e.parentType=l,c[1]=e.line,h=0;h<a.length;h++)e.bMarks[h+t]=s[h],e.tShift[h+t]=a[h];return e.blkIndent=u,!0}},{}],84:[function(e,t,n){"use strict";t.exports=function(e,t,n){var r,o,i;if(e.tShift[t]-e.blkIndent<4)return!1;for(o=r=t+1;n>r;)if(e.isEmpty(r))r++;else{if(!(e.tShift[r]-e.blkIndent>=4))break;r++,o=r}return e.line=r,i=e.push("code_block","code",0),i.content=e.getLines(t,o,4+e.blkIndent,!0),i.map=[t,e.line],!0}},{}],85:[function(e,t,n){"use strict";t.exports=function(e,t,n,r){var o,i,a,s,u,l,c,p=!1,f=e.bMarks[t]+e.tShift[t],h=e.eMarks[t];if(f+3>h)return!1;if(o=e.src.charCodeAt(f),126!==o&&96!==o)return!1;if(u=f,f=e.skipChars(f,o),i=f-u,3>i)return!1;if(c=e.src.slice(u,f),a=e.src.slice(f,h),a.indexOf("`")>=0)return!1;if(r)return!0;for(s=t;(s++,!(s>=n))&&(f=u=e.bMarks[s]+e.tShift[s],h=e.eMarks[s],!(h>f&&e.tShift[s]<e.blkIndent));)if(e.src.charCodeAt(f)===o&&!(e.tShift[s]-e.blkIndent>=4||(f=e.skipChars(f,o),i>f-u||(f=e.skipSpaces(f),h>f)))){p=!0;break}return i=e.tShift[t],e.line=s+(p?1:0),l=e.push("fence","code",0),l.info=a,l.content=e.getLines(t+1,s,i,!0),l.markup=c,l.map=[t,e.line],!0}},{}],86:[function(e,t,n){"use strict";t.exports=function(e,t,n,r){var o,i,a,s,u=e.bMarks[t]+e.tShift[t],l=e.eMarks[t];if(o=e.src.charCodeAt(u),35!==o||u>=l)return!1;for(i=1,o=e.src.charCodeAt(++u);35===o&&l>u&&6>=i;)i++,o=e.src.charCodeAt(++u);return i>6||l>u&&32!==o?!1:r?!0:(l=e.skipCharsBack(l,32,u),a=e.skipCharsBack(l,35,u),a>u&&32===e.src.charCodeAt(a-1)&&(l=a),e.line=t+1,s=e.push("heading_open","h"+String(i),1),s.markup="########".slice(0,i),s.map=[t,e.line],s=e.push("inline","",0),s.content=e.src.slice(u,l).trim(),s.map=[t,e.line],s.children=[],s=e.push("heading_close","h"+String(i),-1),s.markup="########".slice(0,i),!0)}},{}],87:[function(e,t,n){"use strict";t.exports=function(e,t,n,r){var o,i,a,s,u=e.bMarks[t]+e.tShift[t],l=e.eMarks[t];if(o=e.src.charCodeAt(u++),42!==o&&45!==o&&95!==o)return!1;for(i=1;l>u;){if(a=e.src.charCodeAt(u++),a!==o&&32!==a)return!1;a===o&&i++}return 3>i?!1:r?!0:(e.line=t+1,s=e.push("hr","hr",0),s.map=[t,e.line],s.markup=Array(i+1).join(String.fromCharCode(o)),!0)}},{}],88:[function(e,t,n){"use strict";var r=e("../common/html_blocks"),o=e("../common/html_re").HTML_OPEN_CLOSE_TAG_RE,i=[[/^<(script|pre|style)(?=(\s|>|$))/i,/<\/(script|pre|style)>/i,!0],[/^<!--/,/-->/,!0],[/^<\?/,/\?>/,!0],[/^<![A-Z]/,/>/,!0],[/^<!\[CDATA\[/,/\]\]>/,!0],[new RegExp("^</?("+r.join("|")+")(?=(\\s|/?>|$))","i"),/^$/,!0],[new RegExp(o.source+"\\s*$"),/^$/,!1]];t.exports=function(e,t,n,r){var o,a,s,u,l=e.bMarks[t]+e.tShift[t],c=e.eMarks[t];if(!e.md.options.html)return!1;if(60!==e.src.charCodeAt(l))return!1;for(u=e.src.slice(l,c),o=0;o<i.length&&!i[o][0].test(u);o++);if(o===i.length)return!1;if(r)return i[o][2];if(a=t+1,!i[o][1].test(u))for(;n>a&&!(e.tShift[a]<e.blkIndent);a++)if(l=e.bMarks[a]+e.tShift[a],c=e.eMarks[a],u=e.src.slice(l,c),i[o][1].test(u)){0!==u.length&&a++;break}return e.line=a,s=e.push("html_block","",0),s.map=[t,a],s.content=e.getLines(t,a,e.blkIndent,!0),!0}},{"../common/html_blocks":66,"../common/html_re":67}],89:[function(e,t,n){"use strict";t.exports=function(e,t,n){var r,o,i,a,s,u=t+1;return u>=n?!1:e.tShift[u]<e.blkIndent?!1:e.tShift[u]-e.blkIndent>3?!1:(o=e.bMarks[u]+e.tShift[u],i=e.eMarks[u],o>=i?!1:(r=e.src.charCodeAt(o),45!==r&&61!==r?!1:(o=e.skipChars(o,r),o=e.skipSpaces(o),i>o?!1:(o=e.bMarks[t]+e.tShift[t],e.line=u+1,s=61===r?1:2,a=e.push("heading_open","h"+String(s),1),a.markup=String.fromCharCode(r),a.map=[t,e.line],a=e.push("inline","",0),a.content=e.src.slice(o,e.eMarks[t]).trim(),a.map=[t,e.line-1],a.children=[],a=e.push("heading_close","h"+String(s),-1),a.markup=String.fromCharCode(r),!0))))}},{}],90:[function(e,t,n){"use strict";function r(e,t){var n,r,o;return r=e.bMarks[t]+e.tShift[t],o=e.eMarks[t],n=e.src.charCodeAt(r++),42!==n&&45!==n&&43!==n?-1:o>r&&32!==e.src.charCodeAt(r)?-1:r}function o(e,t){var n,r=e.bMarks[t]+e.tShift[t],o=r,i=e.eMarks[t];if(o+1>=i)return-1;if(n=e.src.charCodeAt(o++),48>n||n>57)return-1;for(;;){if(o>=i)return-1;n=e.src.charCodeAt(o++);{if(!(n>=48&&57>=n)){if(41===n||46===n)break;return-1}if(o-r>=10)return-1}}return i>o&&32!==e.src.charCodeAt(o)?-1:o}function i(e,t){var n,r,o=e.level+2;for(n=t+2,r=e.tokens.length-2;r>n;n++)e.tokens[n].level===o&&"paragraph_open"===e.tokens[n].type&&(e.tokens[n+2].hidden=!0,e.tokens[n].hidden=!0,n+=2)}t.exports=function(e,t,n,a){var s,u,l,c,p,f,h,d,m,v,g,y,k,b,w,_,x,C,S,M,O,A,T,E=!0;if((d=o(e,t))>=0)k=!0;else{if(!((d=r(e,t))>=0))return!1;k=!1}if(y=e.src.charCodeAt(d-1),a)return!0;for(w=e.tokens.length,k?(h=e.bMarks[t]+e.tShift[t],g=Number(e.src.substr(h,d-h-1)),M=e.push("ordered_list_open","ol",1),1!==g&&(M.attrs=[["start",g]])):M=e.push("bullet_list_open","ul",1),M.map=x=[t,0],M.markup=String.fromCharCode(y),s=t,_=!1,S=e.md.block.ruler.getRules("list");!(!(n>s)||(b=e.skipSpaces(d),m=e.eMarks[s],v=b>=m?1:b-d,v>4&&(v=1),u=d-e.bMarks[s]+v,M=e.push("list_item_open","li",1),M.markup=String.fromCharCode(y),M.map=C=[t,0],c=e.blkIndent,p=e.tight,l=e.tShift[t],f=e.parentType,e.tShift[t]=b-e.bMarks[t],e.blkIndent=u,e.tight=!0,e.parentType="list",e.md.block.tokenize(e,t,n,!0),(!e.tight||_)&&(E=!1),_=e.line-t>1&&e.isEmpty(e.line-1),e.blkIndent=c,e.tShift[t]=l,e.tight=p,e.parentType=f,M=e.push("list_item_close","li",-1),M.markup=String.fromCharCode(y),s=t=e.line,C[1]=s,b=e.bMarks[t],s>=n)||e.isEmpty(s)||e.tShift[s]<e.blkIndent);){for(T=!1,O=0,A=S.length;A>O;O++)if(S[O](e,s,n,!0)){T=!0;break}if(T)break;if(k){if(d=o(e,s),0>d)break}else if(d=r(e,s),0>d)break;if(y!==e.src.charCodeAt(d-1))break}return M=k?e.push("ordered_list_close","ol",-1):e.push("bullet_list_close","ul",-1),M.markup=String.fromCharCode(y),x[1]=s,e.line=s,E&&i(e,w),!0}},{}],91:[function(e,t,n){"use strict";t.exports=function(e,t){for(var n,r,o,i,a,s=t+1,u=e.md.block.ruler.getRules("paragraph"),l=e.lineMax;l>s&&!e.isEmpty(s);s++)if(!(e.tShift[s]-e.blkIndent>3||e.tShift[s]<0)){for(r=!1,o=0,i=u.length;i>o;o++)if(u[o](e,s,l,!0)){r=!0;break}if(r)break}return n=e.getLines(t,s,e.blkIndent,!1).trim(),e.line=s,a=e.push("paragraph_open","p",1),a.map=[t,e.line],a=e.push("inline","",0),a.content=n,a.map=[t,e.line],a.children=[],a=e.push("paragraph_close","p",-1),!0}},{}],92:[function(e,t,n){"use strict";var r=e("../helpers/parse_link_destination"),o=e("../helpers/parse_link_title"),i=e("../common/utils").normalizeReference;t.exports=function(e,t,n,a){var s,u,l,c,p,f,h,d,m,v,g,y,k,b,w,_=0,x=e.bMarks[t]+e.tShift[t],C=e.eMarks[t],S=t+1;if(91!==e.src.charCodeAt(x))return!1;for(;++x<C;)if(93===e.src.charCodeAt(x)&&92!==e.src.charCodeAt(x-1)){if(x+1===C)return!1;if(58!==e.src.charCodeAt(x+1))return!1;break}for(c=e.lineMax,b=e.md.block.ruler.getRules("reference");c>S&&!e.isEmpty(S);S++)if(!(e.tShift[S]-e.blkIndent>3||e.tShift[S]<0)){for(k=!1,f=0,h=b.length;h>f;f++)if(b[f](e,S,c,!0)){k=!0;break}if(k)break}for(y=e.getLines(t,S,e.blkIndent,!1).trim(),C=y.length,x=1;C>x;x++){if(s=y.charCodeAt(x),91===s)return!1;if(93===s){m=x;break}10===s?_++:92===s&&(x++,C>x&&10===y.charCodeAt(x)&&_++)}if(0>m||58!==y.charCodeAt(m+1))return!1;for(x=m+2;C>x;x++)if(s=y.charCodeAt(x),10===s)_++;else if(32!==s)break;if(v=r(y,x,C),!v.ok)return!1;if(p=e.md.normalizeLink(v.str),!e.md.validateLink(p))return!1;for(x=v.pos,_+=v.lines,u=x,l=_,g=x;C>x;x++)if(s=y.charCodeAt(x),10===s)_++;else if(32!==s)break;for(v=o(y,x,C),C>x&&g!==x&&v.ok?(w=v.str,x=v.pos,_+=v.lines):(w="",x=u,_=l);C>x&&32===y.charCodeAt(x);)x++;if(C>x&&10!==y.charCodeAt(x)&&w)for(w="",x=u,_=l;C>x&&32===y.charCodeAt(x);)x++;return C>x&&10!==y.charCodeAt(x)?!1:(d=i(y.slice(1,m)))?a?!0:("undefined"==typeof e.env.references&&(e.env.references={}),"undefined"==typeof e.env.references[d]&&(e.env.references[d]={title:w,href:p}),e.line=t+_+1,!0):!1}},{"../common/utils":69,"../helpers/parse_link_destination":71,"../helpers/parse_link_title":73}],93:[function(e,t,n){"use strict";function r(e,t,n,r){var o,i,a,s,u,l,c;for(this.src=e,this.md=t,this.env=n,this.tokens=r,this.bMarks=[],this.eMarks=[],this.tShift=[],this.blkIndent=0,this.line=0,this.lineMax=0,this.tight=!1,this.parentType="root",this.ddIndent=-1,this.level=0,this.result="",i=this.src,l=0,c=!1,a=s=l=0,u=i.length;u>s;s++){if(o=i.charCodeAt(s),!c){if(32===o){l++;continue}c=!0}(10===o||s===u-1)&&(10!==o&&s++,this.bMarks.push(a),this.eMarks.push(s),this.tShift.push(l),c=!1,l=0,a=s+1)}this.bMarks.push(i.length),this.eMarks.push(i.length),this.tShift.push(0),this.lineMax=this.bMarks.length-1}var o=e("../token");r.prototype.push=function(e,t,n){var r=new o(e,t,n);return r.block=!0,0>n&&this.level--,r.level=this.level,n>0&&this.level++,this.tokens.push(r),r},r.prototype.isEmpty=function(e){return this.bMarks[e]+this.tShift[e]>=this.eMarks[e]},r.prototype.skipEmptyLines=function(e){for(var t=this.lineMax;t>e&&!(this.bMarks[e]+this.tShift[e]<this.eMarks[e]);e++);return e},r.prototype.skipSpaces=function(e){for(var t=this.src.length;t>e&&32===this.src.charCodeAt(e);e++);return e},r.prototype.skipChars=function(e,t){for(var n=this.src.length;n>e&&this.src.charCodeAt(e)===t;e++);return e},r.prototype.skipCharsBack=function(e,t,n){if(n>=e)return e;for(;e>n;)if(t!==this.src.charCodeAt(--e))return e+1;return e},r.prototype.getLines=function(e,t,n,r){var o,i,a,s,u,l=e;if(e>=t)return"";if(l+1===t)return i=this.bMarks[l]+Math.min(this.tShift[l],n),a=this.eMarks[t-1]+(r?1:0),this.src.slice(i,a);for(s=new Array(t-e),o=0;t>l;l++,o++)u=this.tShift[l],u>n&&(u=n),0>u&&(u=0),i=this.bMarks[l]+u,a=t>l+1||r?this.eMarks[l]+1:this.eMarks[l],s[o]=this.src.slice(i,a);return s.join("")},r.prototype.Token=o,t.exports=r},{"../token":114}],94:[function(e,t,n){"use strict";function r(e,t){var n=e.bMarks[t]+e.blkIndent,r=e.eMarks[t];return e.src.substr(n,r-n)}function o(e){var t,n=[],r=0,o=e.length,i=0,a=0,s=!1,u=0;for(t=e.charCodeAt(r);o>r;)96===t&&i%2===0?(s=!s,u=r):124!==t||i%2!==0||s?92===t?i++:i=0:(n.push(e.substring(a,r)),a=r+1),r++,r===o&&s&&(s=!1,r=u+1),t=e.charCodeAt(r);return n.push(e.substring(a)),n}t.exports=function(e,t,n,i){var a,s,u,l,c,p,f,h,d,m,v;if(t+2>n)return!1;if(c=t+1,e.tShift[c]<e.blkIndent)return!1;if(u=e.bMarks[c]+e.tShift[c],u>=e.eMarks[c])return!1;if(a=e.src.charCodeAt(u),124!==a&&45!==a&&58!==a)return!1;if(s=r(e,t+1),!/^[-:| ]+$/.test(s))return!1;if(p=s.split("|"),p.length<2)return!1;for(h=[],l=0;l<p.length;l++){if(d=p[l].trim(),!d){if(0===l||l===p.length-1)continue;return!1}if(!/^:?-+:?$/.test(d))return!1;58===d.charCodeAt(d.length-1)?h.push(58===d.charCodeAt(0)?"center":"right"):58===d.charCodeAt(0)?h.push("left"):h.push("")}if(s=r(e,t).trim(),-1===s.indexOf("|"))return!1;if(p=o(s.replace(/^\||\|$/g,"")),h.length!==p.length)return!1;if(i)return!0;for(f=e.push("table_open","table",1),f.map=m=[t,0],f=e.push("thead_open","thead",1),f.map=[t,t+1],f=e.push("tr_open","tr",1),f.map=[t,t+1],l=0;l<p.length;l++)f=e.push("th_open","th",1),f.map=[t,t+1],h[l]&&(f.attrs=[["style","text-align:"+h[l]]]),f=e.push("inline","",0),f.content=p[l].trim(),f.map=[t,t+1],f.children=[],f=e.push("th_close","th",-1);for(f=e.push("tr_close","tr",-1),f=e.push("thead_close","thead",-1),f=e.push("tbody_open","tbody",1),f.map=v=[t+2,0],c=t+2;n>c&&!(e.tShift[c]<e.blkIndent)&&(s=r(e,c).trim(),-1!==s.indexOf("|"));c++){for(p=o(s.replace(/^\||\|$/g,"")),p.length=h.length,f=e.push("tr_open","tr",1),l=0;l<p.length;l++)f=e.push("td_open","td",1),h[l]&&(f.attrs=[["style","text-align:"+h[l]]]),f=e.push("inline","",0),f.content=p[l]?p[l].trim():"",f.children=[],f=e.push("td_close","td",-1);f=e.push("tr_close","tr",-1)}return f=e.push("tbody_close","tbody",-1),f=e.push("table_close","table",-1),m[1]=v[1]=c,e.line=c,!0}},{}],95:[function(e,t,n){"use strict";t.exports=function(e){var t;e.inlineMode?(t=new e.Token("inline","",0),t.content=e.src,t.map=[0,1],t.children=[],e.tokens.push(t)):e.md.block.parse(e.src,e.md,e.env,e.tokens)}},{}],96:[function(e,t,n){"use strict";t.exports=function(e){var t,n,r,o=e.tokens;for(n=0,r=o.length;r>n;n++)t=o[n],"inline"===t.type&&e.md.inline.parse(t.content,e.md,e.env,t.children)}},{}],97:[function(e,t,n){"use strict";function r(e){return/^<a[>\s]/i.test(e)}function o(e){return/^<\/a\s*>/i.test(e)}var i=e("../common/utils").arrayReplaceAt;t.exports=function(e){var t,n,a,s,u,l,c,p,f,h,d,m,v,g,y,k,b,w=e.tokens;if(e.md.options.linkify)for(n=0,a=w.length;a>n;n++)if("inline"===w[n].type&&e.md.linkify.pretest(w[n].content))for(s=w[n].children,v=0,t=s.length-1;t>=0;t--)if(l=s[t],"link_close"!==l.type){if("html_inline"===l.type&&(r(l.content)&&v>0&&v--,o(l.content)&&v++),!(v>0)&&"text"===l.type&&e.md.linkify.test(l.content)){for(f=l.content,b=e.md.linkify.match(f),c=[],m=l.level,d=0,p=0;p<b.length;p++)g=b[p].url,y=e.md.normalizeLink(g),e.md.validateLink(y)&&(k=b[p].text,k=b[p].schema?"mailto:"!==b[p].schema||/^mailto:/i.test(k)?e.md.normalizeLinkText(k):e.md.normalizeLinkText("mailto:"+k).replace(/^mailto:/,""):e.md.normalizeLinkText("http://"+k).replace(/^http:\/\//,""),h=b[p].index,h>d&&(u=new e.Token("text","",0),u.content=f.slice(d,h),u.level=m,c.push(u)),u=new e.Token("link_open","a",1),u.attrs=[["href",y]],u.level=m++,u.markup="linkify",u.info="auto",c.push(u),u=new e.Token("text","",0),u.content=k,u.level=m,c.push(u),u=new e.Token("link_close","a",-1),u.level=--m,u.markup="linkify",u.info="auto",c.push(u),d=b[p].lastIndex);d<f.length&&(u=new e.Token("text","",0),u.content=f.slice(d),u.level=m,c.push(u)),w[n].children=s=i(s,t,c)}}else for(t--;s[t].level!==l.level&&"link_open"!==s[t].type;)t--}},{"../common/utils":69}],98:[function(e,t,n){"use strict";var r=/[\n\t]/g,o=/\r[\n\u0085]|[\u2424\u2028\u0085]/g,i=/\u0000/g;t.exports=function(e){var t,n,a;t=e.src.replace(o,"\n"),t=t.replace(i,"�"),t.indexOf(" ")>=0&&(n=0,a=0,t=t.replace(r,function(e,r){var o;return 10===t.charCodeAt(r)?(n=r+1,a=0,e):(o=" ".slice((r-n-a)%4),a=r-n+1,o)})),e.src=t}},{}],99:[function(e,t,n){"use strict";function r(e,t){return l[t.toLowerCase()]}function o(e){var t,n;for(t=e.length-1;t>=0;t--)n=e[t],"text"===n.type&&(n.content=n.content.replace(u,r))}function i(e){var t,n;for(t=e.length-1;t>=0;t--)n=e[t],"text"===n.type&&a.test(n.content)&&(n.content=n.content.replace(/\+-/g,"±").replace(/\.{2,}/g,"…").replace(/([?!])…/g,"$1..").replace(/([?!]){4,}/g,"$1$1$1").replace(/,{2,}/g,",").replace(/(^|[^-])---([^-]|$)/gm,"$1—$2").replace(/(^|\s)--(\s|$)/gm,"$1–$2").replace(/(^|[^-\s])--([^-\s]|$)/gm,"$1–$2"))}var a=/\+-|\.\.|\?\?\?\?|!!!!|,,|--/,s=/\((c|tm|r|p)\)/i,u=/\((c|tm|r|p)\)/gi,l={c:"©",r:"®",p:"§",tm:"™"};t.exports=function(e){var t;if(e.md.options.typographer)for(t=e.tokens.length-1;t>=0;t--)"inline"===e.tokens[t].type&&(s.test(e.tokens[t].content)&&o(e.tokens[t].children),a.test(e.tokens[t].content)&&i(e.tokens[t].children))}},{}],100:[function(e,t,n){"use strict";function r(e,t,n){return e.substr(0,t)+n+e.substr(t+1)}function o(e,t){var n,o,u,p,f,h,d,m,v,g,y,k,b,w,_,x,C,S,M,O,A;for(M=[],n=0;n<e.length;n++){for(o=e[n],d=e[n].level,C=M.length-1;C>=0&&!(M[C].level<=d);C--);if(M.length=C+1,"text"===o.type){u=o.content,f=0,h=u.length;e:for(;h>f&&(l.lastIndex=f,p=l.exec(u));)if(_=x=!0,f=p.index+1,S="'"===p[0],v=p.index-1>=0?u.charCodeAt(p.index-1):32,g=h>f?u.charCodeAt(f):32,y=s(v)||a(String.fromCharCode(v)),k=s(g)||a(String.fromCharCode(g)),b=i(v),w=i(g),w?_=!1:k&&(b||y||(_=!1)),b?x=!1:y&&(w||k||(x=!1)),34===g&&'"'===p[0]&&v>=48&&57>=v&&(x=_=!1),_&&x&&(_=!1,x=k),_||x){if(x)for(C=M.length-1;C>=0&&(m=M[C],!(M[C].level<d));C--)if(m.single===S&&M[C].level===d){m=M[C],S?(O=t.md.options.quotes[2],A=t.md.options.quotes[3]):(O=t.md.options.quotes[0],A=t.md.options.quotes[1]),o.content=r(o.content,p.index,A),e[m.token].content=r(e[m.token].content,m.pos,O),f+=A.length-1,m.token===n&&(f+=O.length-1),u=o.content,h=u.length,M.length=C;continue e}_?M.push({token:n,pos:p.index,single:S,level:d}):x&&S&&(o.content=r(o.content,p.index,c))}else S&&(o.content=r(o.content,p.index,c))}}}var i=e("../common/utils").isWhiteSpace,a=e("../common/utils").isPunctChar,s=e("../common/utils").isMdAsciiPunct,u=/['"]/,l=/['"]/g,c="’";t.exports=function(e){var t;if(e.md.options.typographer)for(t=e.tokens.length-1;t>=0;t--)"inline"===e.tokens[t].type&&u.test(e.tokens[t].content)&&o(e.tokens[t].children,e)}},{"../common/utils":69}],101:[function(e,t,n){"use strict";function r(e,t,n){this.src=e,this.env=n,this.tokens=[],this.inlineMode=!1,this.md=t}var o=e("../token");r.prototype.Token=o,t.exports=r},{"../token":114}],102:[function(e,t,n){"use strict";var r=e("../common/url_schemas"),o=/^<([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/,i=/^<([a-zA-Z.\-]{1,25}):([^<>\x00-\x20]*)>/;t.exports=function(e,t){var n,a,s,u,l,c,p=e.pos;return 60!==e.src.charCodeAt(p)?!1:(n=e.src.slice(p),n.indexOf(">")<0?!1:i.test(n)?(a=n.match(i),r.indexOf(a[1].toLowerCase())<0?!1:(u=a[0].slice(1,-1),l=e.md.normalizeLink(u),e.md.validateLink(l)?(t||(c=e.push("link_open","a",1),c.attrs=[["href",l]],c=e.push("text","",0),c.content=e.md.normalizeLinkText(u),c=e.push("link_close","a",-1)),e.pos+=a[0].length,!0):!1)):o.test(n)?(s=n.match(o),u=s[0].slice(1,-1),l=e.md.normalizeLink("mailto:"+u),e.md.validateLink(l)?(t||(c=e.push("link_open","a",1),c.attrs=[["href",l]],c.markup="autolink",c.info="auto",c=e.push("text","",0),c.content=e.md.normalizeLinkText(u),c=e.push("link_close","a",-1),c.markup="autolink",c.info="auto"),e.pos+=s[0].length,!0):!1):!1)}},{"../common/url_schemas":68}],103:[function(e,t,n){"use strict";t.exports=function(e,t){var n,r,o,i,a,s,u=e.pos,l=e.src.charCodeAt(u);if(96!==l)return!1;for(n=u,u++,r=e.posMax;r>u&&96===e.src.charCodeAt(u);)u++;for(o=e.src.slice(n,u),i=a=u;-1!==(i=e.src.indexOf("`",a));){for(a=i+1;r>a&&96===e.src.charCodeAt(a);)a++;if(a-i===o.length)return t||(s=e.push("code_inline","code",0),s.markup=o,s.content=e.src.slice(u,i).replace(/[ \n]+/g," ").trim()),e.pos=a,!0}return t||(e.pending+=o),e.pos+=o.length,!0}},{}],104:[function(e,t,n){"use strict";function r(e,t){var n,r,s,u,l,c,p,f,h,d=t,m=!0,v=!0,g=e.posMax,y=e.src.charCodeAt(t);for(n=t>0?e.src.charCodeAt(t-1):32;g>d&&e.src.charCodeAt(d)===y;)d++;return s=d-t,r=g>d?e.src.charCodeAt(d):32,p=a(n)||i(String.fromCharCode(n)),h=a(r)||i(String.fromCharCode(r)),c=o(n),f=o(r),f?m=!1:h&&(c||p||(m=!1)),c?v=!1:p&&(f||h||(v=!1)),95===y?(u=m&&(!v||p),l=v&&(!m||h)):(u=m,l=v),{can_open:u,can_close:l,delims:s}}var o=e("../common/utils").isWhiteSpace,i=e("../common/utils").isPunctChar,a=e("../common/utils").isMdAsciiPunct;t.exports=function(e,t){var n,o,i,a,s,u,l,c,p=e.posMax,f=e.pos,h=e.src.charCodeAt(f);if(95!==h&&42!==h)return!1;if(t)return!1;if(l=r(e,f),n=l.delims,!l.can_open)return e.pos+=n,e.pending+=e.src.slice(f,e.pos),!0;for(e.pos=f+n,u=[n];e.pos<p;)if(e.src.charCodeAt(e.pos)!==h)e.md.inline.skipToken(e);else{if(l=r(e,e.pos),o=l.delims,l.can_close){for(a=u.pop(),s=o;a!==s;){if(a>s){u.push(a-s);break}if(s-=a,0===u.length)break;e.pos+=a,a=u.pop()}if(0===u.length){n=a,i=!0;break}e.pos+=o;continue}l.can_open&&u.push(o),e.pos+=o}if(!i)return e.pos=f,!1;for(e.posMax=e.pos,e.pos=f+n,o=n;o>1;o-=2)c=e.push("strong_open","strong",1),c.markup=String.fromCharCode(h)+String.fromCharCode(h);for(o%2&&(c=e.push("em_open","em",1),c.markup=String.fromCharCode(h)),e.md.inline.tokenize(e),o%2&&(c=e.push("em_close","em",-1),c.markup=String.fromCharCode(h)),o=n;o>1;o-=2)c=e.push("strong_close","strong",-1),c.markup=String.fromCharCode(h)+String.fromCharCode(h);return e.pos=e.posMax+n,e.posMax=p,!0}},{"../common/utils":69}],105:[function(e,t,n){"use strict";var r=e("../common/entities"),o=e("../common/utils").has,i=e("../common/utils").isValidEntityCode,a=e("../common/utils").fromCodePoint,s=/^&#((?:x[a-f0-9]{1,8}|[0-9]{1,8}));/i,u=/^&([a-z][a-z0-9]{1,31});/i;t.exports=function(e,t){var n,l,c,p=e.pos,f=e.posMax;if(38!==e.src.charCodeAt(p))return!1;if(f>p+1)if(n=e.src.charCodeAt(p+1),35===n){if(c=e.src.slice(p).match(s))return t||(l="x"===c[1][0].toLowerCase()?parseInt(c[1].slice(1),16):parseInt(c[1],10),e.pending+=a(i(l)?l:65533)),e.pos+=c[0].length,!0}else if(c=e.src.slice(p).match(u),c&&o(r,c[1]))return t||(e.pending+=r[c[1]]),e.pos+=c[0].length,!0;return t||(e.pending+="&"),e.pos++,!0}},{"../common/entities":65,"../common/utils":69}],106:[function(e,t,n){"use strict";for(var r=[],o=0;256>o;o++)r.push(0);"\\!\"#$%&'()*+,./:;<=>?@[]^_`{|}~-".split("").forEach(function(e){r[e.charCodeAt(0)]=1}),t.exports=function(e,t){var n,o=e.pos,i=e.posMax;if(92!==e.src.charCodeAt(o))return!1;if(o++,i>o){if(n=e.src.charCodeAt(o),256>n&&0!==r[n])return t||(e.pending+=e.src[o]),e.pos+=2,!0;if(10===n){for(t||e.push("hardbreak","br",0),o++;i>o&&32===e.src.charCodeAt(o);)o++;return e.pos=o,!0}}return t||(e.pending+="\\"),e.pos++,!0}},{}],107:[function(e,t,n){"use strict";function r(e){var t=32|e;return t>=97&&122>=t}var o=e("../common/html_re").HTML_TAG_RE;t.exports=function(e,t){var n,i,a,s,u=e.pos;return e.md.options.html?(a=e.posMax,60!==e.src.charCodeAt(u)||u+2>=a?!1:(n=e.src.charCodeAt(u+1),(33===n||63===n||47===n||r(n))&&(i=e.src.slice(u).match(o))?(t||(s=e.push("html_inline","",0),s.content=e.src.slice(u,u+i[0].length)),e.pos+=i[0].length,!0):!1)):!1}},{"../common/html_re":67}],108:[function(e,t,n){"use strict";var r=e("../helpers/parse_link_label"),o=e("../helpers/parse_link_destination"),i=e("../helpers/parse_link_title"),a=e("../common/utils").normalizeReference;t.exports=function(e,t){var n,s,u,l,c,p,f,h,d,m,v,g,y="",k=e.pos,b=e.posMax;if(33!==e.src.charCodeAt(e.pos))return!1;if(91!==e.src.charCodeAt(e.pos+1))return!1;if(c=e.pos+2,l=r(e,e.pos+1,!1),0>l)return!1;if(p=l+1,b>p&&40===e.src.charCodeAt(p)){for(p++;b>p&&(s=e.src.charCodeAt(p),32===s||10===s);p++);if(p>=b)return!1;for(g=p,h=o(e.src,p,e.posMax),h.ok&&(y=e.md.normalizeLink(h.str),e.md.validateLink(y)?p=h.pos:y=""),g=p;b>p&&(s=e.src.charCodeAt(p),32===s||10===s);p++);if(h=i(e.src,p,e.posMax),b>p&&g!==p&&h.ok)for(d=h.str,p=h.pos;b>p&&(s=e.src.charCodeAt(p),32===s||10===s);p++);else d="";if(p>=b||41!==e.src.charCodeAt(p))return e.pos=k,!1;p++}else{if("undefined"==typeof e.env.references)return!1;for(;b>p&&(s=e.src.charCodeAt(p),32===s||10===s);p++);if(b>p&&91===e.src.charCodeAt(p)?(g=p+1,p=r(e,p),p>=0?u=e.src.slice(g,p++):p=l+1):p=l+1,u||(u=e.src.slice(c,l)),f=e.env.references[a(u)],!f)return e.pos=k,!1;y=f.href,d=f.title}if(!t){e.pos=c,e.posMax=l;var w=new e.md.inline.State(e.src.slice(c,l),e.md,e.env,v=[]);w.md.inline.tokenize(w),m=e.push("image","img",0),m.attrs=n=[["src",y],["alt",""]],m.children=v,d&&n.push(["title",d])}return e.pos=p,e.posMax=b,!0}},{"../common/utils":69,"../helpers/parse_link_destination":71,"../helpers/parse_link_label":72,"../helpers/parse_link_title":73}],109:[function(e,t,n){"use strict";var r=e("../helpers/parse_link_label"),o=e("../helpers/parse_link_destination"),i=e("../helpers/parse_link_title"),a=e("../common/utils").normalizeReference;t.exports=function(e,t){var n,s,u,l,c,p,f,h,d,m,v="",g=e.pos,y=e.posMax,k=e.pos;if(91!==e.src.charCodeAt(e.pos))return!1;if(c=e.pos+1,l=r(e,e.pos,!0),0>l)return!1;if(p=l+1,y>p&&40===e.src.charCodeAt(p)){for(p++;y>p&&(s=e.src.charCodeAt(p),32===s||10===s);p++);if(p>=y)return!1;for(k=p,f=o(e.src,p,e.posMax),f.ok&&(v=e.md.normalizeLink(f.str),e.md.validateLink(v)?p=f.pos:v=""),k=p;y>p&&(s=e.src.charCodeAt(p),32===s||10===s);p++);if(f=i(e.src,p,e.posMax),y>p&&k!==p&&f.ok)for(d=f.str,p=f.pos;y>p&&(s=e.src.charCodeAt(p),32===s||10===s);p++);else d="";if(p>=y||41!==e.src.charCodeAt(p))return e.pos=g,!1;p++}else{if("undefined"==typeof e.env.references)return!1;for(;y>p&&(s=e.src.charCodeAt(p),32===s||10===s);p++);if(y>p&&91===e.src.charCodeAt(p)?(k=p+1,p=r(e,p),p>=0?u=e.src.slice(k,p++):p=l+1):p=l+1,u||(u=e.src.slice(c,l)),h=e.env.references[a(u)],!h)return e.pos=g,!1;v=h.href,d=h.title}return t||(e.pos=c,e.posMax=l,m=e.push("link_open","a",1),m.attrs=n=[["href",v]],
+d&&n.push(["title",d]),e.md.inline.tokenize(e),m=e.push("link_close","a",-1)),e.pos=p,e.posMax=y,!0}},{"../common/utils":69,"../helpers/parse_link_destination":71,"../helpers/parse_link_label":72,"../helpers/parse_link_title":73}],110:[function(e,t,n){"use strict";t.exports=function(e,t){var n,r,o=e.pos;if(10!==e.src.charCodeAt(o))return!1;for(n=e.pending.length-1,r=e.posMax,t||(n>=0&&32===e.pending.charCodeAt(n)?n>=1&&32===e.pending.charCodeAt(n-1)?(e.pending=e.pending.replace(/ +$/,""),e.push("hardbreak","br",0)):(e.pending=e.pending.slice(0,-1),e.push("softbreak","br",0)):e.push("softbreak","br",0)),o++;r>o&&32===e.src.charCodeAt(o);)o++;return e.pos=o,!0}},{}],111:[function(e,t,n){"use strict";function r(e,t,n,r){this.src=e,this.env=n,this.md=t,this.tokens=r,this.pos=0,this.posMax=this.src.length,this.level=0,this.pending="",this.pendingLevel=0,this.cache={}}var o=e("../token");r.prototype.pushPending=function(){var e=new o("text","",0);return e.content=this.pending,e.level=this.pendingLevel,this.tokens.push(e),this.pending="",e},r.prototype.push=function(e,t,n){this.pending&&this.pushPending();var r=new o(e,t,n);return 0>n&&this.level--,r.level=this.level,n>0&&this.level++,this.pendingLevel=this.level,this.tokens.push(r),r},r.prototype.Token=o,t.exports=r},{"../token":114}],112:[function(e,t,n){"use strict";function r(e,t){var n,r,s,u,l,c,p,f=t,h=!0,d=!0,m=e.posMax,v=e.src.charCodeAt(t);for(n=t>0?e.src.charCodeAt(t-1):32;m>f&&e.src.charCodeAt(f)===v;)f++;return f>=m&&(h=!1),s=f-t,r=m>f?e.src.charCodeAt(f):32,l=a(n)||i(String.fromCharCode(n)),p=a(r)||i(String.fromCharCode(r)),u=o(n),c=o(r),c?h=!1:p&&(u||l||(h=!1)),u?d=!1:l&&(c||p||(d=!1)),{can_open:h,can_close:d,delims:s}}var o=e("../common/utils").isWhiteSpace,i=e("../common/utils").isPunctChar,a=e("../common/utils").isMdAsciiPunct;t.exports=function(e,t){var n,o,i,a,s,u,l,c=e.posMax,p=e.pos,f=e.src.charCodeAt(p);if(126!==f)return!1;if(t)return!1;if(u=r(e,p),n=u.delims,!u.can_open)return e.pos+=n,e.pending+=e.src.slice(p,e.pos),!0;if(s=Math.floor(n/2),0>=s)return!1;for(e.pos=p+n;e.pos<c;)if(e.src.charCodeAt(e.pos)!==f)e.md.inline.skipToken(e);else{if(u=r(e,e.pos),o=u.delims,i=Math.floor(o/2),u.can_close){if(i>=s){e.pos+=o-2,a=!0;break}s-=i,e.pos+=o;continue}u.can_open&&(s+=i),e.pos+=o}return a?(e.posMax=e.pos,e.pos=p+2,l=e.push("s_open","s",1),l.markup="~~",e.md.inline.tokenize(e),l=e.push("s_close","s",-1),l.markup="~~",e.pos=e.posMax+2,e.posMax=c,!0):(e.pos=p,!1)}},{"../common/utils":69}],113:[function(e,t,n){"use strict";function r(e){switch(e){case 10:case 33:case 35:case 36:case 37:case 38:case 42:case 43:case 45:case 58:case 60:case 61:case 62:case 64:case 91:case 92:case 93:case 94:case 95:case 96:case 123:case 125:case 126:return!0;default:return!1}}t.exports=function(e,t){for(var n=e.pos;n<e.posMax&&!r(e.src.charCodeAt(n));)n++;return n===e.pos?!1:(t||(e.pending+=e.src.slice(e.pos,n)),e.pos=n,!0)}},{}],114:[function(e,t,n){"use strict";function r(e,t,n){this.type=e,this.tag=t,this.attrs=null,this.map=null,this.nesting=n,this.level=0,this.children=null,this.content="",this.markup="",this.info="",this.meta=null,this.block=!1,this.hidden=!1}r.prototype.attrIndex=function(e){var t,n,r;if(!this.attrs)return-1;for(t=this.attrs,n=0,r=t.length;r>n;n++)if(t[n][0]===e)return n;return-1},r.prototype.attrPush=function(e){this.attrs?this.attrs.push(e):this.attrs=[e]},t.exports=r},{}],115:[function(e,t,n){"use strict";function r(e){var t,n,r=i[e];if(r)return r;for(r=i[e]=[],t=0;128>t;t++)n=String.fromCharCode(t),r.push(n);for(t=0;t<e.length;t++)n=e.charCodeAt(t),r[n]="%"+("0"+n.toString(16).toUpperCase()).slice(-2);return r}function o(e,t){var n;return"string"!=typeof t&&(t=o.defaultChars),n=r(t),e.replace(/(%[a-f0-9]{2})+/gi,function(e){var t,r,o,i,a,s,u,l="";for(t=0,r=e.length;r>t;t+=3)o=parseInt(e.slice(t+1,t+3),16),128>o?l+=n[o]:192===(224&o)&&r>t+3&&(i=parseInt(e.slice(t+4,t+6),16),128===(192&i))?(u=o<<6&1984|63&i,l+=128>u?"��":String.fromCharCode(u),t+=3):224===(240&o)&&r>t+6&&(i=parseInt(e.slice(t+4,t+6),16),a=parseInt(e.slice(t+7,t+9),16),128===(192&i)&&128===(192&a))?(u=o<<12&61440|i<<6&4032|63&a,l+=2048>u||u>=55296&&57343>=u?"���":String.fromCharCode(u),t+=6):240===(248&o)&&r>t+9&&(i=parseInt(e.slice(t+4,t+6),16),a=parseInt(e.slice(t+7,t+9),16),s=parseInt(e.slice(t+10,t+12),16),128===(192&i)&&128===(192&a)&&128===(192&s))?(u=o<<18&1835008|i<<12&258048|a<<6&4032|63&s,65536>u||u>1114111?l+="����":(u-=65536,l+=String.fromCharCode(55296+(u>>10),56320+(1023&u))),t+=9):l+="�";return l})}var i={};o.defaultChars=";/?:@&=+$,#",o.componentChars="",t.exports=o},{}],116:[function(e,t,n){"use strict";function r(e){var t,n,r=i[e];if(r)return r;for(r=i[e]=[],t=0;128>t;t++)n=String.fromCharCode(t),/^[0-9a-z]$/i.test(n)?r.push(n):r.push("%"+("0"+t.toString(16).toUpperCase()).slice(-2));for(t=0;t<e.length;t++)r[e.charCodeAt(t)]=e[t];return r}function o(e,t,n){var i,a,s,u,l,c="";for("string"!=typeof t&&(n=t,t=o.defaultChars),"undefined"==typeof n&&(n=!0),l=r(t),i=0,a=e.length;a>i;i++)if(s=e.charCodeAt(i),n&&37===s&&a>i+2&&/^[0-9a-f]{2}$/i.test(e.slice(i+1,i+3)))c+=e.slice(i,i+3),i+=2;else if(128>s)c+=l[s];else if(s>=55296&&57343>=s){if(s>=55296&&56319>=s&&a>i+1&&(u=e.charCodeAt(i+1),u>=56320&&57343>=u)){c+=encodeURIComponent(e[i]+e[i+1]),i++;continue}c+="%EF%BF%BD"}else c+=encodeURIComponent(e[i]);return c}var i={};o.defaultChars=";/?:@&=+$,-_.!~*'()#",o.componentChars="-_.!~*'()",t.exports=o},{}],117:[function(e,t,n){"use strict";t.exports=function(e){var t="";return t+=e.protocol||"",t+=e.slashes?"//":"",t+=e.auth?e.auth+"@":"",t+=e.hostname&&-1!==e.hostname.indexOf(":")?"["+e.hostname+"]":e.hostname||"",t+=e.port?":"+e.port:"",t+=e.pathname||"",t+=e.search||"",t+=e.hash||""}},{}],118:[function(e,t,n){"use strict";t.exports.encode=e("./encode"),t.exports.decode=e("./decode"),t.exports.format=e("./format"),t.exports.parse=e("./parse")},{"./decode":115,"./encode":116,"./format":117,"./parse":119}],119:[function(e,t,n){"use strict";function r(){this.protocol=null,this.slashes=null,this.auth=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.pathname=null}function o(e,t){if(e&&e instanceof r)return e;var n=new r;return n.parse(e,t),n}var i=/^([a-z0-9.+-]+:)/i,a=/:[0-9]*$/,s=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,u=["<",">",'"',"`"," ","\r","\n"," "],l=["{","}","|","\\","^","`"].concat(u),c=["'"].concat(l),p=["%","/","?",";","#"].concat(c),f=["/","?","#"],h=255,d=/^[+a-z0-9A-Z_-]{0,63}$/,m=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,v={javascript:!0,"javascript:":!0},g={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0};r.prototype.parse=function(e,t){var n,r,o,a,u,l=e;if(l=l.trim(),!t&&1===e.split("#").length){var c=s.exec(l);if(c)return this.pathname=c[1],c[2]&&(this.search=c[2]),this}var y=i.exec(l);if(y&&(y=y[0],o=y.toLowerCase(),this.protocol=y,l=l.substr(y.length)),(t||y||l.match(/^\/\/[^@\/]+@[^@\/]+/))&&(u="//"===l.substr(0,2),!u||y&&v[y]||(l=l.substr(2),this.slashes=!0)),!v[y]&&(u||y&&!g[y])){var k=-1;for(n=0;n<f.length;n++)a=l.indexOf(f[n]),-1!==a&&(-1===k||k>a)&&(k=a);var b,w;for(w=-1===k?l.lastIndexOf("@"):l.lastIndexOf("@",k),-1!==w&&(b=l.slice(0,w),l=l.slice(w+1),this.auth=b),k=-1,n=0;n<p.length;n++)a=l.indexOf(p[n]),-1!==a&&(-1===k||k>a)&&(k=a);-1===k&&(k=l.length),":"===l[k-1]&&k--;var _=l.slice(0,k);l=l.slice(k),this.parseHost(_),this.hostname=this.hostname||"";var x="["===this.hostname[0]&&"]"===this.hostname[this.hostname.length-1];if(!x){var C=this.hostname.split(/\./);for(n=0,r=C.length;r>n;n++){var S=C[n];if(S&&!S.match(d)){for(var M="",O=0,A=S.length;A>O;O++)M+=S.charCodeAt(O)>127?"x":S[O];if(!M.match(d)){var T=C.slice(0,n),E=C.slice(n+1),D=S.match(m);D&&(T.push(D[1]),E.unshift(D[2])),E.length&&(l=E.join(".")+l),this.hostname=T.join(".");break}}}}this.hostname.length>h&&(this.hostname=""),x&&(this.hostname=this.hostname.substr(1,this.hostname.length-2))}var P=l.indexOf("#");-1!==P&&(this.hash=l.substr(P),l=l.slice(0,P));var j=l.indexOf("?");return-1!==j&&(this.search=l.substr(j),l=l.slice(0,j)),l&&(this.pathname=l),g[o]&&this.hostname&&!this.pathname&&(this.pathname=""),this},r.prototype.parseHost=function(e){var t=a.exec(e);t&&(t=t[0],":"!==t&&(this.port=t.substr(1)),e=e.substr(0,e.length-t.length)),e&&(this.hostname=e)},t.exports=o},{}],120:[function(e,t,n){t.exports=/[\0-\x1F\x7F-\x9F]/},{}],121:[function(e,t,n){t.exports=/[\xAD\u0600-\u0605\u061C\u06DD\u070F\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804\uDCBD|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]/},{}],122:[function(e,t,n){t.exports=/[!-#%-\*,-\/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDE38-\uDE3D]|\uD805[\uDCC6\uDDC1-\uDDC9\uDE41-\uDE43]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F/},{}],123:[function(e,t,n){t.exports=/[ \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/},{}],124:[function(e,t,n){t.exports.Any=e("./properties/Any/regex"),t.exports.Cc=e("./categories/Cc/regex"),t.exports.Cf=e("./categories/Cf/regex"),t.exports.P=e("./categories/P/regex"),t.exports.Z=e("./categories/Z/regex")},{"./categories/Cc/regex":120,"./categories/Cf/regex":121,"./categories/P/regex":122,"./categories/Z/regex":123,"./properties/Any/regex":125}],125:[function(e,t,n){t.exports=/[\0-\uD7FF\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF]/},{}]},{},[1]);
\ No newline at end of file
diff --git a/src/main/resources/gitblit.css b/src/main/resources/gitblit.css
index 748a319..3318441 100644
--- a/src/main/resources/gitblit.css
+++ b/src/main/resources/gitblit.css
@@ -38,6 +38,19 @@
font-weight: bold;
}
+.gray {
+ color: #888;
+}
+
+.octicon-centered {
+ text-align: center;
+ width: 16px;
+}
+
+tr:hover .octicon-centered {
+ color:#eee;
+}
+
.label a.bugtraq {
font-weight: normal;
color: white;
@@ -237,14 +250,13 @@
}
.repositorynavbar {
- background-color: #fbfbfb;
+ background-color: #f8f8f8;
border-bottom: 1px solid #ccc;
margin-bottom: 10px;
}
.repositorynavbar .title {
- line-height: 32px;
- padding: 5px 0px;
+ padding: 10px 0px;
}
.repositorynavbar .repository {
@@ -782,6 +794,10 @@
td.ticket-list-state {
vertical-align: middle;
+}
+
+td.ticket-list-priority {
+ vertical-align: middle;
}
.ticket-list-details {
@@ -1334,19 +1350,6 @@
font-family: inherit;
}
-div.diff.hunk_header {
- -moz-border-bottom-colors: none;
- -moz-border-image: none;
- -moz-border-left-colors: none;
- -moz-border-right-colors: none;
- -moz-border-top-colors: none;
- border-color: #FFE0FF;
- border-style: dotted;
- border-width: 1px 0 0;
- margin-top: 2px;
- font-family: inherit;
-}
-
span.diff.hunk_info {
background-color: #FFEEFF;
color: #990099;
@@ -1358,62 +1361,242 @@
font-family: inherit;
}
-div.diff.add2 {
- background-color: #DDFFDD;
- font-family: inherit;
+.diff-cell {
+ margin: 0px;
+ padding: 0 2px;
+ border: 0;
+ border-left: 1px solid #bbb;
}
-div.diff.remove2 {
+.add2 {
+ background-color: #DDFFDD;
+}
+
+.remove2 {
background-color: #FFDDDD;
- font-family: inherit;
}
-div.diff table {
+.context2 {
+ background-color: #FEFEFE;
+}
+
+.trailingws-add {
+ background-color: #99FF99;
+}
+
+.trailingws-sub {
+ background-color: #FF9999;
+}
+
+div.diff > table {
border-radius: 0;
border-right: 1px solid #bbb;
border-bottom: 1px solid #bbb;
width: 100%;
}
-div.diff table th, div.diff table td {
- margin: 0px;
- padding: 0px;
- font-family: monospace;
- border: 0;
-}
-
-div.diff table th {
- background-color: #f0f0f0;
+.diff-line {
+ background-color: #fbfbfb;
text-align: center;
color: #999;
- padding-left: 5px;
- padding-right: 5px;
- width: 30px;
+ padding-left: 2px;
+ padding-right: 2px;
+ width: 3em; /* Font-size relative! */
+ min-width: 3em;
}
-div.diff table th.header {
- background-color: #D2C3AF;
- border-right: 0px;
- border-bottom: 1px solid #808080;
- font-family: inherit;
- font-size:0.9em;
- color: black;
- padding: 2px;
- text-align: left;
+.diff-line:before {
+ content: attr(data-lineno);
}
-div.diff table td.hunk_header {
+.diff-state {
+ background-color: #fbfbfb;
+ text-align: center;
+ color: #999;
+ padding-left: 2px;
+ padding-right: 2px;
+ width: 0.5em; /* Font-size relative! */
+}
+
+.diff-state-add:before {
+ color: green;
+ font-weight: bold;
+ content: '+';
+}
+
+.diff-state-sub:before {
+ color: red;
+ font-weight: bold;
+ content: '-';
+}
+
+.hunk_header {
background-color: #dAe2e5 !important;
+ border-left: 1px solid #bbb;
border-top: 1px solid #bac2c5;
border-bottom: 1px solid #bac2c5;
color: #555;
}
-div.diff table td {
- border-left: 1px solid #bbb;
- background-color: #fbfbfb;
+/* Image diffs. */
+
+/* Note: can't use gradients; IE < 10 doesn't support them. Use pre-created pngs with transparency instead. */
+
+/* Set on body during mouse tracking. */
+.no-select {
+ -webkit-touch-callout:none;
+ -webkit-user-select:none;
+ -khtml-user-select:none;
+ -moz-user-select:none;
+ -ms-user-select:none;
+ user-select:none;
}
+div.imgdiff-container {
+ padding: 10px;
+ background: #EEE;
+}
+
+div.imgdiff {
+ margin: 10px 20px;
+ position:relative;
+ display: inline-block;
+ /* Checkerboard background to reveal transparency. */
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAK0lEQVQoz2O8e/cuAzagpKSEVZyJgUQwqoEYwPj//3+sEvfu3RsNJfppAACQZwizxs5QrAAAAABJRU5ErkJggg==);
+ background-repeat: repeat;
+ /* Same with CSS:
+ background-color: white;
+ background-image: linear-gradient(45deg, #DDD 25%, transparent 25%, transparent 75%, #DDD 75%, #DDD), linear-gradient(45deg, #DDD 25%, transparent 25%, transparent 75%, #DDD 75%, #DDD);
+ background-size:16px 16px;
+ background-position:0 0, 8px 8px;
+ */
+}
+
+div.imgdiff-left {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 0;
+ max-width: 100%;
+ overflow: hidden;
+}
+
+img.imgdiff {
+ user-select: none;
+ border: 1px solid #0F0;
+}
+img.imgdiff-old {
+ user-select: none;
+ border: 1px solid #F00;
+}
+
+.imgdiff-opa-container {
+ display: inline-block;
+ width: 200px;
+ height: 4px;
+ margin: 12px 35px 6px 35px;
+ padding: 0;
+ position: relative;
+ border: 1px solid #888;
+ background-color: #DDD;
+}
+
+.imgdiff-opa-container:before {
+ content: '';
+ position: absolute;
+ left: -20px;
+ top: -4px;
+ width : 12px;
+ height: 12px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA7klEQVQoz13STUrDMRAF8N+02fQDhCpqNyJIvUEXXfYCXtlFsZeQLpRCpYJ01RakxM38IRgIQyZv3rx5SchVaw1c4xm36OEb7/jqcJHgPuZYYopx3h2xwwrriPgtyTzHCx5xg1ESnvJ8lR1fS8pYJvgBEwyywwlD9LGotX6W1DxNpknutkPgnGSzkgOOEzRoogY8zPx9L7VFa0Ku//lAlLTumO1PjYxuhnMT9yV93uUMw2Q+NwU/OGCL95KPskrr+o3mruCAD7xhU1LjOpkX6caoGXib4HVEXKLWKiK67/GEGe6SYJ+SNxFxqbX2/gBxKkhxx1tQIAAAAABJRU5ErkJggg==);
+ /* With CSS: background-image: radial-gradient(6px at 50% 50%, rgba(255, 255, 255, 255) 50%, rgba(255, 255, 255, 0) 6px); */
+}
+
+.imgdiff-opa-container:after {
+ content: '';
+ position: absolute;
+ right: -20px;
+ top: -4px;
+ width : 12px;
+ height: 12px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAACXBIWXMAAAsTAAALEwEAmpwYAAABlklEQVQoz02Rr45TYRTE5/z7ynebYNBIFFuLXV2FQRIgWcUDkN3bBNnbR0CQJvsGGLCbIEFgSCrgGXCw7b3fOT2ILQm/ZJIRI2YyhBObzaZm5kVmvszMs8wkJtoR0ZZUr/vLyz/DMBCdwg+PcfzUvC3cHRGBTICZUYpBVb+KyPO+73/SZrOpEccv03hYHMbRp9bk6EEJQITTzFqttZjZ59ls9kwz88K9LQ7j6IfDXsdxgrcGEEFEqJRSAAQzn7fWXmtmvnJ3tNZkHCfc3t5iahOQgJWCBKCq1FqDiDzVzHwcEYgIcndMU8N+v0dmogNQzODufLcrzxiZhAQyEwBASGSedPL/wyDaMTNEJEUEpRi6rkPXdShWoKoQkWBmENGOmWhrxWBmrZSCe7ViPp9jPp+j1opSSpoZmxmY+YOS6rVmvqi1PgEQqkruzgAgImFmXGslVb1h5u2/4x65+/uIOG+tISLu+jLDzKCqN6r6pu/7bzIMA61Wq1/L5fIjgN8ict/MHphZmNl3VX1nJm+vrvof6/Xa/gJuQtCzTqj1dQAAAABJRU5ErkJggg==);
+ /* With CSS: background-image: radial-gradient(6px at 50% 50%, #888, #888 1px, transparent 6px); */
+}
+
+.imgdiff-opa-slider {
+ position:absolute;
+ top : 0;
+ left: -5px;
+ bottom: 0;
+ right: -5px;
+ text-align: left;
+}
+
+.imgdiff-opa-handle {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ position: absolute;
+ top: -3px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAsTAAALEwEAmpwYAAABMklEQVQY022OsU7CUBRA723fpTS0NcVBJuOoQUed0MSJhOiP0IHK6g84ShoM/2HE1TjZxeCA0UF3RW2tLQ73mesgLuiZT3IOwoww7FSLYrrDzHUAMInUbblsX0RR9NJsNgEBAIIg2EjT9DDP8y2ttSsiqEhlTsW58TzvaDAYxBiGB9XJ5PkkSZJdZvZFRAEAIKImUm+et3BVqy11VFHkjTzPN2cS/a6ICDHrxWlR1N+zjz2DmddnOQVziIjJWnuaedUAAENEEP4iPweIImIYRDQmogwR9T+SVkplpVLpzrBt+9KpVEaK6BUReU6a2LY9dl33zIzj+LOx3XgUkRUQsADwSylzalnWk+M4I9/3o36/f42t/RYMT4fQ7XaX0zRpMes1ADAU0b3nuee9495DO2ib37s7ktpA7ddzAAAAAElFTkSuQmCC);
+ /* With CSS: background-image: radial-gradient(5px at 50% 50%, #444, #888, transparent 5px); */
+}
+
+.imgdiff-ovr-slider {
+ display: inline-block;
+ margin: 0;
+ padding: 0;
+ position: relative;
+ text-align: left;
+}
+
+.imgdiff-ovr-handle {
+ display: inline-block;
+ width : 1px;
+ height: 100%;
+ top: 0px;
+ background-color: #444;
+ border-right: 1px solid #FFF;
+}
+
+.imgdiff-ovr-handle:before {
+ content: '';
+ position: absolute;
+ right: -4px;
+ bottom: -5px;
+ width : 10px;
+ height: 10px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAsTAAALEwEAmpwYAAABMklEQVQY022OsU7CUBRA723fpTS0NcVBJuOoQUed0MSJhOiP0IHK6g84ShoM/2HE1TjZxeCA0UF3RW2tLQ73mesgLuiZT3IOwoww7FSLYrrDzHUAMInUbblsX0RR9NJsNgEBAIIg2EjT9DDP8y2ttSsiqEhlTsW58TzvaDAYxBiGB9XJ5PkkSZJdZvZFRAEAIKImUm+et3BVqy11VFHkjTzPN2cS/a6ICDHrxWlR1N+zjz2DmddnOQVziIjJWnuaedUAAENEEP4iPweIImIYRDQmogwR9T+SVkplpVLpzrBt+9KpVEaK6BUReU6a2LY9dl33zIzj+LOx3XgUkRUQsADwSylzalnWk+M4I9/3o36/f42t/RYMT4fQ7XaX0zRpMes1ADAU0b3nuee9495DO2ib37s7ktpA7ddzAAAAAElFTkSuQmCC);
+ /* With CSS: background-image: radial-gradient(5px at 50% 50%, #444, #888, transparent 5px); */
+}
+
+.imgdiff-ovr-handle:after {
+ content: '';
+ position: absolute;
+ right: -4px;
+ top: -5px;
+ width : 10px;
+ height: 10px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAsTAAALEwEAmpwYAAABMklEQVQY022OsU7CUBRA723fpTS0NcVBJuOoQUed0MSJhOiP0IHK6g84ShoM/2HE1TjZxeCA0UF3RW2tLQ73mesgLuiZT3IOwoww7FSLYrrDzHUAMInUbblsX0RR9NJsNgEBAIIg2EjT9DDP8y2ttSsiqEhlTsW58TzvaDAYxBiGB9XJ5PkkSZJdZvZFRAEAIKImUm+et3BVqy11VFHkjTzPN2cS/a6ICDHrxWlR1N+zjz2DmddnOQVziIjJWnuaedUAAENEEP4iPweIImIYRDQmogwR9T+SVkplpVLpzrBt+9KpVEaK6BUReU6a2LY9dl33zIzj+LOx3XgUkRUQsADwSylzalnWk+M4I9/3o36/f42t/RYMT4fQ7XaX0zRpMes1ADAU0b3nuee9495DO2ib37s7ktpA7ddzAAAAAElFTkSuQmCC);
+ /* With CSS: background-image: radial-gradient(5px at 50% 50%, #444, #888, transparent 5px); */
+}
+
+.imgdiff-link {
+ margin: 0px 4px;
+ text-decoration: none;
+ border: none;
+}
+
+.imgdiff-link > img {
+ border: 1px solid transparent; /* Avoid jumping when we change the border */
+ width: 20px;
+ height: 20px;
+ margin-bottom: 10px;
+}
+
+/* End image diffs */
+
td.changeType {
width: 15px;
}
@@ -1546,12 +1729,22 @@
}
}
+td.sha256 {
+ max-width: 20em;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
table.comments td {
padding: 4px;
line-height: 17px;
}
-table.repositories {
+table.projectlist {
+ margin-top: 10px;
+}
+
+table.repositories {
border:1px solid #ddd;
border-spacing: 0px;
width: 100%;
@@ -1735,7 +1928,7 @@
white-space: nowrap;
}
-span.sha1, span.sha1 a, span.sha1 a span, .commit_message, span.shortsha1, td.sha1 {
+span.sha1, span.sha1 a, span.sha1 a span, .commit_message, span.shortsha1, td.sha1, td.sha256 {
font-family: consolas, monospace;
font-size: 13px;
}
@@ -1751,6 +1944,12 @@
padding-right:15px;
}
+td.filestore {
+ text-align: right;
+ width:1em;
+ padding-right:15px;
+}
+
td.size {
text-align: right;
width: 8em;
@@ -1873,10 +2072,6 @@
li.L7,
li.L9 { background: #fafafa !important; }
-div.docs {
- max-width: 880px;
-}
-
div.docs ul.nav {
margin-bottom: 0px !important;
}
@@ -2079,4 +2274,121 @@
background-color: #fff;
border-color: #ece7e2;
color: #815b3a;
+}
+.severity-catastrophic {
+ color:#D51900;
+}
+.severity-catastrophic:after {
+ font-family: Helvetica,arial,freesans,clean,sans-serif ;
+ content: "●●●●●";
+ font-weight:900;
+ font-size:.45em;
+ font-variant:small-caps;
+ display:flex;
+ white-space: pre;
+}
+.severity-critical {
+ color:#D55900;
+}
+.severity-critical:after {
+ font-family: Helvetica,arial,freesans,clean,sans-serif ;
+ content: " ●●●●";
+ font-weight:900;
+ font-size:.45em;
+ font-variant:small-caps;
+ display:flex;
+ white-space: pre;
+}
+.severity-serious {
+ color:#E69F00;
+}
+.severity-serious:after {
+ font-family: Helvetica,arial,freesans,clean,sans-serif ;
+ content: " ●●●";
+ font-weight:900;
+ font-size:.45em;
+ font-variant:small-caps;
+ display:flex;
+ white-space: pre;
+}
+.severity-minor {
+ color:#009E73;
+}
+.severity-minor:after {
+ font-family: Helvetica,arial,freesans,clean,sans-serif ;
+ content: " ●●";
+ font-weight:900;
+ font-size:.45em;
+ font-variant:small-caps;
+ display:flex;
+ white-space: pre;
+}
+.severity-negligible {
+ color:#0072B2;
+}
+.severity-negligible:after {
+ font-family: Helvetica,arial,freesans,clean,sans-serif ;
+ content: " ●";
+ font-weight:900;
+ font-size:.45em;
+ font-variant:small-caps;
+ display:flex;
+ white-space: pre;
+}
+.severity-unrated {
+}
+.priority-urgent {
+ color:#D51900;
+}
+.priority-high {
+ color:#D55900;
+}
+.priority-normal {
+}
+.priority-low {
+ color:#0072B2;
+}
+
+.file-positive {
+ color:#009E73;
+}
+
+.file-negative {
+ color:#D51900;
+}
+
+.filestore-item {
+ color:#815b3a;
+}
+
+.filestore-status {
+ display: inline;
+ font-size: 1.2em;
+}
+
+table.filestore-status {
+ border:none!important;
+ border-spacing: 10px 0px;
+ border-collapse: separate;
+}
+
+.filestore-status tr td a {
+ border:none!important;
+ margin-right:1.5em!important;
+ padding:0.25em;
+}
+
+.filestore-status td a:hover, .filestore-status td a.filter-on {
+ background-color: #eee;
+ border-radius:5px;
+}
+
+.filestore-status span:nth-child(2) {
+ font-weight:800;
+ margin-left:0.25em;
+}
+
+.delete-patchset {
+ color:#D51900;
+ font-size: 1.2em;
}
\ No newline at end of file
diff --git a/src/main/resources/octicons/octicons-local.ttf b/src/main/resources/octicons/octicons-local.ttf
new file mode 100644
index 0000000..03d78cc
--- /dev/null
+++ b/src/main/resources/octicons/octicons-local.ttf
Binary files differ
diff --git a/src/main/resources/octicons/octicons.css b/src/main/resources/octicons/octicons.css
new file mode 100644
index 0000000..a5dcd15
--- /dev/null
+++ b/src/main/resources/octicons/octicons.css
@@ -0,0 +1,235 @@
+@font-face {
+ font-family: 'octicons';
+ src: url('octicons.eot?#iefix') format('embedded-opentype'),
+ url('octicons.woff') format('woff'),
+ url('octicons.ttf') format('truetype'),
+ url('octicons.svg#octicons') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+
+/*
+
+.octicon is optimized for 16px.
+.mega-octicon is optimized for 32px but can be used larger.
+
+*/
+.octicon, .mega-octicon {
+ font: normal normal normal 16px/1 octicons;
+ display: inline-block;
+ text-decoration: none;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.mega-octicon { font-size: 32px; }
+
+
+.octicon-alert:before { content: '\f02d'} /* */
+.octicon-alignment-align:before { content: '\f08a'} /* */
+.octicon-alignment-aligned-to:before { content: '\f08e'} /* */
+.octicon-alignment-unalign:before { content: '\f08b'} /* */
+.octicon-arrow-down:before { content: '\f03f'} /* */
+.octicon-arrow-left:before { content: '\f040'} /* */
+.octicon-arrow-right:before { content: '\f03e'} /* */
+.octicon-arrow-small-down:before { content: '\f0a0'} /* */
+.octicon-arrow-small-left:before { content: '\f0a1'} /* */
+.octicon-arrow-small-right:before { content: '\f071'} /* */
+.octicon-arrow-small-up:before { content: '\f09f'} /* */
+.octicon-arrow-up:before { content: '\f03d'} /* */
+.octicon-beer:before { content: '\f069'} /* */
+.octicon-book:before { content: '\f007'} /* */
+.octicon-bookmark:before { content: '\f07b'} /* */
+.octicon-briefcase:before { content: '\f0d3'} /* */
+.octicon-broadcast:before { content: '\f048'} /* */
+.octicon-browser:before { content: '\f0c5'} /* */
+.octicon-bug:before { content: '\f091'} /* */
+.octicon-calendar:before { content: '\f068'} /* */
+.octicon-check:before { content: '\f03a'} /* */
+.octicon-checklist:before { content: '\f076'} /* */
+.octicon-chevron-down:before { content: '\f0a3'} /* */
+.octicon-chevron-left:before { content: '\f0a4'} /* */
+.octicon-chevron-right:before { content: '\f078'} /* */
+.octicon-chevron-up:before { content: '\f0a2'} /* */
+.octicon-circle-slash:before { content: '\f084'} /* */
+.octicon-circuit-board:before { content: '\f0d6'} /* */
+.octicon-clippy:before { content: '\f035'} /* */
+.octicon-clock:before { content: '\f046'} /* */
+.octicon-cloud-download:before { content: '\f00b'} /* */
+.octicon-cloud-upload:before { content: '\f00c'} /* */
+.octicon-code:before { content: '\f05f'} /* */
+.octicon-color-mode:before { content: '\f065'} /* */
+.octicon-comment-add:before,
+.octicon-comment:before { content: '\f02b'} /* */
+.octicon-comment-discussion:before { content: '\f04f'} /* */
+.octicon-credit-card:before { content: '\f045'} /* */
+.octicon-dash:before { content: '\f0ca'} /* */
+.octicon-dashboard:before { content: '\f07d'} /* */
+.octicon-database:before { content: '\f096'} /* */
+.octicon-device-camera:before { content: '\f056'} /* */
+.octicon-device-camera-video:before { content: '\f057'} /* */
+.octicon-device-desktop:before { content: '\f27c'} /* */
+.octicon-device-mobile:before { content: '\f038'} /* */
+.octicon-diff:before { content: '\f04d'} /* */
+.octicon-diff-added:before { content: '\f06b'} /* */
+.octicon-diff-ignored:before { content: '\f099'} /* */
+.octicon-diff-modified:before { content: '\f06d'} /* */
+.octicon-diff-removed:before { content: '\f06c'} /* */
+.octicon-diff-renamed:before { content: '\f06e'} /* */
+.octicon-ellipsis:before { content: '\f09a'} /* */
+.octicon-eye-unwatch:before,
+.octicon-eye-watch:before,
+.octicon-eye:before { content: '\f04e'} /* */
+.octicon-file-binary:before { content: '\f094'} /* */
+.octicon-file-code:before { content: '\f010'} /* */
+.octicon-file-directory:before { content: '\f016'} /* */
+.octicon-file-media:before { content: '\f012'} /* */
+.octicon-file-pdf:before { content: '\f014'} /* */
+.octicon-file-submodule:before { content: '\f017'} /* */
+.octicon-file-symlink-directory:before { content: '\f0b1'} /* */
+.octicon-file-symlink-file:before { content: '\f0b0'} /* */
+.octicon-file-text:before { content: '\f011'} /* */
+.octicon-file-zip:before { content: '\f013'} /* */
+.octicon-flame:before { content: '\f0d2'} /* */
+.octicon-fold:before { content: '\f0cc'} /* */
+.octicon-gear:before { content: '\f02f'} /* */
+.octicon-gift:before { content: '\f042'} /* */
+.octicon-gist:before { content: '\f00e'} /* */
+.octicon-gist-secret:before { content: '\f08c'} /* */
+.octicon-git-branch-create:before,
+.octicon-git-branch-delete:before,
+.octicon-git-branch:before { content: '\f020'} /* */
+.octicon-git-commit:before { content: '\f01f'} /* */
+.octicon-git-compare:before { content: '\f0ac'} /* */
+.octicon-git-merge:before { content: '\f023'} /* */
+.octicon-git-pull-request-abandoned:before,
+.octicon-git-pull-request:before { content: '\f009'} /* */
+.octicon-globe:before { content: '\f0b6'} /* */
+.octicon-graph:before { content: '\f043'} /* */
+.octicon-heart:before { content: '\2665'} /* ♥ */
+.octicon-history:before { content: '\f07e'} /* */
+.octicon-home:before { content: '\f08d'} /* */
+.octicon-horizontal-rule:before { content: '\f070'} /* */
+.octicon-hourglass:before { content: '\f09e'} /* */
+.octicon-hubot:before { content: '\f09d'} /* */
+.octicon-inbox:before { content: '\f0cf'} /* */
+.octicon-info:before { content: '\f059'} /* */
+.octicon-issue-closed:before { content: '\f028'} /* */
+.octicon-issue-opened:before { content: '\f026'} /* */
+.octicon-issue-reopened:before { content: '\f027'} /* */
+.octicon-jersey:before { content: '\f019'} /* */
+.octicon-jump-down:before { content: '\f072'} /* */
+.octicon-jump-left:before { content: '\f0a5'} /* */
+.octicon-jump-right:before { content: '\f0a6'} /* */
+.octicon-jump-up:before { content: '\f073'} /* */
+.octicon-key:before { content: '\f049'} /* */
+.octicon-keyboard:before { content: '\f00d'} /* */
+.octicon-law:before { content: '\f0d8'} /* */
+.octicon-light-bulb:before { content: '\f000'} /* */
+.octicon-link:before { content: '\f05c'} /* */
+.octicon-link-external:before { content: '\f07f'} /* */
+.octicon-list-ordered:before { content: '\f062'} /* */
+.octicon-list-unordered:before { content: '\f061'} /* */
+.octicon-location:before { content: '\f060'} /* */
+.octicon-gist-private:before,
+.octicon-mirror-private:before,
+.octicon-git-fork-private:before,
+.octicon-lock:before { content: '\f06a'} /* */
+.octicon-logo-github:before { content: '\f092'} /* */
+.octicon-mail:before { content: '\f03b'} /* */
+.octicon-mail-read:before { content: '\f03c'} /* */
+.octicon-mail-reply:before { content: '\f051'} /* */
+.octicon-mark-github:before { content: '\f00a'} /* */
+.octicon-markdown:before { content: '\f0c9'} /* */
+.octicon-megaphone:before { content: '\f077'} /* */
+.octicon-mention:before { content: '\f0be'} /* */
+.octicon-microscope:before { content: '\f089'} /* */
+.octicon-milestone:before { content: '\f075'} /* */
+.octicon-mirror-public:before,
+.octicon-mirror:before { content: '\f024'} /* */
+.octicon-mortar-board:before { content: '\f0d7'} /* */
+.octicon-move-down:before { content: '\f0a8'} /* */
+.octicon-move-left:before { content: '\f074'} /* */
+.octicon-move-right:before { content: '\f0a9'} /* */
+.octicon-move-up:before { content: '\f0a7'} /* */
+.octicon-mute:before { content: '\f080'} /* */
+.octicon-no-newline:before { content: '\f09c'} /* */
+.octicon-octoface:before { content: '\f008'} /* */
+.octicon-organization:before { content: '\f037'} /* */
+.octicon-package:before { content: '\f0c4'} /* */
+.octicon-paintcan:before { content: '\f0d1'} /* */
+.octicon-pencil:before { content: '\f058'} /* */
+.octicon-person-add:before,
+.octicon-person-follow:before,
+.octicon-person:before { content: '\f018'} /* */
+.octicon-pin:before { content: '\f041'} /* */
+.octicon-playback-fast-forward:before { content: '\f0bd'} /* */
+.octicon-playback-pause:before { content: '\f0bb'} /* */
+.octicon-playback-play:before { content: '\f0bf'} /* */
+.octicon-playback-rewind:before { content: '\f0bc'} /* */
+.octicon-plug:before { content: '\f0d4'} /* */
+.octicon-repo-create:before,
+.octicon-gist-new:before,
+.octicon-file-directory-create:before,
+.octicon-file-add:before,
+.octicon-plus:before { content: '\f05d'} /* */
+.octicon-podium:before { content: '\f0af'} /* */
+.octicon-primitive-dot:before { content: '\f052'} /* */
+.octicon-primitive-square:before { content: '\f053'} /* */
+.octicon-pulse:before { content: '\f085'} /* */
+.octicon-puzzle:before { content: '\f0c0'} /* */
+.octicon-question:before { content: '\f02c'} /* */
+.octicon-quote:before { content: '\f063'} /* */
+.octicon-radio-tower:before { content: '\f030'} /* */
+.octicon-repo-delete:before,
+.octicon-repo:before { content: '\f001'} /* */
+.octicon-repo-clone:before { content: '\f04c'} /* */
+.octicon-repo-force-push:before { content: '\f04a'} /* */
+.octicon-gist-fork:before,
+.octicon-repo-forked:before { content: '\f002'} /* */
+.octicon-repo-pull:before { content: '\f006'} /* */
+.octicon-repo-push:before { content: '\f005'} /* */
+.octicon-rocket:before { content: '\f033'} /* */
+.octicon-rss:before { content: '\f034'} /* */
+.octicon-ruby:before { content: '\f047'} /* */
+.octicon-screen-full:before { content: '\f066'} /* */
+.octicon-screen-normal:before { content: '\f067'} /* */
+.octicon-search-save:before,
+.octicon-search:before { content: '\f02e'} /* */
+.octicon-server:before { content: '\f097'} /* */
+.octicon-settings:before { content: '\f07c'} /* */
+.octicon-log-in:before,
+.octicon-sign-in:before { content: '\f036'} /* */
+.octicon-log-out:before,
+.octicon-sign-out:before { content: '\f032'} /* */
+.octicon-split:before { content: '\f0c6'} /* */
+.octicon-squirrel:before { content: '\f0b2'} /* */
+.octicon-star-add:before,
+.octicon-star-delete:before,
+.octicon-star:before { content: '\f02a'} /* */
+.octicon-steps:before { content: '\f0c7'} /* */
+.octicon-stop:before { content: '\f08f'} /* */
+.octicon-repo-sync:before,
+.octicon-sync:before { content: '\f087'} /* */
+.octicon-tag-remove:before,
+.octicon-tag-add:before,
+.octicon-tag:before { content: '\f015'} /* */
+.octicon-telescope:before { content: '\f088'} /* */
+.octicon-terminal:before { content: '\f0c8'} /* */
+.octicon-three-bars:before { content: '\f05e'} /* */
+.octicon-tools:before { content: '\f031'} /* */
+.octicon-trashcan:before { content: '\f0d0'} /* */
+.octicon-triangle-down:before { content: '\f05b'} /* */
+.octicon-triangle-left:before { content: '\f044'} /* */
+.octicon-triangle-right:before { content: '\f05a'} /* */
+.octicon-triangle-up:before { content: '\f0aa'} /* */
+.octicon-unfold:before { content: '\f039'} /* */
+.octicon-unmute:before { content: '\f0ba'} /* */
+.octicon-versions:before { content: '\f064'} /* */
+.octicon-remove-close:before,
+.octicon-x:before { content: '\f081'} /* */
+.octicon-zap:before { content: '\26A1'} /* ⚡ */
diff --git a/src/main/resources/octicons/octicons.eot b/src/main/resources/octicons/octicons.eot
new file mode 100644
index 0000000..22881a8
--- /dev/null
+++ b/src/main/resources/octicons/octicons.eot
Binary files differ
diff --git a/src/main/resources/octicons/octicons.less b/src/main/resources/octicons/octicons.less
new file mode 100644
index 0000000..b054eb8
--- /dev/null
+++ b/src/main/resources/octicons/octicons.less
@@ -0,0 +1,233 @@
+@octicons-font-path: ".";
+@octicons-version: "a594b5fd4cae0b2afd156bca8dad8d27ac3d7594";
+
+@font-face {
+ font-family: 'octicons';
+ src: ~"url('@{octicons-font-path}/octicons.eot?#iefix&v=@{octicons-version}') format('embedded-opentype')",
+ ~"url('@{octicons-font-path}/octicons.woff?v=@{octicons-version}') format('woff')",
+ ~"url('@{octicons-font-path}/octicons.ttf?v=@{octicons-version}') format('truetype')",
+ ~"url('@{octicons-font-path}/octicons.svg?v=@{octicons-version}#octicons') format('svg')";
+ font-weight: normal;
+ font-style: normal;
+}
+
+// .octicon is optimized for 16px.
+// .mega-octicon is optimized for 32px but can be used larger.
+.octicon, .mega-octicon {
+ font: normal normal normal 16px/1 octicons;
+ display: inline-block;
+ text-decoration: none;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.mega-octicon { font-size: 32px; }
+
+.octicon-alert:before { content: '\f02d'} /* */
+.octicon-alignment-align:before { content: '\f08a'} /* */
+.octicon-alignment-aligned-to:before { content: '\f08e'} /* */
+.octicon-alignment-unalign:before { content: '\f08b'} /* */
+.octicon-arrow-down:before { content: '\f03f'} /* */
+.octicon-arrow-left:before { content: '\f040'} /* */
+.octicon-arrow-right:before { content: '\f03e'} /* */
+.octicon-arrow-small-down:before { content: '\f0a0'} /* */
+.octicon-arrow-small-left:before { content: '\f0a1'} /* */
+.octicon-arrow-small-right:before { content: '\f071'} /* */
+.octicon-arrow-small-up:before { content: '\f09f'} /* */
+.octicon-arrow-up:before { content: '\f03d'} /* */
+.octicon-beer:before { content: '\f069'} /* */
+.octicon-book:before { content: '\f007'} /* */
+.octicon-bookmark:before { content: '\f07b'} /* */
+.octicon-briefcase:before { content: '\f0d3'} /* */
+.octicon-broadcast:before { content: '\f048'} /* */
+.octicon-browser:before { content: '\f0c5'} /* */
+.octicon-bug:before { content: '\f091'} /* */
+.octicon-calendar:before { content: '\f068'} /* */
+.octicon-check:before { content: '\f03a'} /* */
+.octicon-checklist:before { content: '\f076'} /* */
+.octicon-chevron-down:before { content: '\f0a3'} /* */
+.octicon-chevron-left:before { content: '\f0a4'} /* */
+.octicon-chevron-right:before { content: '\f078'} /* */
+.octicon-chevron-up:before { content: '\f0a2'} /* */
+.octicon-circle-slash:before { content: '\f084'} /* */
+.octicon-circuit-board:before { content: '\f0d6'} /* */
+.octicon-clippy:before { content: '\f035'} /* */
+.octicon-clock:before { content: '\f046'} /* */
+.octicon-cloud-download:before { content: '\f00b'} /* */
+.octicon-cloud-upload:before { content: '\f00c'} /* */
+.octicon-code:before { content: '\f05f'} /* */
+.octicon-color-mode:before { content: '\f065'} /* */
+.octicon-comment-add:before,
+.octicon-comment:before { content: '\f02b'} /* */
+.octicon-comment-discussion:before { content: '\f04f'} /* */
+.octicon-credit-card:before { content: '\f045'} /* */
+.octicon-dash:before { content: '\f0ca'} /* */
+.octicon-dashboard:before { content: '\f07d'} /* */
+.octicon-database:before { content: '\f096'} /* */
+.octicon-device-camera:before { content: '\f056'} /* */
+.octicon-device-camera-video:before { content: '\f057'} /* */
+.octicon-device-desktop:before { content: '\f27c'} /* */
+.octicon-device-mobile:before { content: '\f038'} /* */
+.octicon-diff:before { content: '\f04d'} /* */
+.octicon-diff-added:before { content: '\f06b'} /* */
+.octicon-diff-ignored:before { content: '\f099'} /* */
+.octicon-diff-modified:before { content: '\f06d'} /* */
+.octicon-diff-removed:before { content: '\f06c'} /* */
+.octicon-diff-renamed:before { content: '\f06e'} /* */
+.octicon-ellipsis:before { content: '\f09a'} /* */
+.octicon-eye-unwatch:before,
+.octicon-eye-watch:before,
+.octicon-eye:before { content: '\f04e'} /* */
+.octicon-file-binary:before { content: '\f094'} /* */
+.octicon-file-code:before { content: '\f010'} /* */
+.octicon-file-directory:before { content: '\f016'} /* */
+.octicon-file-media:before { content: '\f012'} /* */
+.octicon-file-pdf:before { content: '\f014'} /* */
+.octicon-file-submodule:before { content: '\f017'} /* */
+.octicon-file-symlink-directory:before { content: '\f0b1'} /* */
+.octicon-file-symlink-file:before { content: '\f0b0'} /* */
+.octicon-file-text:before { content: '\f011'} /* */
+.octicon-file-zip:before { content: '\f013'} /* */
+.octicon-flame:before { content: '\f0d2'} /* */
+.octicon-fold:before { content: '\f0cc'} /* */
+.octicon-gear:before { content: '\f02f'} /* */
+.octicon-gift:before { content: '\f042'} /* */
+.octicon-gist:before { content: '\f00e'} /* */
+.octicon-gist-secret:before { content: '\f08c'} /* */
+.octicon-git-branch-create:before,
+.octicon-git-branch-delete:before,
+.octicon-git-branch:before { content: '\f020'} /* */
+.octicon-git-commit:before { content: '\f01f'} /* */
+.octicon-git-compare:before { content: '\f0ac'} /* */
+.octicon-git-merge:before { content: '\f023'} /* */
+.octicon-git-pull-request-abandoned:before,
+.octicon-git-pull-request:before { content: '\f009'} /* */
+.octicon-globe:before { content: '\f0b6'} /* */
+.octicon-graph:before { content: '\f043'} /* */
+.octicon-heart:before { content: '\2665'} /* ♥ */
+.octicon-history:before { content: '\f07e'} /* */
+.octicon-home:before { content: '\f08d'} /* */
+.octicon-horizontal-rule:before { content: '\f070'} /* */
+.octicon-hourglass:before { content: '\f09e'} /* */
+.octicon-hubot:before { content: '\f09d'} /* */
+.octicon-inbox:before { content: '\f0cf'} /* */
+.octicon-info:before { content: '\f059'} /* */
+.octicon-issue-closed:before { content: '\f028'} /* */
+.octicon-issue-opened:before { content: '\f026'} /* */
+.octicon-issue-reopened:before { content: '\f027'} /* */
+.octicon-jersey:before { content: '\f019'} /* */
+.octicon-jump-down:before { content: '\f072'} /* */
+.octicon-jump-left:before { content: '\f0a5'} /* */
+.octicon-jump-right:before { content: '\f0a6'} /* */
+.octicon-jump-up:before { content: '\f073'} /* */
+.octicon-key:before { content: '\f049'} /* */
+.octicon-keyboard:before { content: '\f00d'} /* */
+.octicon-law:before { content: '\f0d8'} /* */
+.octicon-light-bulb:before { content: '\f000'} /* */
+.octicon-link:before { content: '\f05c'} /* */
+.octicon-link-external:before { content: '\f07f'} /* */
+.octicon-list-ordered:before { content: '\f062'} /* */
+.octicon-list-unordered:before { content: '\f061'} /* */
+.octicon-location:before { content: '\f060'} /* */
+.octicon-gist-private:before,
+.octicon-mirror-private:before,
+.octicon-git-fork-private:before,
+.octicon-lock:before { content: '\f06a'} /* */
+.octicon-logo-github:before { content: '\f092'} /* */
+.octicon-mail:before { content: '\f03b'} /* */
+.octicon-mail-read:before { content: '\f03c'} /* */
+.octicon-mail-reply:before { content: '\f051'} /* */
+.octicon-mark-github:before { content: '\f00a'} /* */
+.octicon-markdown:before { content: '\f0c9'} /* */
+.octicon-megaphone:before { content: '\f077'} /* */
+.octicon-mention:before { content: '\f0be'} /* */
+.octicon-microscope:before { content: '\f089'} /* */
+.octicon-milestone:before { content: '\f075'} /* */
+.octicon-mirror-public:before,
+.octicon-mirror:before { content: '\f024'} /* */
+.octicon-mortar-board:before { content: '\f0d7'} /* */
+.octicon-move-down:before { content: '\f0a8'} /* */
+.octicon-move-left:before { content: '\f074'} /* */
+.octicon-move-right:before { content: '\f0a9'} /* */
+.octicon-move-up:before { content: '\f0a7'} /* */
+.octicon-mute:before { content: '\f080'} /* */
+.octicon-no-newline:before { content: '\f09c'} /* */
+.octicon-octoface:before { content: '\f008'} /* */
+.octicon-organization:before { content: '\f037'} /* */
+.octicon-package:before { content: '\f0c4'} /* */
+.octicon-paintcan:before { content: '\f0d1'} /* */
+.octicon-pencil:before { content: '\f058'} /* */
+.octicon-person-add:before,
+.octicon-person-follow:before,
+.octicon-person:before { content: '\f018'} /* */
+.octicon-pin:before { content: '\f041'} /* */
+.octicon-playback-fast-forward:before { content: '\f0bd'} /* */
+.octicon-playback-pause:before { content: '\f0bb'} /* */
+.octicon-playback-play:before { content: '\f0bf'} /* */
+.octicon-playback-rewind:before { content: '\f0bc'} /* */
+.octicon-plug:before { content: '\f0d4'} /* */
+.octicon-repo-create:before,
+.octicon-gist-new:before,
+.octicon-file-directory-create:before,
+.octicon-file-add:before,
+.octicon-plus:before { content: '\f05d'} /* */
+.octicon-podium:before { content: '\f0af'} /* */
+.octicon-primitive-dot:before { content: '\f052'} /* */
+.octicon-primitive-square:before { content: '\f053'} /* */
+.octicon-pulse:before { content: '\f085'} /* */
+.octicon-puzzle:before { content: '\f0c0'} /* */
+.octicon-question:before { content: '\f02c'} /* */
+.octicon-quote:before { content: '\f063'} /* */
+.octicon-radio-tower:before { content: '\f030'} /* */
+.octicon-repo-delete:before,
+.octicon-repo:before { content: '\f001'} /* */
+.octicon-repo-clone:before { content: '\f04c'} /* */
+.octicon-repo-force-push:before { content: '\f04a'} /* */
+.octicon-gist-fork:before,
+.octicon-repo-forked:before { content: '\f002'} /* */
+.octicon-repo-pull:before { content: '\f006'} /* */
+.octicon-repo-push:before { content: '\f005'} /* */
+.octicon-rocket:before { content: '\f033'} /* */
+.octicon-rss:before { content: '\f034'} /* */
+.octicon-ruby:before { content: '\f047'} /* */
+.octicon-screen-full:before { content: '\f066'} /* */
+.octicon-screen-normal:before { content: '\f067'} /* */
+.octicon-search-save:before,
+.octicon-search:before { content: '\f02e'} /* */
+.octicon-server:before { content: '\f097'} /* */
+.octicon-settings:before { content: '\f07c'} /* */
+.octicon-log-in:before,
+.octicon-sign-in:before { content: '\f036'} /* */
+.octicon-log-out:before,
+.octicon-sign-out:before { content: '\f032'} /* */
+.octicon-split:before { content: '\f0c6'} /* */
+.octicon-squirrel:before { content: '\f0b2'} /* */
+.octicon-star-add:before,
+.octicon-star-delete:before,
+.octicon-star:before { content: '\f02a'} /* */
+.octicon-steps:before { content: '\f0c7'} /* */
+.octicon-stop:before { content: '\f08f'} /* */
+.octicon-repo-sync:before,
+.octicon-sync:before { content: '\f087'} /* */
+.octicon-tag-remove:before,
+.octicon-tag-add:before,
+.octicon-tag:before { content: '\f015'} /* */
+.octicon-telescope:before { content: '\f088'} /* */
+.octicon-terminal:before { content: '\f0c8'} /* */
+.octicon-three-bars:before { content: '\f05e'} /* */
+.octicon-tools:before { content: '\f031'} /* */
+.octicon-trashcan:before { content: '\f0d0'} /* */
+.octicon-triangle-down:before { content: '\f05b'} /* */
+.octicon-triangle-left:before { content: '\f044'} /* */
+.octicon-triangle-right:before { content: '\f05a'} /* */
+.octicon-triangle-up:before { content: '\f0aa'} /* */
+.octicon-unfold:before { content: '\f039'} /* */
+.octicon-unmute:before { content: '\f0ba'} /* */
+.octicon-versions:before { content: '\f064'} /* */
+.octicon-remove-close:before,
+.octicon-x:before { content: '\f081'} /* */
+.octicon-zap:before { content: '\26A1'} /* ⚡ */
diff --git a/src/main/resources/octicons/octicons.svg b/src/main/resources/octicons/octicons.svg
new file mode 100644
index 0000000..ea3e0f1
--- /dev/null
+++ b/src/main/resources/octicons/octicons.svg
@@ -0,0 +1,198 @@
+<?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>
+(c) 2012-2014 GitHub
+
+When using the GitHub logos, be sure to follow the GitHub logo guidelines (https://github.com/logos)
+
+Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL)
+Applies to all font files
+
+Code License: MIT (http://choosealicense.com/licenses/mit/)
+Applies to all other files
+</metadata>
+<defs>
+<font id="octicons" horiz-adv-x="1024" >
+<font-face font-family="octicons" font-weight="400" font-stretch="normal" units-per-em="1024" ascent="832" descent="-192" />
+<missing-glyph d="M512 832C229.25 832 0 602.75 0 320c0-226.25 146.688-418.125 350.156-485.812 25.594-4.688 34.938 11.125 34.938 24.625 0 12.188-0.469 52.562-0.719 95.312C242-76.81200000000001 211.906 14.5 211.906 14.5c-23.312 59.125-56.844 74.875-56.844 74.875-46.531 31.75 3.53 31.125 3.53 31.125 51.406-3.562 78.47-52.75 78.47-52.75 45.688-78.25 119.875-55.625 149-42.5 4.654 33 17.904 55.625 32.5 68.375C304.906 106.56200000000001 185.344 150.5 185.344 346.688c0 55.938 19.969 101.562 52.656 137.406-5.219 13-22.844 65.094 5.062 135.562 0 0 42.938 13.75 140.812-52.5 40.812 11.406 84.594 17.031 128.125 17.219 43.5-0.188 87.312-5.875 128.188-17.281 97.688 66.312 140.688 52.5 140.688 52.5 28-70.531 10.375-122.562 5.125-135.5 32.812-35.844 52.625-81.469 52.625-137.406 0-196.688-119.75-240-233.812-252.688 18.438-15.875 34.75-47 34.75-94.75 0-68.438-0.688-123.625-0.688-140.5 0-13.625 9.312-29.562 35.25-24.562C877.438-98 1024 93.875 1024 320 1024 602.75 794.75 832 512 832z" horiz-adv-x="1024" />
+<glyph glyph-name="alert" unicode="" d="M1005.854 31.753000000000043l-438.286 767C556.173 818.694 534.967 831 512 831s-44.173-12.306-55.567-32.247l-438.286-767c-11.319-19.809-11.238-44.144 0.213-63.876C29.811-51.85500000000002 50.899-64 73.714-64h876.572c22.814 0 43.903 12.145 55.354 31.877S1017.173 11.94399999999996 1005.854 31.753000000000043zM576 64H448V192h128V64zM576 256H448V512h128V256z" horiz-adv-x="1024" />
+<glyph glyph-name="alignment-align" unicode="" d="M192 768C85.938 768 0 682.062 0 576s85.938-192 192-192c106.062 0 192 85.938 192 192S298.062 768 192 768zM672 224l160 160H384v-448l160 160 288-288 128 128L672 224z" horiz-adv-x="960" />
+<glyph glyph-name="alignment-aligned-to" unicode="" d="M384 256l128 128 288-288 160 160v-448H512l160 160L384 256zM192 384C85.938 384 0 469.938 0 576S85.938 768 192 768c106.062 0 192-85.938 192-192S298.062 384 192 384z" horiz-adv-x="960" />
+<glyph glyph-name="alignment-unalign" unicode="" d="M512 640L384 512 128 768 0 640l256-256L128 256l64-64 384 384L512 640zM640 256l128 128-64 64L320 64l64-64 128 128 256-256 128 128L640 256z" horiz-adv-x="896" />
+<glyph glyph-name="arrow-down" unicode="" d="M448 384V640H192v-256H0l320-384 320 384H448z" horiz-adv-x="640" />
+<glyph glyph-name="arrow-left" unicode="" d="M384 448V640L0 320l384-320V192h256V448H384z" horiz-adv-x="640" />
+<glyph glyph-name="arrow-right" unicode="" d="M640 320L256 640v-192H0v-256h256v-192L640 320z" horiz-adv-x="640" />
+<glyph glyph-name="arrow-small-down" unicode="" d="M256 384V512H128v-128H0l192-256 192 256H256z" horiz-adv-x="384" />
+<glyph glyph-name="arrow-small-left" unicode="" d="M256 384V512L0 320l256-192V256h128V384H256z" horiz-adv-x="384" />
+<glyph glyph-name="arrow-small-right" unicode="" d="M384 320L128 512v-128H0v-128h128v-128L384 320z" horiz-adv-x="384" />
+<glyph glyph-name="arrow-small-up" unicode="" d="M192 512L0 256h128v-128h128V256h128L192 512z" horiz-adv-x="384" />
+<glyph glyph-name="arrow-up" unicode="" d="M320 640L0 256h192v-256h256V256h192L320 640z" horiz-adv-x="640" />
+<glyph glyph-name="beer" unicode="" d="M896 576c-31 0-192 0-192 0v128c0 71-158 128-352 128s-352-57-352-128v-768c0-71 158-128 352-128s352 57 352 128v128s160 0 192 0 64 30 64 64 0 350 0 384-29 64-64 64z m-704-576h-64v512h64v-512z m192-64h-64v512h64v-512z m192 64h-64v512h64v-512z m-224 640c-124 0-224 29-224 64s100 64 224 64 224-29 224-64-100-64-224-64z m480-448h-128v256h128v-256z" horiz-adv-x="1024" />
+<glyph glyph-name="book" unicode="" d="M768 256h-128c-34 0-64-32-64-64h256c0 34-32 64-64 64z m-55 416c-167 0-209-32-233-56-24 24-66 56-233 56s-247-46-247-78v-586c29 16 119 48 214 56 115 9 234-9 234-32 0-16 8-31 31-32 0 0 0 0 1 0 0 0 0 0 1 0 23 1 31 16 31 32 0 23 119 41 234 32 94-7 185-40 214-56v586c0 32-80 78-247 78z m-265-572c-30 16-103 28-192 28s-170-12-192-27c0 0 0 411 0 443s64 59 192 59 192-27 192-59 0-444 0-444z m448 1c-22 15-103 27-192 27s-162-12-192-28c0 0 0 412 0 444s64 59 192 59 192-27 192-59 0-443 0-443z m-128 283h-128c-34 0-64-32-64-64h256c0 34-32 64-64 64z m0 128h-128c-34 0-64-32-64-64h256c0 34-32 64-64 64z m-448-128h-128c-32 0-64-30-64-64h256c0 32-30 64-64 64z m0-128h-128c-32 0-64-30-64-64h256c0 32-30 64-64 64z m0 256h-128c-32 0-64-30-64-64h256c0 32-30 64-64 64z" horiz-adv-x="1024" />
+<glyph glyph-name="bookmark" unicode="" d="M0 704v-768l192 128 192-128V704H0zM316.25 507.25l-71.875-51.938 27.188-83.406c2.75-8.375-0.688-11.062-7.562-6.594l-72 52.094-72-52.031c-6.844-4.469-10.312-1.781-7.562 6.594l27.219 83.406L67.783 507.25c-6.469 5.125-5 9.219 3.906 9.219l88 0.125 27.125 83.094c2.812 8.812 7.562 8.812 10.375 0l27.188-83.094 87.938-0.125C321.25 516.469 322.688 512.375 316.25 507.25z" horiz-adv-x="384" />
+<glyph glyph-name="briefcase" unicode="" d="M896 640H640v66c0 34.2-27.8 62-62 62H446c-34.2 0-62-27.8-62-62v-66H128c-35.3 0-64-28.7-64-64v-512c0-35.3 28.7-64 64-64h768c35.3 0 64 28.7 64 64V576C960 611.3 931.3 640 896 640zM448 688c0 8.8 7.2 16 16 16h96c8.8 0 16-7.2 16-16v-48H448V688zM896 320H576v-64H448v64H128V576h64v-192h640V576h64V320z" horiz-adv-x="1024" />
+<glyph glyph-name="broadcast" unicode="" d="M448 640c142 0 256-115 256-256 0-69-28-132-72-178l-16-93c91 56 152 156 152 271 0 177-143 320-320 320s-320-143-320-320c0-115 61-215 152-271l-16 93c-45 46-72 109-72 178 0 142 114 256 256 256z m-64-320c-36 0-64-29-64-64v-128c0-36 30-64 64-64v-256h128v256c34 0 64 28 64 64v128c0 35-28 64-64 64s-64 0-64 0-28 0-64 0z m192 128c0 71-57 128-128 128s-128-57-128-128 57-128 128-128 128 57 128 128z m-128 384c-247 0-448-201-448-448 0-197 128-363 305-423l-12 72c-135 60-229 194-229 351 0 212 172 384 384 384s384-172 384-384c0-157-94-291-229-351l-12-72c177 60 305 225 305 423 0 247-201 448-448 448z" horiz-adv-x="896" />
+<glyph glyph-name="browser" unicode="" d="M320 640h64v-64h-64V640zM192 640h64v-64h-64V640zM64 640h64v-64H64V640zM832 0H64V512h768V0zM832 576H448v64h384V576zM896 640c0 35.35-28.65 64-64 64H64c-35.35 0-64-28.65-64-64v-640c0-35.35 28.65-64 64-64h768c35.35 0 64 28.65 64 64V640z" horiz-adv-x="896" />
+<glyph glyph-name="bug" unicode="" d="M243.621 675.469C190.747 618.688 205.34 528 205.34 528s53.968-64 160-64c106.031 0 160.031 64 160.031 64s14.375 89.469-37.375 146.312c32.375 18.031 51.438 44.094 43.562 61.812-8.938 19.969-48.375 21.75-88.25 3.969-14.812-6.594-27.438-14.969-37.25-23.875-12.438 2.25-25.625 3.781-40.72 3.781-14.061 0-26.561-1.344-38.344-3.25-9.656 8.75-22.062 16.875-36.531 23.344-39.875 17.719-79.375 15.938-88.25-3.969C194.465 718.781 212.497 693.438 243.621 675.469zM644.746 262.25c-8.25 1.75-16.125 2.75-23.75 3.5 0 2.125 0.375 4.125 0.375 6.312 0 33.594-4.75 65.654-12.438 96.125 16.438-1.406 37.375 2.375 58.562 11.779 39.875 17.781 65 48.375 56.125 68.219-8.875 19.969-48.375 21.75-88.25 3.969-18.625-8.312-33.812-19.469-44-30.906-7.75 18.25-16.5 35.781-26.812 51.719-30.188-25.156-87.312-62.719-167.062-71.062v-321.781c0 0-0.25-32-32.031-32-31.75 0-32 32-32 32V401.781c-79.811 8.344-136.968 45.969-167.093 71.062-9.875-15.312-18.375-32-25.938-49.344-10.281 10.625-24.625 20.844-41.969 28.594-39.875 17.719-79.375 15.938-88.25-3.969-8.906-19.906 16.25-50.438 56.125-68.219 19.844-8.846 39.531-12.812 55.469-12.096-7.656-30.404-12.469-62.344-12.469-95.812 0-2.188 0.375-4.25 0.438-6.5-6.719-0.75-13.688-1.75-20.781-3.25-51.969-10.75-91.781-37.625-88.844-59.812 2.938-22.312 47.5-31.5 99.594-20.688 6.781 1.375 13.438 3.125 19.781 5.062C128.684 146 143.34 108.125 163.622 75.5c-12.031-6.062-24.531-15-36.031-26.625C95.715 17 82.779-21.75 98.715-37.68799999999999c15.938-15.937 54.656-3 86.531 28.812 9.344 9.375 16.844 19.25 22.656 29C251.434-22.5 305.965-48 365.465-48c60.343 0 115.781 26.25 159.531 69.938 5.875-10.312 13.75-20.812 23.625-30.688 31.812-31.875 70.625-44.812 86.562-28.875s3 54.625-28.875 86.5c-12.312 12.375-25.688 21.75-38.438 27.938 20.125 32.5 34.625 70.375 43.688 111.062 7.188-2.25 14.688-4.375 22.562-6.062 52.061-10.812 96.625-1.562 99.625 20.688C736.558 224.625 696.746 251.5 644.746 262.25z" horiz-adv-x="733.886" />
+<glyph glyph-name="calendar" unicode="" d="M704 320h-64v-128h64V320zM576 320h-64v-128h64V320zM704 512h-64v-128h64V512zM832 320h-64v-128h64V320zM576 128h-64v-128h64V128zM768 832h-64v-128h64V832zM256 832h-64v-128h64V832zM832 512h-64v-128h64V512zM576 512h-64v-128h64V512zM320 128h-64v-128h64V128zM192 320h-64v-128h64V320zM320 320h-64v-128h64V320zM832 768v-128H640V768H320v-128H128V768H0v-896h960V768H832zM896-64H64V576h832V-64zM192 128h-64v-128h64V128zM448 512h-64v-128h64V512zM448 128h-64v-128h64V128zM320 512h-64v-128h64V512zM448 320h-64v-128h64V320zM704 128h-64v-128h64V128z" horiz-adv-x="1024" />
+<glyph glyph-name="check" unicode="" d="M640 640L256 256 128 384 0 256l256-256 512 512L640 640z" horiz-adv-x="768" />
+<glyph glyph-name="checklist" unicode="" d="M760.688 315.78099999999995l-49.812 49.656c-6.438 6.529-16.938 6.594-23.375 0L582.5 260.5 462.375 140.125l-93.031 93.125c-6.531 6.562-17.031 6.562-23.5 0l-49.719-49.688c-6.531-6.562-6.531-17.062 0-23.562l104.781-104.875 17.969-17.875 31.688-31.812c6.562-6.562 17.188-6.562 23.562 0l49.625 49.688L760.625 292.22C767.25 298.688 767.25 309.188 760.688 315.78099999999995zM228.469 251.188L278.156 301c42.469 42.375 116.344 42.438 158.781-0.062l25.312-25.312L576 384V704H0v-704h320l-91.531 92.125C184.688 136.062 184.688 207.375 228.469 251.188zM192 640h320v-64H192V640zM192 512h320v-64H192V512zM128 320H64v64h64V320zM128 448H64v64h64V448zM128 576H64v64h64V576zM192 384h64v-64h-64V384z" horiz-adv-x="765.602" />
+<glyph glyph-name="chevron-down" unicode="" d="M512 512L320 320 128 512 0 384l320-320 320 320L512 512z" horiz-adv-x="640" />
+<glyph glyph-name="chevron-left" unicode="" d="M448 512L320 640 0 320l320-320 128 128L256 320 448 512z" horiz-adv-x="448" />
+<glyph glyph-name="chevron-right" unicode="" d="M128 640L0 512l192-192L0 128l128-128 320 320L128 640z" horiz-adv-x="448" />
+<glyph glyph-name="chevron-up" unicode="" d="M320 576L0 256l128-128 192 192 192-192 128 128L320 576z" horiz-adv-x="640" />
+<glyph glyph-name="circle-slash" unicode="" d="M320 640C143.219 640 0 496.781 0 320c0-176.75 143.219-320 320-320 176.75 0 320 143.25 320 320C640 496.781 496.75 640 320 640zM320 512c27.656 0 53.688-6.094 77.438-16.562L144.562 242.562C134.094 266.312 128 292.34400000000005 128 320 128 426 213.938 512 320 512zM320 128c-28.031 0-54.531 6.375-78.594 17.125l253.906 252.5C505.875 373.812 512 347.719 512 320 512 213.938 426.062 128 320 128z" horiz-adv-x="640" />
+<glyph glyph-name="circuit-board" unicode="" d="M320 576c35.346 0 64-28.654 64-64 0-35.346-28.654-64-64-64s-64 28.654-64 64C256 547.346 284.654 576 320 576zM960 64c0-106.039-85.961-192-192-192H320l192 192h81.128c22.132-38.258 63.494-64 110.872-64 70.692 0 128 57.308 128 128s-57.308 128-128 128c-47.377 0-88.74-25.742-110.872-64H448L156.044-99.95600000000002C100.845-66.23199999999997 64-5.419999999999959 64 64V576c0 106.039 85.961 192 192 192v-145.128C217.742 600.74 192 559.377 192 512c0-70.692 57.308-128 128-128 47.276 0 88.56 25.633 110.727 63.756l162.416 0.219C615.279 409.731 656.633 384 704 384c70.692 0 128 57.308 128 128s-57.308 128-128 128c-47.388 0-88.758-25.753-110.887-64.025l-162.097-0.219c-11.246 19.54-27.503 35.828-47.016 47.116V768h384c106.039 0 192-85.961 192-192V64zM640 128c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64S640 92.654 640 128zM640 512c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64S640 476.654 640 512z" horiz-adv-x="1024" />
+<glyph glyph-name="clippy" unicode="" d="M704-64h-640v576h640v-192h64v320c0 35-29 64-64 64h-192c0 71-57 128-128 128s-128-57-128-128h-192c-35 0-64-29-64-64v-704c0-35 29-64 64-64h640c35 0 64 29 64 64v128h-64v-128z m-512 704c29 0 29 0 64 0s64 29 64 64 29 64 64 64 64-29 64-64 32-64 64-64 33 0 64 0 64-29 64-64h-512c0 39 28 64 64 64z m-64-512h128v64h-128v-64z m448 128v128l-256-192 256-192v128h320v128h-320z m-448-256h192v64h-192v-64z m320 448h-320v-64h320v64z m-192-128h-128v-64h128v64z" horiz-adv-x="896" />
+<glyph glyph-name="clock" unicode="" d="M384 256h256l64 64-64 64H512V576l-64 64-64-64V256zM448 768C200.562 768 0 567.438 0 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448C896 567.438 695.438 768 448 768zM448 0c-176.25 0-320 143.75-320 320 0 175.938 144.188 319.5 320 320 175.812-0.5 320-144.062 320-320C768 143.75 624.25 0 448 0z" horiz-adv-x="896" />
+<glyph glyph-name="cloud-download" unicode="" d="M832 512c-8.75 0-17.125-1.406-25.625-2.562C757.625 623.75 644.125 704 512 704c-132.156 0-245.562-80.25-294.406-194.562C209.156 510.594 200.781 512 192 512 85.938 512 0 426.062 0 320s85.938-192 192-192c20.531 0 39.875 4.25 58.375 10.375C284.469 100.625 331.312 75.25 384 67.5v65.25c-49.844 10.375-91.594 42.812-112.625 87.875C249.531 203 222.219 192 192 192c-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 25.281 0 48.625-7.562 68.406-20.094C281.344 548.219 385.594 640 512 640c126.5 0 229.75-92.219 250.5-212.75 20 13 43.875 20.75 69.5 20.75 70.625 0 128-57.344 128-128 0-70.625-57.375-128-128-128-10.25 0-20 1.5-29.625 3.75C773.438 154.875 725.938 128 672 128c-11.062 0-21.625 1.625-32 4v-64.938c10.438-1.688 21.062-3.062 32-3.062 61.188 0 116.5 24.625 156.938 64.438C830 128.375 830.875 128 832 128c106.062 0 192 85.938 192 192S938.062 512 832 512zM576 320H448v-320H320l192-192 192 192H576V320z" horiz-adv-x="1024" />
+<glyph glyph-name="cloud-upload" unicode="" d="M512 448L320 256h128v-320h128V256h128L512 448zM832 512c-8.75 0-17.125-1.406-25.625-2.562C757.625 623.812 644.125 704 512 704c-132.156 0-245.562-80.188-294.406-194.562C209.156 510.594 200.781 512 192 512 85.938 512 0 426 0 320c0-106.062 85.938-192 192-192 20.531 0 39.875 4.25 58.375 10.438C284.469 100.625 331.312 75.25 384 67.5v65.25c-49.844 10.375-91.594 42.812-112.625 87.75C249.531 203 222.219 192 192 192c-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 25.281 0 48.625-7.562 68.406-20.156C281.344 548.219 385.594 640 512 640c126.5 0 229.75-92.219 250.5-212.75 20 13 43.875 20.75 69.5 20.75 70.625 0 128-57.344 128-128 0-70.625-57.375-128-128-128-10.25 0-20 1.5-29.625 3.75C773.438 154.875 725.938 128 672 128c-11.062 0-21.625 1.625-32 4v-64.938c10.438-1.688 21.062-3.062 32-3.062 61.188 0 116.5 24.688 157 64.438 1 0 1.875-0.438 3-0.438 106.062 0 192 85.938 192 192C1024 426 938.062 512 832 512z" horiz-adv-x="1024" />
+<glyph glyph-name="code" unicode="" d="M608 640l-96-96 224-224L512 96l96-96 288 320L608 640zM288 640L0 320l288-320 96 96L160 320l224 224L288 640z" horiz-adv-x="896" />
+<glyph glyph-name="color-mode" unicode="" d="M0 704v-768h768V704H0zM64 0V640h640L64 0z" horiz-adv-x="768" />
+<glyph glyph-name="comment" unicode="" d="M768 704H128C66 704 0 640 0 576v-384c0-128 128-128 128-128h64v-256l256 256c0 0 258 0 320 0s128 68 128 128V576C896 638 832 704 768 704z" horiz-adv-x="896" />
+<glyph glyph-name="comment-discussion" unicode="" d="M256 320c0 64 0 192 0 192s-160 0-192 0-64-32-64-64 0-288 0-320 32-64 64-64 64 0 64 0v-192l194 192s162 0 192 0 62 32 62 64 0 64 0 64-128 0-192 0-128 64-128 128z m576 384c-32 0-416 0-448 0s-64-32-64-64 0-288 0-320 32-64 64-64 190 0 190 0l194-192v192s32 0 64 0 64 32 64 64 0 288 0 320-32 64-64 64z" horiz-adv-x="896" />
+<glyph glyph-name="credit-card" unicode="" d="M128 128h128v64h-128v-64z m192 0h128v64h-128v-64z m64 192h-256v-64h256v64z m-128 64h64l128 128h-64l-128-128z m192-128h192v64h-192v-64z m512 384c-32 0-864 0-896 0s-64-32-64-64 0-480 0-512 32-64 64-64 864 0 896 0 64 32 64 64 0 480 0 512-32 64-64 64z m0-256v-288s0-32-32-32h-832c-32 0-32 32-32 32v288h64l128 128h-192v32s0 32 32 32h832c32 0 32-32 32-32v-32h-384l-128-128h512z" horiz-adv-x="1024" />
+<glyph glyph-name="dash" unicode="" d="M0 384v-128h512V384H0z" horiz-adv-x="512" />
+<glyph glyph-name="dashboard" unicode="" d="M416 367.5c-61.562 0-111.5-49.938-111.5-111.5S354.438 144.5 416 144.5 527.5 194.438 527.5 256c0 8.5-1.125 16.75-3 24.688C606.125 375.625 732.5 523.656 800 608c23.125 28.875-2.312 56.188-32 32-85.188-69.375-232.312-194.688-326.906-275.594C433.031 366.281 424.625 367.5 416 367.5zM447.875 576.125c0 17.656-14.344 32-32 32s-32-14.344-32-32 14.344-32 32-32S447.875 558.469 447.875 576.125zM639.875 320.125c0-17.656 14.375-32 32-32s32 14.344 32 32-14.375 32-32 32S639.875 337.781 639.875 320.125zM287.875 576.125c-17.656 0-32-14.344-32-32s14.344-32 32-32 32 14.344 32 32S305.531 576.125 287.875 576.125zM223.875 448.125c0 17.656-14.344 32-32 32s-32-14.344-32-32 14.344-32 32-32S223.875 430.469 223.875 448.125zM127.875 320.125c0-17.656 14.344-32 32-32s32 14.344 32 32-14.344 32-32 32S127.875 337.781 127.875 320.125zM575.875 544.125c0 17.656-14.375 32-32 32s-32-14.344-32-32 14.375-32 32-32S575.875 526.469 575.875 544.125zM792.875 495.312l-68.75-89.938C731.625 378.188 736 349.625 736 320c0-176.75-143.312-320-320-320S96 143.25 96 320c0 176.688 143.312 320 320 320 65.875 0 127-19.969 177.875-54.094l79.25 60.625C602.375 702.406 513.25 736 416 736 186.25 736 0 549.75 0 320s186.25-416 416-416 416 186.25 416 416C832 382.719 817.75 442 792.875 495.312z" horiz-adv-x="832" />
+<glyph glyph-name="database" unicode="" d="M384-128C171.969-128 0-70.625 0 0c0 38.625 0 80.875 0 128 0 11.125 5.562 21.688 13.562 32C56.375 104.875 205.25 64 384 64s327.625 40.875 370.438 96c8-10.312 13.562-20.875 13.562-32 0-37.062 0-76.375 0-128C768-70.625 596-128 384-128zM384 128C171.969 128 0 185.375 0 256c0 38.656 0 80.844 0 128 0 6.781 2.562 13.375 6 19.906l0 0C7.938 408 10.5 412.031 13.562 416 56.375 360.906 205.25 320 384 320s327.625 40.906 370.438 96c3.062-3.969 5.625-8 7.562-12.094l0 0c3.438-6.531 6-13.125 6-19.906 0-37.062 0-76.344 0-128C768 185.375 596 128 384 128zM384 384C171.969 384 0 441.344 0 512c0 20.219 0 41.594 0 64 0 20.344 0 41.469 0 64C0 710.656 171.969 768 384 768c212 0 384-57.344 384-128 0-19.969 0-41.156 0-64 0-19.594 0-40.25 0-64C768 441.344 596 384 384 384zM384 704c-141.375 0-256-28.594-256-64s114.625-64 256-64 256 28.594 256 64S525.375 704 384 704z" horiz-adv-x="768" />
+<glyph glyph-name="device-camera" unicode="" d="M512 447.999c-70.691 0-127.999-57.308-127.999-127.999S441.309 192.00099999999998 512 192.00099999999998c5.713 0 11.337 0.38 16.852 1.105-46.344 7.058-81.851 47.079-81.851 95.394 0 53.295 43.204 96.499 96.499 96.499 48.314 0 88.336-35.507 95.394-81.851 0.726 5.515 1.105 11.139 1.105 16.852C639.999 390.691 582.691 447.999 512 447.999zM896 576H767.999L640 704H384L255.999 576H128c-35.348 0-64-28.652-64-64v-448c0-35.347 28.652-64 64-64h768c35.347 0 64 28.653 64 64V512C960 547.348 931.347 576 896 576zM416 640h192l64-64H352L416 640zM160.143 64C142.391 64 128 78.39099999999996 128 96.14300000000003V384h64v64h-64v31.857C128 497.609 142.391 512 160.143 512h182.526c-3.98-3.518-7.881-7.174-11.688-10.98-99.974-99.975-99.974-262.064 0-362.039l74.98-74.98H160.143zM512 128.00099999999998c-106.038 0-191.999 85.961-191.999 191.999S405.962 511.999 512 511.999 703.999 426.038 703.999 320 618.038 128.00099999999998 512 128.00099999999998zM832 352L681.327 512H832V352z" horiz-adv-x="1024" />
+<glyph glyph-name="device-camera-video" unicode="" d="M576 640c-35.347 0-64-28.653-64-64s28.653-64 64-64 64 28.653 64 64S611.347 640 576 640zM896 448L768 320v64c0 30.625-21.515 56.21-50.25 62.503C748.958 480.646 768 526.097 768 575.998 768 682.038 682.039 768 576 768c-101.123 0-183.986-78.178-191.45-177.393C350.516 621.306 305.442 640 256 640c-106.038 0-192-85.962-192-192.002C64 341.961 149.962 256 256 256h-64v-128h64v-128c0-35.347 28.653-64 64-64h384c35.347 0 64 28.653 64 64v64l128-128h64V448H896zM256 512c-35.347 0-64-28.653-64-64s28.653-64 64-64v-64c-70.692 0-128 57.308-128 127.999C128 518.692 185.308 576 256 576s128-57.307 128-128h-64C320 483.347 291.347 512 256 512zM576 128H448V256h128V128zM704 237.21299999999997c-33.526 33.547-70.276 70.317-73.373 73.414C624.837 316.418 616.837 320 608 320H416c-17.674 0-32-14.326-32-32v-192c0-8.329 3.183-15.915 8.396-21.607 0.53-0.58 39.123-39.164 74.409-74.393H352c-17.674 0-32 14.326-32 32V352c0 17.674 14.326 32 32 32h320c17.674 0 32-14.326 32-32V237.21299999999997zM576 448c-70.692 0-128 57.308-128 127.999C448 646.692 505.308 704 576 704s128-57.308 128-128.001C704 505.308 646.692 448 576 448zM896 128l-64 64 0.082 128.084L896 384.002V128z" horiz-adv-x="1024" />
+<glyph glyph-name="device-desktop" unicode="" d="M960 768c-32 0-864 0-896 0s-64-32-64-64 0-544 0-576 32-64 64-64 320 0 320 0-192-64-192-128c0-32 32-64 64-64s480 0 512 0 64 32 64 64c0 64-192 128-192 128s288 0 320 0 64 32 64 64 0 544 0 576-32 64-64 64z m0-640h-896v576h896v-576z m-64 512h-192c-384-64-542-300-576-384v-64h768v448z" horiz-adv-x="1024" />
+<glyph glyph-name="device-mobile" unicode="" d="M576 832H64C28.688 832 0 803.312 0 768v-896c0-35.375 28.688-64 64-64h512c35.375 0 64 28.625 64 64V768C640 803.312 611.375 832 576 832zM288 768h64c17.625 0 32-14.344 32-32s-14.375-32-32-32h-64c-17.656 0-32 14.344-32 32S270.344 768 288 768zM352-128h-64c-17.656 0-32 14.375-32 32s14.344 32 32 32h64c17.625 0 32-14.375 32-32S369.625-128 352-128zM576 0H64V640h512V0z" horiz-adv-x="640" />
+<glyph glyph-name="diff" unicode="" d="M448 576H320v-128H192v-128h128v-128h128V320h128V448H448V576zM192-64h384V64H192V-64zM640 832H128v-64h480l224-224v-608h64V576L640 832zM0 704v-896h768V512L576 704H0zM704-128H64V640h480l160-160V-128z" horiz-adv-x="896" />
+<glyph glyph-name="diff-added" unicode="" d="M512 512h-128v-128h-128v-128h128v-128h128v128h128v128h-128v128z m320 256c-32 0-736 0-768 0s-64-32-64-64 0-736 0-768 32-64 64-64 736 0 768 0 64 32 64 64 0 736 0 768-32 64-64 64z m-64-736c0-16-17-32-32-32s-558 0-576 0-32 12-32 32c0 16 0 560 0 576s16 32 32 32 561 0 576 0 32-16 32-32 0-560 0-576z" horiz-adv-x="896" />
+<glyph glyph-name="diff-ignored" unicode="" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-512 194v-98h98l286 286v98h-98l-286-286z" horiz-adv-x="896" />
+<glyph glyph-name="diff-modified" unicode="" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-320 416c-71 0-128-57-128-128s57-128 128-128 128 57 128 128-57 128-128 128z" horiz-adv-x="896" />
+<glyph glyph-name="diff-removed" unicode="" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-512 224h384v128h-384v-128z" horiz-adv-x="896" />
+<glyph glyph-name="diff-renamed" unicode="" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-320 352h-192v-128h192v-128l256 192-256 192v-128z" horiz-adv-x="896" />
+<glyph glyph-name="ellipsis" unicode="" d="M640 512c-64 0-448 0-512 0s-128-64-128-128 0-64 0-128 64-128 128-128 448 0 512 0 128 64 128 128 0 64 0 128-64 128-128 128z m-384-256h-128v128h128v-128z m192 0h-128v128h128v-128z m192 0h-128v128h128v-128z" horiz-adv-x="768" />
+<glyph glyph-name="eye" unicode="" d="M512 704c-192 0-416-128-512-384 96-192 288-320 512-320s416 128 512 320c-96 256-320 384-512 384z m0-640c-192 0-352 128-384 256 32 128 192 256 384 256s352-128 384-256c-32-128-192-256-384-256z m0 448c-20 0-38-4-56-9 33-15 56-48 56-87 0-53-43-96-96-96-39 0-72 23-87 56-5-18-9-36-9-56 0-106 86-192 192-192s192 86 192 192-86 192-192 192z" horiz-adv-x="1024" />
+<glyph glyph-name="file-binary" unicode="" d="M0-128V768h576l192-192v-704H0zM704 512L512 704H64v-768h640V512zM320 320H128V576h192V320zM256 512h-64v-128h64V512zM256 64h64v-64H128v64h64V192h-64v64h128V64zM512 384h64v-64H384v64h64V512h-64v64h128V384zM576 0H384V256h192V0zM512 192h-64v-128h64V192z" horiz-adv-x="768" />
+<glyph glyph-name="file-code" unicode="" d="M288 448L128 288l160-160 64 64-96 96 96 96L288 448zM416 384l96-96-96-96 64-64 160 160L480 448 416 384zM576 768H0v-896h768V576L576 768zM704-64H64V704h448l192-192V-64z" horiz-adv-x="768" />
+<glyph glyph-name="file-directory" unicode="" d="M832 640c-32 0-336 0-352 0s-32 16-32 32 0 0 0 32-32 64-64 64-288 0-320 0-64-32-64-64 0-704 0-704h896s0 544 0 576-32 64-64 64z m-448 0h-320s0 15 0 32 16 32 32 32 241 0 256 0 32-15 32-32 0-32 0-32z" horiz-adv-x="896" />
+<glyph glyph-name="file-media" unicode="" d="M576 768H0v-896h768V576L576 768zM704-64H64V704h448l192-192V-64zM128 576v-512h128c0 70.625 57.344 128 128 128-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 70.625 0 128-57.344 128-128 0-70.625-57.375-128-128-128 70.625 0 128-57.375 128-128h128V448L512 576H128z" horiz-adv-x="768" />
+<glyph glyph-name="file-pdf" unicode="" d="M576 768H0v-896h768V576L576 768zM64 704h255.812c-13.188-4.094-27.281-15.031-34.625-42.875-13.25-49.406-7.031-130.75 15.625-209.344C276.688 370.562 178.188 175.125 171.531 163.5c-15.625-4.875-65.344-23.625-107.531-59.812V704zM347.125 396.531c57.625-149.781 95-149.531 135.188-167.594C398.344 216 334.219 206.75 249.781 169.5 246.094 163.062 326.281 315.40599999999995 347.125 396.531zM704-64H65.844 64v0.375c0.781-0.062 1.094-0.375 1.844-0.375 33.812 0 84.75 21 180.562 182.375 38.188 15.438 72.062 26.875 78.469 28.938 58.812 14.875 125 26.625 187.562 33.375C566.875 153.5 639.125 135 680.25 132.375c9.625-0.5 16.062 1.188 23.75 2V-64zM704 246.625c-23.688 14.688-54 25-89.125 25-24.25 0-50.625-1.375-78.688-4.375-26.938 13-92.562 32.719-147.188 190.219 17.094 103.625 12.719 173.562 12.719 173.562 6.781 52.938-23.344 72.844-51.625 72.844 0 0-0.279 0.125-0.344 0.125H512l192-192V246.625z" horiz-adv-x="768" />
+<glyph glyph-name="file-submodule" unicode="" d="M832 320c-32 0-192 0-192 0 0 32-32 64-64 64s-96 0-128 0-64-32-64-64 0-320 0-320h512s0 224 0 256-32 64-64 64z m-256-64h-128s0 17 0 32 15 32 32 32 48 0 64 0 32-15 32-32 0-32 0-32z m256 320c-32 0-336 0-352 0s-32 17-32 32 0 0 0 32-32 64-64 64-288 0-320 0-64-32-64-64 0-640 0-640h320s0 352 0 384 32 64 64 64 224 0 256 0 64-32 64-64h192s0 96 0 128-32 64-64 64z m-448 0h-320s0 16 0 32 16 32 32 32 240 0 256 0 32-17 32-32 0-32 0-32z" horiz-adv-x="896" />
+<glyph glyph-name="file-symlink-directory" unicode="" d="M832 640h-352c-16 0-32 16-32 32s0 0 0 32-32 64-64 64h-320c-32 0-64-32-64-64s0-704 0-704h896s0 544 0 576-32 64-64 64z m-768 32c0 17 16 32 32 32h256c15 0 32-15 32-32s0-32 0-32h-320s0 15 0 32z m384-544v128c-125 0-224-56-256-192 0 209 107 320 256 320 0 49 0 128 0 128l256-192-256-192z" horiz-adv-x="896" />
+<glyph glyph-name="file-symlink-file" unicode="" d="M576 768h-576v-896h768v704l-192 192z m128-832h-640v768h448l192-192v-576z m-320 448c-149 0-256-111-256-320 32 136 131 192 256 192v-128l256 192-256 192s0-79 0-128z" horiz-adv-x="768" />
+<glyph glyph-name="file-text" unicode="" d="M448 576H128v-64h320V576zM576 768H0v-896h768V576L576 768zM704-64H64V704h448l192-192V-64zM128 64h512v64H128V64zM128 192h512v64H128V192zM128 320h512v64H128V320z" horiz-adv-x="768" />
+<glyph glyph-name="file-zip" unicode="" d="M320 256v64h-64v-64H320zM320 384v64h-64v-64H320zM320 512v64h-64v-64H320zM192 448h64v64h-64V448zM576 768H0v-896h768V576L576 768zM704-64H64V704h192v-64h64v64h192l192-192V-64zM192 576h64v64h-64V576zM192 320h64v64h-64V320zM192 192l-64-64v-128h256V128l-64 64h-64v64h-64V192zM320 128v-64H192v64H320z" horiz-adv-x="768" />
+<glyph glyph-name="flame" unicode="" d="M433 787c50-134 24-207-32-265-61-64-156-112-223-206-89-125-104-400 217-472-135 71-164 277-18 406-38-125 32-205 119-176 85 29 141-32 139-102-1-48-20-89-69-112 209 37 293 210 293 342 0 174-155 198-77 344-93-8-125-69-116-169 6-66-63-111-114-81-41 25-40 73-4 109 77 76 107 251-115 382z" horiz-adv-x="1024" />
+<glyph glyph-name="fold" unicode="" d="M896 576H672l-64-64h192L672 384H224L96 512h192l-64 64H0v-63.999L160 352 0 192v-64h224l64 64H96l128 128h448l128-128H608l64-64h224v64L736 352l160 160.001V576zM640 640H512V832H384v-192H256l192-192L640 640zM256 64h128v-192h128V64h128L448 256 256 64z" horiz-adv-x="896" />
+<glyph glyph-name="gear" unicode="" d="M447.938 482C358.531 482 286 409.469 286 320c0-89.375 72.531-162.062 161.938-162.062 89.438 0 161.438 72.688 161.438 162.062C609.375 409.469 537.375 482 447.938 482zM772.625 226.938l-29.188-70.312 52.062-102.25 6.875-13.5-72.188-72.188L611.75 24.625l-70.312-28.875L505.75-113.5l-4.562-14.5H399.156L355-4.687999999999988l-70.312 29-102.404-51.938-13.5-6.75-72.156 72.125 55.875 118.5-28.969 70.25L14.469 262.125 0 266.812V368.781L123.406 413l28.969 70.188-51.906 102.469-6.844 13.438 72.062 72.062 118.594-55.844 70.219 29.031 35.656 109.188L394.75 768h102l44.188-123.469 70.125-29.031L713.5 667.469l13.625 6.844 72.125-72.062-55.875-118.406L772.25 413.5l109.375-35.656L896 373.25v-101.938L772.625 226.938z" horiz-adv-x="896" />
+<glyph glyph-name="gift" unicode="" d="M448-128h320V192H448V-128zM64-128h320V192H64V-128zM447.75 455.812c31.469 3.5 66.875 7.406 87.375 9.719C619 474.875 694.5 550.406 703.812 634.25c9.312 83.75-51 144.125-134.688 134.719C503.688 761.656 443.844 714 416 653.625 388.156 714 328.312 761.656 262.906 769.031 179.188 778.375 118.781 718 128.188 634.25c9.344-83.844 84.875-159.312 168.656-168.719 20.531-2.312 55.938-6.281 87.406-9.719C383.75 451.594 384 448 384 448h64C448 448 448.25 451.594 447.75 455.812zM555.375 691.312c45.25 5.062 78-27.562 72.875-72.875-5-45.312-45.875-86.156-91.125-91.219-45.375-5.031-78 27.594-72.938 72.906C469.249 645.436 510.125 686.281 555.375 691.312zM294.906 527.219c-45.25 5.062-86.062 45.906-91.125 91.219-5.063 45.313 27.594 77.938 72.812 72.875 45.312-5.031 86.156-45.875 91.222-91.188C372.875 554.812 340.219 522.188 294.906 527.219zM448 448v-192h384V448H448zM0 256h384V448H0V256z" horiz-adv-x="896" />
+<glyph glyph-name="gist" unicode="" d="M416 448l96-96-96-96 64-64 160 160-160 160-64-64z m-416 320v-832h768v832h-768z m704-768h-640v704h640v-704z m-352 256l-96 96 96 96-64 64-160-160 160-160 64 64z" horiz-adv-x="768" />
+<glyph glyph-name="gist-secret" unicode="" d="M193 128l128-192h-256l-65 256 257 64-64-128z m448 128l64-128-128-192h256l64 256-256 64z m-84 0h-216l44-102-64-218h256l-64 218 44 102z m84 192h-384l-128-64h640l-128 64z m-64 256l-128-64-128 64-64-192h384l-64 192z" horiz-adv-x="896" />
+<glyph glyph-name="git-branch" unicode="" d="M512 640c-71 0-128-57-128-128 0-47 26-88 64-110v-18c0-64-64-128-128-128-53 0-95-11-128-29v303c38 22 64 63 64 110 0 71-57 128-128 128s-128-57-128-128c0-47 26-88 64-110v-419c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 34-13 64-34 87 19 23 49 41 98 41 128 0 256 128 256 256v18c38 22 64 63 64 110 0 71-57 128-128 128z m-384 64c35 0 64-29 64-64s-29-64-64-64-64 29-64 64 29 64 64 64z m0-768c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m384 512c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z" horiz-adv-x="640" />
+<glyph glyph-name="git-commit" unicode="" d="M694.875 384C666.375 494.219 567.125 576 448 576c-119.094 0-218.375-81.781-246.906-192H0v-128h201.094C229.625 145.75 328.906 64 448 64c119.125 0 218.375 81.75 246.875 192H896V384H694.875zM448 192c-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 70.625 0 128-57.344 128-128C576 249.375 518.625 192 448 192z" horiz-adv-x="896" />
+<glyph glyph-name="git-compare" unicode="" d="M832 110s0 306 0 402-96 192-192 192c-64 0-64 0-64 0v128l-192-192 192-192v128s32 0 64 0 64-32 64-64 0-402 0-402c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 47-26 88-64 110z m-64-174c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m-448 128s-32 0-64 0-64 32-64 64 0 402 0 402c38 22 64 63 64 110 0 71-57 128-128 128s-128-57-128-128c0-47 26-88 64-110 0 0 0-306 0-402s96-192 192-192c64 0 64 0 64 0v-128l192 192-192 192v-128z m-192 512c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z" horiz-adv-x="896" />
+<glyph glyph-name="git-merge" unicode="" d="M640 384c-47.625 0-88.625-26.312-110.625-64.906C523.625 319.5 518 320 512 320c-131.062 0-255.438 99.844-300.812 223.438C238.469 566.906 256 601.281 256 640c0 70.656-57.344 128-128 128S0 710.656 0 640c0-47.219 25.844-88.062 64-110.281V110.25C25.844 88.06200000000001 0 47.25 0 0c0-70.625 57.344-128 128-128s128 57.375 128 128c0 47.25-25.844 88.062-64 110.25V340.531C276.156 251.5 392.375 192 512 192c6.375 0 11.625 0.438 17.375 0.625C551.5 154.188 592.5 128 640 128c70.625 0 128 57.375 128 128C768 326.656 710.625 384 640 384zM128-64c-35.312 0-64 28.625-64 64 0 35.312 28.688 64 64 64 35.406 0 64-28.688 64-64C192-35.375 163.406-64 128-64zM128 576c-35.312 0-64 28.594-64 64s28.688 64 64 64c35.406 0 64-28.594 64-64S163.406 576 128 576zM640 192c-35.312 0-64 28.625-64 64 0 35.406 28.688 64 64 64 35.375 0 64-28.594 64-64C704 220.625 675.375 192 640 192z" horiz-adv-x="768" />
+<glyph glyph-name="git-pull-request" unicode="" d="M704 110s0 306 0 402-96 192-192 192c-64 0-64 0-64 0v128l-192-192 192-192v128s32 0 64 0 64-32 64-64 0-402 0-402c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 47-26 88-64 110z m-64-174c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m-512 832c-71 0-128-57-128-128 0-47 26-88 64-110v-419c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 47-26 88-64 110v419c38 22 64 63 64 110 0 71-57 128-128 128z m0-832c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m0 640c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z" horiz-adv-x="768" />
+<glyph glyph-name="globe" unicode="" d="M512 704c-212.077 0-384-171.923-384-384s171.923-384 384-384c25.953 0 51.303 2.582 75.812 7.49-9.879 4.725-10.957 40.174-1.188 60.385 10.875 22.5 45 79.5 11.25 98.625s-24.375 27.75-45 49.875-12.19 25.451-13.5 31.125c-4.5 19.5 19.875 48.75 21 51.75s1.125 14.25 0.75 17.625S545.75 265.25 542 265.625s-5.625-6-10.875-6.375-28.125 13.875-33 17.625-7.125 12.75-13.875 19.5-7.5 1.5-18 5.625-44.25 16.5-70.125 27-28.125 25.219-28.5 35.625-15.75 25.5-22.961 36.375c-7.209 10.875-8.539 25.875-11.164 22.5s13.5-42.75 10.875-43.875-8.25 10.875-15.75 20.625 7.875 4.5-16.125 51.75 7.5 71.344 9 96 20.25-9 10.5 6.75 0.75 48.75-6.75 60.75S275 602 275 602c1.125 11.625 37.5 31.5 63.75 49.875s42.281 4.125 63.375-2.625 22.5-4.5 15.375 2.25 3 10.125 19.5 7.5 21-22.5 46.125-20.625 2.625-4.875 6-11.25-3.75-5.625-20.25-16.875S469.25 599 498.5 577.625s20.25 14.25 17.25 30S537.125 611 537.125 611c18-12 14.674-0.66 27.799-4.785S613.625 572 613.625 572c-44.625-24.375-16.5-27-9-32.625s-15.375-16.5-15.375-16.5c-9.375 9.375-10.875-0.375-16.875-3.75s-0.375-12-0.375-12c-31.031-4.875-24-37.5-23.625-45.375s-19.875-19.875-25.125-31.125S536.75 395 527 393.5s-19.5 36.75-72 22.5c-15.828-4.297-51-22.5-32.25-59.625s49.875 10.5 60.375 5.25-3-28.875-0.75-29.25 29.625-1.031 31.125-33 41.625-29.25 50.25-30 37.5 23.625 41.625 24.75S626 309.125 662 288.5s54.375-17.625 66.75-26.25 3.75-25.875 15.375-31.5 58.125 1.875 69.75-17.25-48-115.125-66.75-125.625S719.75 53.375 701 38s-45-34.406-69.75-49.125c-21.908-13.027-25.85-36.365-35.609-43.732C767.496-16.67999999999995 896 136.64999999999998 896 320 896 532.077 724.077 704 512 704zM602 343.625c-5.25-1.5-16.125-11.25-42.75 4.5s-45 12.75-47.25 15.375c0 0-2.25 6.375 9.375 7.5 23.871 2.311 54-22.125 60.75-22.5s10.125 6.75 22.125 2.883C616.25 347.52 607.25 345.125 602 343.625zM476.375 665.75c-2.615 1.902 2.166 4.092 5.016 7.875 1.645 2.186 0.425 5.815 2.484 7.875 5.625 5.625 33.375 13.5 27.949-1.875C506.4 664.25 480.5 662.75 476.375 665.75zM543.5 617c-9.375 0.375-31.443 2.707-27.375 6.75 15.844 15.75-6 20.25-19.5 21.375S477.5 653.75 484.25 654.5s33.75-0.375 38.25-4.125 28.875-13.5 30.375-20.625S552.875 616.625 543.5 617zM624.875 619.625c-7.5-6-45.24 21.529-52.5 27.75-31.5 27-48.375 18-54.99 22.5-6.617 4.5-4.26 10.5 5.865 19.5s38.625-3 55.125-4.875 35.625-14.625 36-29.781C614.75 639.564 632.375 625.625 624.875 619.625z" horiz-adv-x="1024" />
+<glyph glyph-name="graph" unicode="" d="M704 576H512v-640h192V576zM960 384H768v-448h192V384zM64-128V0h64v64H64V192h64v64H64V384h64v64H64V576h64v64H64V768h64V832H0v-1024h1024v64H64zM448 256H256v-320h192V256z" horiz-adv-x="1024" />
+<glyph glyph-name="heart" unicode="♥" d="M384-32c399 314 384 425 384 512s-72 192-192 192-192-128-192-128-72 128-192 128-192-105-192-192-15-198 384-512z" horiz-adv-x="768.199" />
+<glyph glyph-name="history" unicode="" d="M448 768c-90.938 0-175.312-27.531-245.938-74.062L128 768v-256h256l-88 88c45.438 24.688 96.688 40 152 40 176.75 0 320-143.219 320-320 0-176.75-143.25-320-320-320-176.781 0-320 143.25-320 320 0 45.562 9.781 88.781 27 128H64v99.406C24.312 480.5 0 403.406 0 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448C896 567.438 695.438 768 448 768zM447.031 1L512 64V256h128l64 64-64 64H512l-64 64L320 320l64-64v-192L447.031 1z" horiz-adv-x="896" />
+<glyph glyph-name="home" unicode="" d="M192 256l64-384h192V192h128v-320h192l64 384L512 576 192 256zM832 448V704H704l0.312-128.312L512 768 0 256h128l384 384 384-384h128L832 448z" horiz-adv-x="1024" />
+<glyph glyph-name="horizontal-rule" unicode="" d="M63.938 384h128v-128h64V639.938h-64V448h-128V639.938H0V256h63.938V384zM639.875 256V384h-63.938v-128H639.875zM639.875 448V575.938h-63.938V448H639.875zM447.938 448V575.938h128v64h-192V256h64V384h128v64H447.938zM0 0h639.875V128H0V0z" horiz-adv-x="639.875" />
+<glyph glyph-name="hourglass" unicode="" d="M571 320c118 85 197 240 197 384 0 71-172 128-384 128s-384-57-384-128c0-144 80-299 197-384-118-85-197-240-197-384 0-71 172-128 384-128s384 57 384 128c0 144-80 299-197 384z m-187 448c141 0 256-29 256-64s-115-64-256-64-256 29-256 64 115 64 256 64z m-64-706c-154-7-238-40-253-82 16 114 75 189 141 251 73 68 112 60 112 103v-273z m-105 352c-70 55-122 130-142 215 70-32 183-53 311-53s241 21 311 53c-20-85-72-160-142-215-24 17-70 34-169 34s-145-17-169-34z m233-352v273c0-43 39-35 112-103 66-62 125-138 141-251-14 41-99 75-253 82z" horiz-adv-x="768" />
+<glyph glyph-name="hubot" unicode="" d="M512 768c-283 0-512-229-512-512 0 0 0-192 0-256s64-128 128-128 704 0 768 0 128 64 128 128 0 256 0 256c0 283-229 512-512 512z m96-768h-192c-18 0-32 14-32 32s14 32 32 32h192c18 0 32-14 32-32s-14-32-32-32z m288 128c0-32-32-64-64-64s-128 0-128 0c0 32-32 64-64 64s-224 0-256 0-64-32-64-64c0 0-96 0-128 0s-64 32-64 64 0 360 0 360c78 129 220 216 384 216s306-87 384-216c0 0 0-328 0-360z m-128 384c-32 0-480 0-512 0s-64-32-64-64 0-96 0-128 32-64 64-64 480 0 512 0 64 32 64 64 0 96 0 128-32 64-64 64z m0-128l-64-64h-128l-64 64-64-64h-128l-64 64v64h64l64-64 64 64h128l64-64 64 64h64v-64z" horiz-adv-x="1024" />
+<glyph glyph-name="inbox" unicode="" d="M704 640H64L0 256v-256h768V256L704 640zM576 256l-64-128H256l-64 128H79l49 320h512l49-320H576z" horiz-adv-x="768" />
+<glyph glyph-name="info" unicode="" d="M448 448c35 0 64 29 64 64s-29 64-64 64-64-29-64-64 29-64 64-64z m0 320c-247 0-448-201-448-448s201-448 448-448 448 201 448 448-201 448-448 448z m0-768c-177 0-320 143-320 320s143 320 320 320 320-143 320-320-143-320-320-320z m64 320c0 32-32 64-64 64s-32 0-64 0-64-32-64-64h64s0-160 0-192 32-64 64-64 32 0 64 0 64 32 64 64h-64s0 160 0 192z" horiz-adv-x="896" />
+<glyph glyph-name="issue-closed" unicode="" d="M704 515.969l-96-96L768 256l256 256-96 96L769.25 449.219 704 515.969zM512 0c-176.781 0-320 143.25-320 320 0 176.781 143.219 320 320 320 88.375 0 168.375-35.844 226.25-93.75l90.562 90.5C747.75 717.875 635.75 768 512 768 264.562 768 64 567.438 64 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448L759.75 119.75C768.688 130.75 684.75 0 512 0zM576 576H448v-320h128V576zM448 64h128V192H448V64z" horiz-adv-x="1024" />
+<glyph glyph-name="issue-opened" unicode="" d="M448 768C200.562 768 0 567.438 0 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448C896 567.438 695.438 768 448 768zM448 0c-176.781 0-320 143.25-320 320 0 176.781 143.219 320 320 320 176.75 0 320-143.219 320-320C768 143.25 624.75 0 448 0zM384 64h128V192H384V64zM384 256h128V576H384V256z" horiz-adv-x="896" />
+<glyph glyph-name="issue-reopened" unicode="" d="M639.125 64.75C585.75 24.625 520 0 448 0c-176.781 0-320 143.25-320 320 0 45.562 9.781 88.781 27 128H64v99.469C24.312 480.562 0 403.406 0 320c0-247.438 200.562-448 448-448 107.375 0 204.5 39.312 281.75 102.25L768-64V128H576L639.125 64.75zM384 64h128V192H384V64zM512 576H384v-320h128V576zM896 320c0 247.438-200.562 448-448 448-107.406 0-204.531-39.312-281.656-102.344L128 704v-192h192l-63.156 63.156C310.281 615.312 376 640 448 640c176.75 0 320-143.219 320-320 0-45.562-9.75-88.75-27-128h91v-99.5C871.688 159.438 896 236.5 896 320z" horiz-adv-x="896" />
+<glyph glyph-name="jersey" unicode="" d="M704 832h-192c0-32-33-64-97-64s-95 32-95 64h-192c0-128-2-384-128-384 0 0-1-544-1-576s32-64 64-64 672 0 704 0 64 32 64 64 0 576 0 576c-126 0-128 256-128 384z m-609-960c-16 0-31 10-31 32 0 32 0 480 0 480 119 64 128 192 128 384h64c0-96 32-191 160-192s160 96 160 192h64c0-186 32-276 64-339v-557s-593 0-609 0z m385 576l-32-32v-320l32-32h128l32 32v320l-32 32h-128z m96-320h-64v256h64v-256z m-352 320l-32-32v-320l32-32h128l32 32v320l-32 32h-128z m96-320h-64v256h64v-256z" horiz-adv-x="896" />
+<glyph glyph-name="jump-down" unicode="" d="M767.75 640H0.25L384 256.25 767.75 640zM0 128v-128h768V128H0z" horiz-adv-x="768" />
+<glyph glyph-name="jump-left" unicode="" d="M256.25 320L640-63.75v767.5L256.25 320zM0-64h128V704H0V-64z" horiz-adv-x="640" />
+<glyph glyph-name="jump-right" unicode="" d="M0-63.75L383.75 320 0 703.812V-63.75zM512 704v-768h128V704H512z" horiz-adv-x="640" />
+<glyph glyph-name="jump-up" unicode="" d="M0.188 0h767.5L384 383.75 0.188 0zM0 640v-128h768V640H0z" horiz-adv-x="768" />
+<glyph glyph-name="key" unicode="" d="M640.9 768.1c-141.4 0-256-114.6-256-256 0-19.6 2.2-38.6 6.4-56.9L0 64v-64l64-64h128l64 64v64h64v64h64v64h128l70.8 70.8c18.7-4.3 38.1-6.6 58.1-6.6 141.4 0 256 114.6 256 256S782.2 768.1 640.9 768.1zM384 320L64 0v64l320 320V320zM704 512c-35.3 0-64 28.7-64 64 0 35.3 28.7 64 64 64s64-28.7 64-64C768 540.7 739.3 512 704 512z" horiz-adv-x="896.9" />
+<glyph glyph-name="keyboard" unicode="" d="M640 256h64V384h-64V256zM768 576h-64v-128h64V576zM640 576h-64v-128h64V576zM512 256h64V384h-64V256zM384 64h320V192H384V64zM768 256h128V576h-64v-192h-64V256zM256 64h64V192h-64V64zM768 64h128V192H768V64zM512 576h-64v-128h64V576zM192 384h-64v-128h64V384zM192 192h-64v-128h64V192zM0 704v-768h1024V704H0zM960 0H64V640h896V0zM384 256h64V384h-64V256zM256 576H128v-128h128V576zM384 576h-64v-128h64V576zM256 256h64V384h-64V256z" horiz-adv-x="1024" />
+<glyph glyph-name="law" unicode="" d="M514 640c34 1 61 28 62 62 1 37-29 67-66 66-34-1-61-28-62-62-1-37 29-67 66-66z m464-384h-18l-127 246c18 2 36 9 52 16 24 11 29 43 11 62l-1 1c-11 11-28 15-43 8-14-6-34-13-53-13-56 0-81 64-287 64s-231-64-287-64c-20 0-39 6-53 13-15 6-32 3-43-8l-1-1c-18-19-13-50 11-62 16-8 34-14 52-16l-127-246h-18c-8 0-14-7-13-15 11-64 92-113 191-113s180 49 191 113c1 8-5 15-13 15h-18l-127 245c83 7 127 49 191 49v-486c-35 0-64-29-64-64h-71c-28 0-57-29-57-64h512c0 35-29 64-71 64h-57c0 35-29 64-64 64v486c64 0 108-42 191-49l-127-245h-18c-8 0-14-7-13-15 11-64 92-113 191-113s180 49 191 113c1 8-5 15-13 15z m-658 0h-192l96 180 96-180z m384 0l96 180 96-180h-192z" horiz-adv-x="1024" />
+<glyph glyph-name="light-bulb" unicode="" d="M512 768c-176.731 0-320-143.269-320-320 0-104.69 50.278-197.633 128-256.015V0c0-35.346 28.653-64 64-64 0-35.346 28.653-64 64-64h128c35.347 0 64 28.654 64 64 35.347 0 64 28.654 64 64V191.985C781.722 250.36699999999996 832 343.31 832 448 832 624.731 688.731 768 512 768zM640 32c0-17.673-14.326-32-32-32H416c-17.674 0-32 14.327-32 32v32h256V32zM704 278.693c-33.234-33.03-64-42.389-64-124.041V128h-64V256l128 128v64l-64 64-64-64-64 64-64-64-64 64-64-64v-64l128-128v-128h-64v26.652c0 81.652-30.766 91.011-64 124.041C280.177 323.82 256 383.082 256 448c0 141.385 114.615 256 256 256s256-114.615 256-256C768 383.082 743.823 323.82 704 278.693zM512 256L384 384v64l64-64 64 64 64-64 64 64v-64L512 256z" horiz-adv-x="1024" />
+<glyph glyph-name="link" unicode="" d="M768 576h-138c48-32 93-89 107-128h30c65 0 128-64 128-128s-65-128-128-128h-192c-63 0-128 64-128 128 0 23 7 45 18 64h-137c-5-21-8-42-8-64 0-128 127-256 255-256s65 0 193 0 256 128 256 256-128 256-256 256z m-481-384h-30c-65 0-128 64-128 128s65 128 128 128h192c63 0 128-64 128-128 0-23-7-45-18-64h137c5 21 8 42 8 64 0 128-127 256-255 256s-65 0-193 0-256-128-256-256 128-256 256-256h138c-48 32-93 89-107 128z" horiz-adv-x="1024" />
+<glyph glyph-name="link-external" unicode="" d="M640 64H128V574.094L256 576V704H0v-768h768V256H640V64zM384 704l128-128L320 384l128-128 192 192 128-128V704H384z" horiz-adv-x="768" />
+<glyph glyph-name="list-ordered" unicode="" d="M320 256h448v128h-448v-128z m0-256h448v128h-448v-128z m0 640v-128h448v128h-448z m-241-256h78v256h-36l-85-23v-50l43 2v-185z m110-206c0 36-12 78-96 78-33 0-64-6-83-16l1-66c21 10 42 15 67 15s32-11 32-28c0-26-30-58-110-112v-50h192v67l-91-2c49 30 87 66 87 113l1 1z" horiz-adv-x="768" />
+<glyph glyph-name="list-unordered" unicode="" d="M0 256h128v128h-128v-128z m0 256h128v128h-128v-128z m0-512h128v128h-128v-128z m256 256h512v128h-512v-128z m0 256h512v128h-512v-128z m0-512h512v128h-512v-128z" horiz-adv-x="768" />
+<glyph glyph-name="location" unicode="" d="M320 832c-177 0-320-143-320-320s160-416 320-704c160 288 320 527 320 704s-143 320-320 320z m0-448c-71 0-128 57-128 128s57 128 128 128 128-57 128-128-57-128-128-128z" horiz-adv-x="640" />
+<glyph glyph-name="lock" unicode="" d="M704 384c-32 0-64 0-64 0s0 64 0 192-128 256-256 256-256-128-256-256 0-192 0-192-32 0-64 0-64-32-64-64 0-416 0-448 32-64 64-64 608 0 640 0 64 32 64 64 0 416 0 448-32 64-64 64z m-192-128h-384v-64h384v-64h-384v-64h384v-64h-384v-64h384v-64h-448v448h448v-64z m0 128h-256s0 128 0 192 64 128 128 128 128-64 128-128 0-192 0-192z" horiz-adv-x="768" />
+<glyph glyph-name="logo-github" unicode="" d="M552.73 499.865H311.557c-6.205 0-11.25-5.045-11.25-11.297v-117.887c0-6.252 5.045-11.272 11.25-11.272h94.109v-146.542c0 0-21.145-7.057-79.496-7.057-68.914 0-165.156 25.244-165.156 236.795 0 211.642 100.197 239.491 194.307 239.491 81.465 0 116.514-14.304 138.869-21.241 7.01-2.203 13.404 4.831 13.404 11.105L534.543 785.87c0 2.912-1.041 6.417-4.262 8.785C521.186 801.048 465.865 832 326.168 832 165.133 832 0 763.513 0 434.243 0 105.02099999999996 189.051 56 348.381 56c131.883 0 212.021 56.314 212.021 56.314 3.268 1.801 3.6 6.395 3.6 8.479V488.568C563.955 494.773 558.887 499.865 552.73 499.865zM1772.381 803.866h-135.695c-6.252 0-11.271-5.044-11.271-11.296v-262.393h-211.619V792.57c0 6.252-5.068 11.296-11.178 11.296h-135.838c-6.111 0-11.084-5.044-11.084-11.296v-710.473c0-6.299 5.021-11.32 11.084-11.32h135.838c6.203 0 11.178 5.068 11.178 11.32V385.933h211.619l-0.475-303.883c0-6.3 5.021-11.272 11.084-11.272h135.885c6.252 0 11.131 5.068 11.131 11.272l0.473 710.521C1783.607 798.822 1778.539 803.866 1772.381 803.866zM714.949 787.763c-48.357 0-87.574-39.572-87.574-88.403 0-48.855 39.217-88.428 87.574-88.428s87.527 39.572 87.527 88.428C802.477 748.19 763.307 787.763 714.949 787.763zM792.861 559.874c0 6.205-5.02 11.344-11.131 11.344H646.32c-6.348 0-11.746-6.394-11.746-12.67 0 0 0-394.654 0-469.867 0-13.735 8.572-17.903 19.703-17.903 0 0 57.688 0 121.959 0 13.311 0 16.814 6.536 16.814 18.188-0.094 25.197-0.094 123.808-0.094 142.942C792.861 250.09500000000003 792.861 559.874 792.861 559.874zM2297.973 570.152h-134.701c-6.158 0-11.084-5.092-11.084-11.344v-348.31c0 0-34.244-25.197-82.934-25.197-48.547 0-61.525 22.024-61.525 69.719 0 47.553 0 303.835 0 303.835 0 6.252-5.068 11.345-11.131 11.345h-136.643c-6.252 0-11.178-5.093-11.178-11.345 0 0 0-185.521 0-326.807 0-141.284 78.766-175.906 186.99-175.906 88.854 0 160.609 49.115 160.609 49.115s3.363-25.766 5.068-28.844c1.422-3.078 5.447-6.158 9.852-6.158h86.58c6.158 0 11.178 5.069 11.178 11.321l0.379 477.278C2309.15 565.0609999999999 2304.129 570.152 2297.973 570.152zM2666.932 586.1610000000001c-76.539 0-128.592-34.148-128.592-34.148V792.57c0 6.252-5.068 11.296-11.131 11.296h-136.264c-6.109 0-11.131-5.044-11.131-11.296l-0.379-710.521c0-6.3 5.068-11.272 11.225-11.272 0 0 94.773 0 94.869 0 4.215 0 7.389 2.179 9.805 5.968 2.369 3.837 5.73 32.775 5.73 32.775s55.557-52.763 161.035-52.763c123.807 0 194.758 62.804 194.758 281.906C2856.859 557.482 2743.471 586.1610000000001 2666.932 586.1610000000001zM2613.791 185.77499999999998c-46.701 1.421-78.34 22.64-78.34 22.64v225.07c0 0 31.307 19.206 69.672 22.593 48.547 4.31 95.438-10.326 95.438-126.13C2700.322 207.94100000000003 2679.199 183.83399999999995 2613.791 185.77499999999998zM1185.125 188.33299999999997c-5.969 0-21.219-2.368-36.85-2.368-49.92 0-66.971 23.256-66.971 53.331 0 30.218 0 199.85 0 199.85h101.926c6.252 0 11.178 5.044 11.178 11.343v109.48c0.094 6.299-4.926 11.344-11.178 11.344h-101.926l-0.143 134.535c0 5.092-2.699 7.625-8.572 7.625H933.861c-5.352 0-8.336-2.391-8.336-7.578v-139.035c0 0-69.576-16.79-74.266-18.188-4.641-1.326-8.051-5.684-8.051-10.822v-87.408c0-6.252 5.068-11.344 11.178-11.344h71.139c0 0 0-91.34 0-210.222 0-156.109 109.553-171.455 183.439-171.455 33.723 0 74.076 10.988 80.848 13.356 4.074 1.421 6.395 5.637 6.395 10.136l0.047 96.101C1196.254 183.312 1190.998 188.428 1185.125 188.33299999999997z" horiz-adv-x="2856.857" />
+<glyph glyph-name="mail" unicode="" d="M0 640v-640h896V640H0zM768 576L448 312 128 576H768zM64 512l252.031-191.625L64 128V512zM128 64l254 206.25L448 220l65.875 50.125L768 64H128zM832 128L579.625 320.062 832 512V128z" horiz-adv-x="896" />
+<glyph glyph-name="mail-read" unicode="" d="M576 448H256v-64h320V448zM384 576H256v-64h128V576zM768 603.469V704H627.188L448 832 268.812 704H128v-100.531L0 512v-640h896V512L768 603.469zM192 640h512v-244.812L448 184 192 395.188V640zM64 384l252.031-191.625L64 0V384zM128-64l254 206.25L448 92l65.875 50.125L768-64H128zM832 0L579.625 192.062 832 384V0z" horiz-adv-x="896" />
+<glyph glyph-name="mail-reply" unicode="" d="M384 672l-384-288 384-288v192c111 0 329-61 384-280 0 291-196 451-384 472v192z" horiz-adv-x="768" />
+<glyph glyph-name="mark-github" unicode="" d="M512 832C229.25 832 0 602.75 0 320c0-226.25 146.688-418.125 350.156-485.812 25.594-4.688 34.938 11.125 34.938 24.625 0 12.188-0.469 52.562-0.719 95.312C242-76.81200000000001 211.906 14.5 211.906 14.5c-23.312 59.125-56.844 74.875-56.844 74.875-46.531 31.75 3.53 31.125 3.53 31.125 51.406-3.562 78.47-52.75 78.47-52.75 45.688-78.25 119.875-55.625 149-42.5 4.654 33 17.904 55.625 32.5 68.375C304.906 106.56200000000001 185.344 150.5 185.344 346.688c0 55.938 19.969 101.562 52.656 137.406-5.219 13-22.844 65.094 5.062 135.562 0 0 42.938 13.75 140.812-52.5 40.812 11.406 84.594 17.031 128.125 17.219 43.5-0.188 87.312-5.875 128.188-17.281 97.688 66.312 140.688 52.5 140.688 52.5 28-70.531 10.375-122.562 5.125-135.5 32.812-35.844 52.625-81.469 52.625-137.406 0-196.688-119.75-240-233.812-252.688 18.438-15.875 34.75-47 34.75-94.75 0-68.438-0.688-123.625-0.688-140.5 0-13.625 9.312-29.562 35.25-24.562C877.438-98 1024 93.875 1024 320 1024 602.75 794.75 832 512 832z" horiz-adv-x="1024" />
+<glyph glyph-name="markdown" unicode="" d="M950.154 640H73.846C33.127 640 0 606.873 0 566.154v-492.308C0 33.125 33.127 0 73.846 0h876.308c40.721 0 73.846 33.125 73.846 73.846V566.154C1024 606.873 990.875 640 950.154 640zM576 128.125L448 128V320l-96-123.077L256 320v-192H128V512h128l96-128 96 128 128 0.125V128.125zM767.091 96.125L608 320h96V512h128v-192h96L767.091 96.125z" horiz-adv-x="1024" />
+<glyph glyph-name="megaphone" unicode="" d="M832 800c-130 0-124-130-704-128C57.344 672 0 557.375 0 416s57.344-256 128-256c22.781 0 43.188-0.5 64.188-0.875L256-128l192-32 64 96-45.125 203.125C709.375 102.875 733.75 32 832 32c106 0 192 172 192 384C1024 628.031 938 800 832 800zM197 349.062c-39.188 1.469-82.188 2.25-127.562 2.625C66 371.406 64 393.094 64 416c0 88.375 28.688 192 64 192 39.031-0.125 75 0.438 109 1.406C209.656 562.438 192 493.688 192 416 192 392.688 194.062 370.562 197 349.062zM261.312 346.062C258.125 368.312 256 391.625 256 416c0 79.5 18.438 149.5 46.906 196.219 155.156 8.312 251.906 28.469 319.031 50.188C593.625 595.531 576 510.344 576 416c0-40 3.875-78 9.5-114.312C513.344 320.375 412.812 337.406 261.312 346.062zM832 128c-12.125 0-23.688 5.062-34.812 12.125-15.25 67.312-83.438 418.344 117.438 494.188C942.125 581.5 960 503.812 960 416 960 257 902.625 128 832 128z" horiz-adv-x="1024" />
+<glyph glyph-name="mention" unicode="" d="M466.697 732.899C238.66 760.898 31.1 598.735 3.102 370.698c-28-228.038 134.163-435.598 362.2-463.597 71.429-8.756 145.115 0.913 213.325 29.946l-0.016 0.032c24.404 10.357 35.788 38.538 25.431 62.939-10.359 24.403-38.538 35.787-62.94 25.43l-0.001 0.004c-52.472-22.339-109.15-29.799-164.1-23.067-175.413 21.538-300.153 181.2-278.616 356.613 21.538 175.413 181.199 300.154 356.613 278.616 175.412-21.538 300.154-181.199 278.617-356.612-4.309-35.083-21.542-55.725-61.6-55.725-42.5 0-64 45.889-64 81.222V432c0 26.51-21.49 48-48 48-9.699 0-18.72-2.887-26.269-7.833-25.684 20.259-57.437 33.87-94.349 38.402-105.246 12.923-201.045-61.924-213.967-167.17C212.508 238.15200000000004 287.354 142.35400000000004 392.6 129.43200000000002c57.379-7.045 116.216 14.707 157.871 53.13 24.959-28.124 59.866-47.624 100.121-52.567 87.707-10.769 167.537 51.602 178.307 139.309C856.898 497.34 694.734 704.899 466.697 732.899zM511.285 308.30100000000004c-6.462-52.623-54.361-90.047-106.985-83.585-52.623 6.461-90.046 54.36-83.585 106.984 6.461 52.623 54.361 90.046 106.984 83.585C480.322 408.823 517.746 360.924 511.285 308.30100000000004z" horiz-adv-x="832" />
+<glyph glyph-name="microscope" unicode="" d="M617-64c86.312 18.75 151 100 151 192 0 58.438-26.625 110.125-67.875 145.375C702.5 288.625 704 304.125 704 320c0 104.844-49.875 197.875-128 256l64 64v64l64 64L640 832l-64-64h-64L256 512l-128-64v-128l64-64h128l64 128 96 96c55.5-33.406 96-90.438 96-160-106.062 0-192-85.938-192-192H0v-64h192c19.125-14.25 42.062-22.125 64-32v-96H128L0-192h768L640-64H617zM512 128c0 35.375 28.625 64 64 64s64-28.625 64-64c0-35.312-28.625-64-64-64S512 92.68799999999999 512 128z" horiz-adv-x="768" />
+<glyph glyph-name="milestone" unicode="" d="M704 640H0v-256h704l128 128L704 640zM448 448H320V576h128V448zM448 832H320v-128h128V832zM320-192h128V320H320V-192z" horiz-adv-x="832" />
+<glyph glyph-name="mirror" unicode="" d="M320 512L128 320l192-192V256h384v-128l192 192L704 512v-128H320V512zM512 832L0 512v-704l512 256 512-256V512L512 832zM960-64L576 128v64H448v-64L64-64V448l384 256v-256h128V704l384-256V-64z" horiz-adv-x="1024" />
+<glyph glyph-name="mortar-board" unicode="" d="M501 244l-245 76s0-96 0-160 115-96 256-96 256 32 256 96 0 160 0 160l-245-76c-7-2-15-2-23 0h1z m18 409c-4 1-9 1-13 0l-489-152c-21-7-21-36 0-43l111-35v-113c-19-11-32-32-32-55 0-12 3-23 9-32-5-9-9-20-9-32v-165c0-35 128-35 128 0v165c0 12-3 23-9 32 5 9 9 20 9 32 0 24-13 44-32 55v93l313-98c4-1 9-1 13 0l489 152c21 7 21 36 0 43l-488 153z m-6-205c-35 0-64 14-64 32s29 32 64 32 64-14 64-32-29-32-64-32z" horiz-adv-x="1024" />
+<glyph glyph-name="move-down" unicode="" d="M640 512H448V832H192v-320H0l320-384L640 512zM0-192h640V0H0V-192z" horiz-adv-x="640" />
+<glyph glyph-name="move-left" unicode="" d="M0 0h192V640H0V0zM704 448V640L320 320l384-320V192h320V448H704z" horiz-adv-x="1024" />
+<glyph glyph-name="move-right" unicode="" d="M832 640v-640h192V640H832zM320 448H0v-256h320v-192l384 320L320 640V448z" horiz-adv-x="1024" />
+<glyph glyph-name="move-up" unicode="" d="M0 128h192v-320h256V128h192L320 512 0 128zM0 832v-192h640V832H0z" horiz-adv-x="640" />
+<glyph glyph-name="mute" unicode="" d="M128 448H0v-256h128l256-192h64V640h-64L128 448zM864 416l-64 64-96-96-96 96-63-63.5 95-96.5-96-96 64-64 96 96 96-96 64 64-96 96L864 416z" horiz-adv-x="896" />
+<glyph glyph-name="no-newline" unicode="" d="M896 512v-128H768V512L576 320l192-192V256h192c0 0 64 0.375 64 64s0 192 0 192H896zM224 544C100.281 544 0 443.719 0 320c0-123.75 100.281-224 224-224s224 100.25 224 224C448 443.719 347.719 544 224 544zM96 320c0 70.656 57.344 128 128 128 18.75 0 36.406-4.219 52.469-11.531L107.531 267.5C100.219 283.625 96 301.25 96 320zM224 192c-18.75 0-36.406 4.25-52.469 11.5l168.938 168.969C347.781 356.406 352 338.75 352 320 352 249.375 294.656 192 224 192z" horiz-adv-x="1024" />
+<glyph glyph-name="octoface" unicode="" d="M940.812 554.312c8.25 20.219 35.375 101.75-8.562 211.906 0 0-67.375 21.312-219.875-82.906C648.5 700.875 579.875 703.5 512 703.5c-67.906 0-136.438-2.625-200.5-20.25C159.031 787.531 91.719 766.219 91.719 766.219 47.812 656 74.938 574.531 83.188 554.312 31.5 498.438 0 427.125 0 339.656 0 10.437999999999988 213.25-64 510.844-64 808.562-64 1024 10.437999999999988 1024 339.656 1024 427.125 992.5 498.438 940.812 554.312zM512-1c-211.406 0-382.781 9.875-382.781 214.688 0 48.938 24.062 94.595 65.344 132.312 68.75 62.969 185.281 29.688 317.438 29.688 132.25 0 248.625 33.281 317.438-29.625 41.312-37.78 65.438-83.312 65.438-132.312C894.875 8.875 723.375-1 512-1zM351.156 319.562c-42.469 0-76.906-51.062-76.906-114.188s34.438-114.312 76.906-114.312c42.375 0 76.812 51.188 76.812 114.312S393.531 319.562 351.156 319.562zM672.875 319.562C630.5 319.562 596 268.5 596 205.375s34.5-114.312 76.875-114.312 76.812 51.188 76.812 114.312C749.75 268.5 715.312 319.562 672.875 319.562z" horiz-adv-x="1024" />
+<glyph glyph-name="organization" unicode="" d="M768 448h-64H576h-64-64-64-64H192h-64C57.344 448 0 390.656 0 320v-64c0-47.25 25.844-88.062 64-110.25V-64h256v-128h256V-64h256V145.75c38.125 22.188 64 62.938 64 110.25v64C896 390.656 838.625 448 768 448zM256 0H128V256H64v64c0 35.312 28.688 64 64 64h81.719c-11-18.875-17.719-40.562-17.719-64v-128c0-47.25 25.844-88.062 64-110.25V0zM576 128V256h-64v-384H384V256h-64v-128c-35.312 0-64 28.625-64 64V320c0 35.312 28.688 64 64 64h256c35.375 0 64-28.688 64-64v-128C640 156.625 611.375 128 576 128zM832 256h-64v-256H640v81.75c38.125 22.188 64 62.938 64 110.25V320c0 23.438-6.75 45.125-17.75 64H768c35.375 0 64-28.688 64-64V256zM303.688 514.625C338.875 474.125 390.156 448 448 448c57.875 0 109.125 26.125 144.312 66.625C614.125 475.062 655.688 448 704 448c70.625 0 128 57.344 128 128s-57.375 128-128 128c-25.625 0-49.375-7.688-69.375-20.688C614.875 768.438 539.062 832 448 832S281.094 768.438 261.375 683.312C241.344 696.312 217.594 704 192 704c-70.656 0-128-57.344-128-128s57.344-128 128-128C240.312 448 281.844 475.062 303.688 514.625zM704 640c35.375 0 64-28.594 64-64s-28.625-64-64-64c-35.312 0-64 28.594-64 64S668.688 640 704 640zM448 768c70.625 0 128-57.344 128-128s-57.375-128-128-128c-70.656 0-128 57.344-128 128S377.344 768 448 768zM192 512c-35.312 0-64 28.594-64 64s28.688 64 64 64c35.406 0 64-28.594 64-64S227.406 512 192 512z" horiz-adv-x="896" />
+<glyph glyph-name="package" unicode="" d="M480 768L0 640v-576l480-128 480 128V640L480 768zM63.875 111.06600000000003L63.5 544l384.498-102.533 0.001-432.833L63.875 111.06600000000003zM63.5 608l160.254 42.734L640 539.735v-0.135l-160-42.667L63.5 608zM896.125 111.06600000000003L512.001 8.634000000000015l0.001 432.833L640 475.6v-156l128 34.135V509.733L896.5 544 896.125 111.06600000000003zM768 573.733v0.125L351.734 684.862 480 719.066 896.5 608 768 573.733z" horiz-adv-x="1024" />
+<glyph glyph-name="paintcan" unicode="" d="M384 832C171.923 832 0 660.077 0 448v-64c0-35.346 28.654-64 64-64v-320c0-70.692 143.269-128 320-128s320 57.308 320 128V320c35.346 0 64 28.654 64 64v64C768 660.077 596.077 832 384 832zM576 192v-32c0-17.673-14.327-32-32-32s-32 14.327-32 32v32c0 17.673-14.327 32-32 32s-32-14.327-32-32v-160c0-17.673-14.327-32-32-32s-32 14.327-32 32V160c0 17.673-14.327 32-32 32s-32-14.327-32-32v-32c0-35.346-28.654-64-64-64s-64 28.654-64 64v64c-35.346 0-64 28.654-64 64V371.193C186.382 340.108 279.318 320 384 320s197.618 20.108 256 51.193V256C640 220.654 611.346 192 576 192zM384 384c-107.433 0-199.393 26.474-237.372 64 37.979 37.526 129.939 64 237.372 64s199.393-26.474 237.372-64C583.393 410.474 491.433 384 384 384zM384 576c-176.62 0-319.816-57.236-319.996-127.867-0.001 0.001-0.002 0.001-0.003 0.002C64.075 624.804 207.314 768 384 768c176.731 0 320-143.269 320-320C704 518.692 560.731 576 384 576z" horiz-adv-x="768" />
+<glyph glyph-name="pencil" unicode="" d="M704 768L576 640l192-192 128 128L704 768zM0 64l0.688-192.562L192-128l512 512L512 576 0 64zM192-64H64V64h64v-64h64V-64z" horiz-adv-x="896" />
+<glyph glyph-name="person" unicode="" d="M448 640C448 746 362.062 832 256 832S64 746 64 640c0-106.062 85.938-192 192-192S448 533.938 448 640zM256 512c-70.656 0-128 57.344-128 128S185.344 768 256 768c70.625 0 128-57.344 128-128S326.625 512 256 512zM384 448H256 128C57.344 448 0 390.656 0 320v-128c0-70.625 57.344-128 128-128v-256h256V64c70.625 0 128 57.375 128 128V320C512 390.656 454.625 448 384 448zM448 192c0-35.375-28.625-64-64-64V256h-64v-384H192V256h-64v-128c-35.312 0-64 28.625-64 64V320c0 35.312 28.688 64 64 64h256c35.375 0 64-28.688 64-64V192z" horiz-adv-x="512" />
+<glyph glyph-name="pin" unicode="" d="M196 128l64-320 64 320c-20-2-43-3-64-3s-44 1-64 3z m254 299c-33 17-62 59-62 85v64c0 22 12 39 23 52 15 13 24 29 24 45 0 53-61 95-175 95s-175-42-175-95c0-16 9-32 24-45 11-13 23-30 23-52v-64c0-26-29-68-62-85-38-19-70-54-70-88 0-74 101-148 260-148s260 73 260 148c0 33-31 68-70 88z" horiz-adv-x="519.657" />
+<glyph glyph-name="playback-fast-forward" unicode="" d="M0 64l384 256L0 576V64zM768 320L384 576v-256-256L768 320z" horiz-adv-x="768" />
+<glyph glyph-name="playback-pause" unicode="" d="M0 0h192V640H0V0zM320 640v-640h192V640H320z" horiz-adv-x="512" />
+<glyph glyph-name="playback-play" unicode="" d="M0 640l512-320L0 0V640z" horiz-adv-x="512" />
+<glyph glyph-name="playback-rewind" unicode="" d="M384 320l384-256V576L384 320zM0 320l384-256V320 576L0 320z" horiz-adv-x="768" />
+<glyph glyph-name="plug" unicode="" d="M1003.386 627.336l-0.905 0.905c-24.744 24.744-64.861 24.744-89.605 0l-45.707-45.707-90.51 90.51 45.707 45.707c24.744 24.744 24.744 64.861 0 89.605l-0.905 0.905c-24.744 24.744-64.861 24.744-89.605 0l-47.973-47.973C621.76 802.446 537.237 795.66 482.502 740.926l-24.89-24.89c-109.011-109.011-121.948-277.692-38.854-400.892l-4.138-4.138c-62.392-62.392-62.484-163.493-0.275-225.999 12.41-12.469 12.642-33.327 0.121-45.683-12.509-12.343-32.655-12.292-45.101 0.153l-89.427 89.427c-62.637 62.638-164.63 63.747-227.299 1.141-62.542-62.479-62.562-163.829-0.058-226.332l8.763-8.763c24.744-24.744 64.861-24.744 89.605 0l0.905 0.905c24.744 24.744 24.744 64.861 0 89.605l-8.292 8.292c-12.329 12.329-13.085 32.418-1.098 45.081 12.437 13.138 33.174 13.353 45.882 0.645l89.328-89.328c62.92-62.92 165.504-63.814 228.081-0.553 61.793 62.468 61.65 163.161-0.431 225.451-12.55 12.592-12.777 32.866-0.207 45.437l4.151 4.151c123.2-83.095 291.881-70.158 400.892 38.854l24.89 24.89c54.734 54.735 61.52 139.258 20.362 201.382l47.973 47.973C1028.129 562.475 1028.129 602.593 1003.386 627.336zM889.796 333.632c-37.49-37.49-98.274-37.49-135.765 0L527.757 559.906c-37.49 37.49-37.49 98.274 0 135.765 29.556 29.556 73.585 35.804 109.269 18.759l-41.839-41.839c-24.744-24.744-24.744-64.861 0-89.604l0.905-0.905c24.744-24.744 64.861-24.744 89.605 0l45.707 45.707 90.51-90.51-45.707-45.707c-24.744-24.744-24.744-64.861 0-89.605l0.905-0.905c24.744-24.744 64.861-24.744 89.604 0l41.839 41.839C925.6 407.218 919.351 363.188 889.796 333.632z" horiz-adv-x="1024" />
+<glyph glyph-name="plus" unicode="" d="M384 384V640H256v-256H0v-128h256v-256h128V256h256V384H384z" horiz-adv-x="640" />
+<glyph glyph-name="podium" unicode="" d="M320 832c-32 0-64-32-64-64s0-64 0-64h-64l-192-192v-128h192l64-384-128-64v-64h512v64l-128 64 64 384h192v128l-192 192h-256v64s14 0 32 0 32 17 32 32-16 32-32 32 0 0-32 0z m0-832l-53 320h118l-1-320h-64z m-224 512l128 128h32v-64h64v64h224l128-128h-576z" horiz-adv-x="768" />
+<glyph glyph-name="primitive-dot" unicode="" d="M-0.088 320c0 141.5 114.5 256 256 256 141.438 0 256-114.5 256-256s-114.562-256-256-256C114.413 64-0.088 178.5-0.088 320z" horiz-adv-x="511.825" />
+<glyph glyph-name="primitive-square" unicode="" d="M512 64H0V576h512V64z" horiz-adv-x="512" />
+<glyph glyph-name="pulse" unicode="" d="M736 320.062L563.188 486.406 422.406 288 352 729.594 152.438 320.062H0V192h230.406L288 307.188l57.594-345.562L576 288l102.375-96H896V320.062H736z" horiz-adv-x="896" />
+<glyph glyph-name="puzzle" unicode="" d="M755.75 256.85c-13.95 9.96-28.52 16.59-43.47 19.92-8.84 1.69-18.06 2.33-27.57 1.81-8.99-0.5-17.56-1.68-25.69-3.52-6.1-1.69-12.22-3.89-18.35-6.59-18.18-8.02-33.89-18.12-46.79-30.33-12.22-12.9-22.32-28.62-30.34-46.79-2.7-6.12-4.9-12.24-6.59-18.34-1.84-8.14-3.03-16.7-3.52-25.69-0.52-9.51 0.12-18.73 1.81-27.57 3.33-14.95 9.96-29.52 19.92-43.47 3.89-5.44 8.08-10.4 12.56-14.88 20.06-20.03 45.83-30.7 75.42-34.11 8.92-1.02 18.12-1.68 26.53-4.48 5.12-1.7 9.16-4.08 12.08-7.02 6.65-6.6 7.63-16.1 2.5-27.24-3.15-6.84-7.7-13.45-12.96-18.84l-2.79-2.86c-3.93-3.92-6.41-6.4-7.05-7.04-3.13-3.16-6.1-6.15-9.06-9.15l-2.96-2.92c-10.52-10.58-21.09-21.12-31.66-31.65-22.76-22.82-45.57-45.58-68.38-68.36-7.5-7.5-15-15-22.5-22.49-3.46-3.45-7.07-6.38-10.78-8.79-1.8-1.22-3.49-2.24-5.18-3.16-19.6-9.89-41.43-5.92-59.24 11.88-5.4 5.4-10.62 10.62-15.85 15.84-30.25 30.25-60.48 60.52-90.77 90.73-8.59 8.57-17.13 17.08-25.68 25.59-6.12 6.09-12.67 11.85-19.56 17.06-5.72 4.33-11.59 7.56-17.46 9.73-21.16 7.32-41.41 2.01-54.67-13.26-3.81-4.8-7-10.47-9.39-16.94-3.43-9.26-4.6-19.47-5.9-29.36-4.9-37.53-25.8-68.43-55.98-82.65-7.48-3.65-15.49-6.29-23.9-7.78-7.95-1.41-15.95-1.71-23.85-1.04-26.61 1.35-49.48 13.09-68.51 32.57-1.68 1.67-2.1 2.09-2.51 2.51-19.48 19.02-31.22 41.9-32.57 68.5-0.68 7.9-0.37 15.9 1.04 23.85 1.49 8.41 4.13 16.43 7.78 23.9 14.22 30.18 45.13 51.07 82.65 55.97 9.89 1.29 20.1 2.47 29.36 5.9 6.94 2.56 12.96 6.05 17.97 10.23 14.54 13.15 19.59 32.63 12.84 52.34-2.78 7.35-6 13.22-10.33 18.94-5.21 6.88-10.97 13.43-17.06 19.55-8.51 8.55-17.03 17.09-25.55 25.63-26.92 26.98-53.84 53.88-80.75 80.78l-10.03 10.03c-5.22 5.22-10.45 10.45-15.26 15.27-18.39 18.4-22.35 40.22-12.46 59.82 0.92 1.69 1.94 3.37 3.08 5.05 2.49 3.84 5.42 7.45 8.87 10.91 7.49 7.5 14.99 15 22.49 22.5 22.77 22.81 45.54 45.62 68.36 68.38 10.53 10.57 21.06 21.14 31.65 31.66l2.92 2.96c2.99 2.97 5.99 5.93 8.98 8.9 0.8 0.81 3.28 3.29 7.2 7.22l2.86 2.79c5.39 5.26 12 9.8 18.84 12.96 11.14 5.13 20.63 4.15 27.24-2.5 2.94-2.92 5.32-6.96 7.02-12.08 2.79-8.41 3.45-17.61 4.48-26.53 3.41-29.59 14.08-55.35 34.11-75.41 4.49-4.48 9.44-8.67 14.88-12.56 13.95-9.96 28.52-16.59 43.47-19.92 8.84-1.69 18.06-2.33 27.57-1.81 8.99 0.5 17.56 1.68 25.69 3.52 6.1 1.69 12.22 3.89 18.35 6.59 18.18 8.02 33.89 18.12 46.79 30.33 12.22 12.9 22.32 28.62 30.34 46.79 2.7 6.12 4.9 12.24 6.59 18.34 1.84 8.14 3.03 16.7 3.52 25.69 0.52 9.51-0.12 18.73-1.81 27.57-3.33 14.95-9.96 29.52-19.92 43.47-3.89 5.44-8.08 10.4-12.56 14.88-20.06 20.03-45.83 30.7-75.42 34.11-8.92 1.02-18.12 1.68-26.53 4.48-5.12 1.7-9.16 4.08-12.08 7.02-6.65 6.6-7.63 16.1-2.5 27.24 3.15 6.84 7.7 13.45 12.96 18.84l2.79 2.86c3.93 3.92 6.41 6.4 7.05 7.04 3.13 3.16 6.1 6.15 9.06 9.15l2.96 2.92c10.52 10.58 21.09 21.12 31.66 31.65 22.76 22.82 45.57 45.58 68.38 68.35 7.5 7.5 15 15 22.5 22.49 3.46 3.45 7.07 6.38 10.78 8.79 1.8 1.22 3.49 2.24 5.18 3.16 19.6 9.89 41.43 5.92 59.24-11.88 5.4-5.4 10.62-10.62 15.85-15.84 30.25-30.25 60.48-60.52 90.77-90.73 8.59-8.57 17.13-17.08 25.68-25.59 6.12-6.09 12.67-11.85 19.56-17.06 5.72-4.33 11.59-7.56 17.46-9.73 21.16-7.32 41.41-2.01 54.67 13.26 3.81 4.8 7 10.47 9.39 16.94 3.43 9.26 4.6 19.47 5.9 29.36 4.9 37.53 25.8 68.43 55.98 82.65 7.48 3.65 15.49 6.28 23.9 7.78 7.95 1.41 15.95 1.71 23.85 1.04 26.61-1.35 49.48-13.09 68.51-32.57 1.68-1.67 2.1-2.09 2.51-2.51 19.48-19.02 31.22-41.9 32.57-68.5 0.68-7.9 0.37-15.9-1.04-23.85-1.49-8.41-4.13-16.43-7.78-23.9-14.22-30.18-45.13-51.07-82.65-55.97-9.89-1.29-20.1-2.47-29.36-5.9-6.94-2.56-12.96-6.05-17.97-10.23-14.54-13.15-19.59-32.63-12.84-52.34 2.78-7.35 6-13.22 10.33-18.94 5.21-6.88 10.97-13.43 17.06-19.55 8.51-8.55 17.03-17.09 25.55-25.63 30.26-30.33 60.54-60.56 90.78-90.81 5.22-5.22 10.45-10.45 15.26-15.27 18.39-18.4 22.35-40.22 12.46-59.82-0.92-1.69-1.94-3.37-3.08-5.05-2.49-3.84-5.42-7.45-8.87-10.91-7.49-7.5-14.99-15-22.49-22.5-22.77-22.81-45.54-45.62-68.36-68.38-10.53-10.57-21.06-21.14-31.65-31.66l-2.92-2.96c-2.99-2.97-5.99-5.93-8.98-8.9-0.8-0.81-3.28-3.29-7.2-7.22l-2.86-2.79c-5.39-5.26-12-9.8-18.84-12.96-11.14-5.13-20.63-4.15-27.24 2.5-2.94 2.92-5.32 6.96-7.02 12.08-2.79 8.41-3.45 17.61-4.48 26.53-3.41 29.59-14.08 55.35-34.11 75.41C766.15 248.76999999999998 761.19 252.97000000000003 755.75 256.85z" horiz-adv-x="1024" />
+<glyph glyph-name="question" unicode="" d="M448 64h128v128h-128v-128z m64 512c-96 0-192-96-192-192h128c0 32 32 64 64 64s64-32 64-64c0-64-128-64-128-128h128c64 22 128 64 128 160s-96 160-192 160z m0 256c-283 0-512-229-512-512s229-512 512-512 512 229 512 512-229 512-512 512z m0-896c-212 0-384 172-384 384s172 384 384 384 384-172 384-384-172-384-384-384z" horiz-adv-x="1024" />
+<glyph glyph-name="quote" unicode="" d="M0 320v-256h256V320H128c0 0 0 128 128 128V576C256 576 0 576 0 320zM640 448V576c0 0-256 0-256-256v-256h256V320H512C512 320 512 448 640 448z" horiz-adv-x="640" />
+<glyph glyph-name="radio-tower" unicode="" d="M306.838 441.261c15.868 16.306 15.868 42.731 0 59.037-20.521 21.116-30.643 48.417-30.705 76.124 0.062 27.77 10.183 55.039 30.705 76.186 15.868 16.337 15.868 42.764 0 59.069-7.934 8.184-18.272 12.275-28.706 12.275-10.371 0-20.804-4.029-28.738-12.213-36.266-37.297-54.633-86.433-54.57-135.317-0.062-48.792 18.305-97.927 54.57-135.161C265.262 424.955 290.97 424.955 306.838 441.261zM149.093 798.858c-8.121 8.309-18.68 12.463-29.3 12.463-10.558 0-21.179-4.154-29.237-12.463C30.8 737.509 0.751 656.856 0.813 576.422 0.751 496.081 30.8 415.272 90.494 353.985c16.181-16.618 42.356-16.618 58.537 0 16.118 16.587 16.118 43.513 0 60.067-43.7 44.98-65.44 103.456-65.44 162.368s21.74 117.449 65.44 162.368C165.149 755.439 165.149 782.365 149.093 798.858zM513.031 472.153c57.351 0 103.956 46.574 103.956 103.956 0 57.382-46.605 103.955-103.956 103.955-57.381 0-103.956-46.573-103.956-103.955C409.076 518.727 455.65 472.153 513.031 472.153zM933.539 798.233c-16.181 16.618-42.355 16.618-58.475 0-16.181-16.587-16.181-43.513 0-60.068 43.668-44.918 65.409-103.456 65.409-162.368 0-58.85-21.805-117.387-65.473-162.306-16.117-16.618-16.117-43.575 0.062-60.068 8.059-8.309 18.616-12.463 29.237-12.463 10.558 0 21.178 4.154 29.236 12.463 59.726 61.287 89.774 142.096 89.649 222.437C1023.313 656.138 993.264 736.947 933.539 798.233zM513.281 389.127L513.281 389.127c-26.489-0.062-53.04 6.466-77.091 19.429L235.057-127.59000000000003h95.209l54.819 63.973h255.891l53.977-63.973h95.272L589.124 408.431C565.384 395.655 539.395 389.127 513.281 389.127zM512.656 358.483L577.004 128.29999999999995H449.059L512.656 358.483zM385.086 0.3550000000000182l63.974 63.973h127.944l63.974-63.973H385.086zM717.194 710.958c-15.868-16.306-15.868-42.731 0-59.037 20.491-21.116 30.611-48.511 30.674-76.124-0.062-27.77-10.183-55.102-30.674-76.187-15.868-16.336-15.868-42.763 0-59.068 7.871-8.184 18.242-12.213 28.737-12.213 10.309 0 20.741 4.029 28.675 12.213 36.298 37.234 54.665 86.433 54.54 135.255 0.125 48.792-18.181 97.927-54.54 135.161C758.801 727.264 733.062 727.264 717.194 710.958z" horiz-adv-x="1024" />
+<glyph glyph-name="repo" unicode="" d="M320 576h-64v-64h64v64z m0 128h-64v-64h64v64z m384 128c-32 0-608 0-640 0s-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l96 96 96-96v128s288 0 320 0 64 32 64 64 0 736 0 768-32 64-64 64z m0-800c0-16-15-32-32-32s-288 0-288 0v64h-192v-64s-79 0-96 0-32 17-32 32 0 96 0 96h640s0-80 0-96z m0 160h-512v576h513l-1-576z m-384 128h-64v-64h64v64z m0 128h-64v-64h64v64z" horiz-adv-x="768" />
+<glyph glyph-name="repo-clone" unicode="" d="M320 448h-64v-64h64v64z m-128 320h256v64s-352 0-384 0-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l96 96 96-96v128s286 0 320 0 64 32 64 64 0 192 0 192h-576v576z m512-640s0-79 0-96-14-32-32-32-288 0-288 0v64h-192v-64s-80 0-96 0-32 16-32 32 0 96 0 96h640z m-384 448h-64v-64h64v64z m-64-320h64v64h-64v-64z m704 576c-32 0-288 0-320 0s-64-32-64-64 0-352 0-384 32-64 64-64 64 0 64 0v-64l32 32 32-32v64s160 0 192 0 64 32 64 64 0 352 0 384-32 64-64 64z m-256-448s-15 0-32 0-32 15-32 32 0 32 0 32h64v-64z m256 32c0-16-15-32-32-32s-160 0-160 0v64h192s0-16 0-32z m0 96h-256v256h224s32 0 32-32 0-224 0-224z m-640 192h-64v-64h64v64z" horiz-adv-x="1024" />
+<glyph glyph-name="repo-force-push" unicode="" d="M768 768c0 32-32 64-64 64s-608 0-640 0-64-32-64-64 0-768 0-768 0 32 0 0 32-64 64-64 128 0 128 0v-128l128 128v128h-128v-64s-79 0-96 0-32 15-32 32 0 96 0 96h256v64h-128v576h512v-576h-128v-64h128s0-80 0-96-15-32-32-32-96 0-96 0v-64s96 0 128 0 64 32 64 64 0 736 0 768z m-272-320h144l-192 256-192-256h144l-144-192h128v-448h128v448h128l-144 192z" horiz-adv-x="767.896" />
+<glyph glyph-name="repo-forked" unicode="" d="M768 704c0 71-57 128-128 128s-128-57-128-128c0-47 26-89 64-111v-106l-192-212-192 212v106c38 22 64 63 64 111 0 71-57 128-128 128s-128-57-128-128c0-47 26-89 64-111v-156l256-282v-109c-38-22-64-63-64-111 0-71 57-128 128-128s128 57 128 128c0 47-26 89-64 111v109l256 282v156c38 22 64 63 64 111z m-640 63c34 0 62-28 62-62s-28-62-62-62-62 28-62 62 28 62 62 62z m256-891c-34 0-62 28-62 62s28 62 62 62 62-28 62-62-28-62-62-62z m256 891c34 0 62-28 62-62s-28-62-62-62-62 28-62 62 28 62 62 62z" horiz-adv-x="768" />
+<glyph glyph-name="repo-pull" unicode="" d="M1024 512l-192 192v-128h-384v-128h384v-128l192 192z m-320-320h-512v576h512v-128h64s0 96 0 128-32 64-64 64-608 0-640 0-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l96 96 96-96v128s288 0 320 0 64 32 64 64 0 384 0 384h-64v-192z m0-160c0-15-15-32-32-32s-288 0-288 0v64h-192v-64s-79 0-96 0-32 16-32 32 0 96 0 96h640s0-81 0-96z m-384 544h-64v-64h64v64z m0 128h-64v-64h64v64z m0-256h-64v-64h64v64z m-64-192h64v64h-64v-64z" horiz-adv-x="1024" />
+<glyph glyph-name="repo-push" unicode="" d="M448 512l-192-256h128v-448h128v448h128l-192 256z m-192 0h64v64h-64v-64z m64 192h-64v-64h64v64z m384 128c-32 0-608 0-640 0s-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l128 128v128h-128v-64s-79 0-96 0-32 14-32 32 0 96 0 96h256v64h-128v576h513l-1-576h-128v-64h128s0-79 0-96-15-32-32-32-96 0-96 0v-64s96 0 128 0 64 32 64 64 0 736 0 768-32 64-64 64z" horiz-adv-x="768" />
+<glyph glyph-name="rocket" unicode="" d="M716.737 707.944c-71.926-41.686-148.041-96.13-218.436-166.555-45-45.031-81.213-88.78-110.39-129.778L209.538 378.65 0.047 169.00300000000004l186.818-5.815 131.562 131.562c-46.439-96.224-50.536-160.019-50.536-160.019l58.854-58.792c0 0 65.827 6.255 162.737 53.163L355.107-5.119000000000028l5.88-186.881 209.585 209.521 33.086 179.252c41.403 29.02 85.185 65.046 129.716 109.545 70.425 70.455 124.837 146.541 166.555 218.466-45.97 9.351-88.125 28.488-121.397 61.668C745.257 619.819 725.994 661.975 716.737 707.944zM786.161 745.157c5.004-45 19.952-81.274 44.78-105.98 24.769-24.985 60.98-39.902 106.138-44.844C1003.063 727.677 1023.953 832 1023.953 832S919.63 811.142 786.161 745.157z" horiz-adv-x="1024" />
+<glyph glyph-name="rss" unicode="" d="M128 192C57.344 192 0 134.625 0 64s57.344-128 128-128 128 57.375 128 128S198.656 192 128 192zM128 448c0 0-64-2-64-64s64-64 64-64c141.375 0 256-114.625 256-256 0 0 0-64 64-64s64 64 64 64C512 276 340.031 448 128 448zM128 704c0 0-64 0-64-64s64-64 64-64c282.75 0 512-229.25 512-512 0 0 0-64 64-64s64 64 64 64C768 417.406 481.5 704 128 704z" horiz-adv-x="768" />
+<glyph glyph-name="ruby" unicode="" d="M768 704H256L0 448l512-512 512 512L768 704zM128 448l192 192h384l192-192L512 64 128 448zM704 576H512v-448l320 320L704 576z" horiz-adv-x="1024" />
+<glyph glyph-name="screen-full" unicode="" d="M128 64h639.875V576H128V64zM255.938 448h384v-256h-384V448zM64 639.938h191.938v64H0V448h64V639.938zM64 192H0v-255.938h255.938V0H64V192zM639.938 703.938v-64h191.938V448h64V703.938H639.938zM831.875 0H639.938v-63.938h255.938V192h-64V0z" horiz-adv-x="895.875" />
+<glyph glyph-name="screen-normal" unicode="" d="M127.938 640.062H0v-64h191.938V768h-64V640.062zM0-0.06200000000001182h127.938V-128h64V63.93799999999999H0V-0.06200000000001182zM768.062 640.062V768h-64v-191.938H896v64H768.062zM704.062-128h64V-0.06200000000001182H896v64H704.062V-128zM192.062 128H704V512H192.062V128zM320 384h256v-128H320V384z" horiz-adv-x="896" />
+<glyph glyph-name="search" unicode="" d="M960 0L710.875 249.125C746.438 307.188 768 374.844 768 448 768 660.031 596 832 384 832 171.969 832 0 660.031 0 448c0-212 171.969-384 384-384 73.156 0 140.812 21.562 198.875 57L832-128c17.5-17.5 46.5-17.375 64 0l64 64C977.5-46.5 977.5-17.5 960 0zM384 192c-141.375 0-256 114.625-256 256s114.625 256 256 256 256-114.625 256-256S525.375 192 384 192z" horiz-adv-x="973.125" />
+<glyph glyph-name="server" unicode="" d="M704 448h-640c-35 0-64-32-64-64v-128c0-32 32-64 64-64h640c32 0 64 32 64 64v128c0 32-32 64-64 64z m-576-192h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m192-128h-640c-35 0-64-32-64-64v-128c0-32 32-64 64-64h640c32 0 64 32 64 64v128c0 32-32 64-64 64z m-576-192h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m192 832h-640c-35 0-64-32-64-64v-128c0-32 32-64 64-64h640c32 0 64 32 64 64v128c0 32-32 64-64 64z m-576-192h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m192 64h-64v64h64v-64z" horiz-adv-x="768" />
+<glyph glyph-name="settings" unicode="" d="M64-64h128V128H64V-64zM192 704H64v-320h128V704zM512 704H384v-128h128V704zM0 192h256V320H0V192zM384-64h128V320H384V-64zM320 384h256V512H320V384zM832 704H704v-384h128V704zM640 256v-128h256V256H640zM704-64h128V64H704V-64z" horiz-adv-x="896" />
+<glyph glyph-name="sign-in" unicode="" d="M640 256L640 384 896 384 896 512 640 512 640 640 448 496 448 640 192 768 704 768 704 576 768 576 768 832 64 832 64 0 448-192 448 0 768 0 768 320 704 320 704 64 448 64 448 400z" horiz-adv-x="896" />
+<glyph glyph-name="sign-out" unicode="" d="M640 64H384V640L128 768h512v-192h64V832H0v-832l384-192V0h320V320h-64V64zM1024 448L768 640v-128H512v-128h256v-128L1024 448z" horiz-adv-x="1024" />
+<glyph glyph-name="split" unicode="" d="M448 576l-256 256-192-192 311-300c15 81 43 136 133 230l5 6z m128 256l133-133-197-197c-99-99-128-162-128-309v-384h256v384c0 52 19 94 53 128l197 197 133-133v448h-448z" horiz-adv-x="1024" />
+<glyph glyph-name="squirrel" unicode="" d="M768 768c-141.385 0-256-83.75-256-186.875C512 457.25 544 387 512 192c0 288-177 405.783-256 405.783 3.266 32.17-30.955 42.217-30.955 42.217s-14-7.124-19.354-21.583c-17.231 20.053-36.154 17.54-36.154 17.54l-8.491-37.081c0 0-117.045-40.876-118.635-206.292C56 371 141.311 353.898 201.887 364.882c57.157-2.956 42.991-50.648 30.193-63.446C178.083 247.438 128 320 64 320s-64-64 0-64 64-64 192-64c-198-77 0-256 0-256h-64c-64 0-64-64-64-64s256 0 384 0c192 0 320 64 320 222.182 0 54.34-27.699 114.629-64 162.228C697.057 349.433 782.453 427.566 832 384s192-64 192 128C1024 653.385 909.385 768 768 768zM160 448c-17.674 0-32 14.327-32 32 0 17.674 14.326 32 32 32 17.673 0 32-14.326 32-32C192 462.327 177.673 448 160 448z" horiz-adv-x="1024" />
+<glyph glyph-name="star" unicode="" d="M896 448l-313.5 40.781L448 768 313.469 488.781 0 448l230.469-208.875L171-63.93799999999999l277 148.812 277.062-148.812L665.5 239.125 896 448z" horiz-adv-x="896" />
+<glyph glyph-name="steps" unicode="" d="M136 768C60.89 768 0 667.71 0 544c0-68.83 17.02-141.84 34-254.54C47.3 201.16999999999996 79.67 128 136 128s94.08 48.79 94.08 137.97c0 30.37-24.97 78.75-26.08 120.03-2.02 74.46 49.93 104.17 49.93 173C253.93 682.71 211.1 768 136 768zM502.97 512c-75.1 0-117.93-85.29-117.93-209 0-68.83 51.95-98.54 49.93-173-1.109-41.28-26.08-89.66-26.08-120.03 0-89.18 37.75-137.97 94.08-137.97s88.7 73.17 102 161.46c16.98 112.7 34 185.71 34 254.54C638.97 411.71 578.08 512 502.97 512z" horiz-adv-x="640" />
+<glyph glyph-name="stop" unicode="" d="M704 832H320L0 512v-384l320-320h384l320 320V512L704 832zM896 192L640-64H384L128 192V448l256 256h256l256-256V192zM448 256h128V576H448V256zM448 64h128V192H448V64z" horiz-adv-x="1024" />
+<glyph glyph-name="sync" unicode="" d="M655.461 358.531c11.875-81.719-13.062-167.781-76.812-230.594-94.188-92.938-239.5-104.375-346.375-34.562l74.875 73L31.96 204.75 70.367-64l84.031 80.5c150.907-111.25 364.938-100.75 502.063 34.562 79.5 78.438 115.75 182.562 111.25 285.312L655.461 358.531zM189.46 511.938c94.156 92.938 239.438 104.438 346.313 34.562l-75-72.969 275.188-38.406L697.586 704l-83.938-80.688C462.711 734.656 248.742 724.031 111.585 588.75 32.085 510.344-4.133 406.219 0.335 303.5l112.25-22.125C100.71 363.125 125.71 449.094 189.46 511.938z" horiz-adv-x="768.051" />
+<glyph glyph-name="tag" unicode="" d="M384 768H128L0 640v-256l512-512 384 384L384 768zM64 416V608l96 96h192l448-448L512-32 64 416zM448 512L256 320l256-256 192 192L448 512zM352 320l96 96 160-160-96-96L352 320zM320 544c0 53-43 96-96 96s-96-43-96-96 43-96 96-96S320 491 320 544zM224 512c-17.656 0-32 14.344-32 32s14.344 32 32 32 32-14.344 32-32S241.656 512 224 512z" horiz-adv-x="896" />
+<glyph glyph-name="telescope" unicode="" d="M76 409c32 8 229 59 229 59-1-6-2-19-2-19 0-71 49-128 128-128s128 59 128 128c0 11-8 22-19 32l49-3s7 2 31 8c-51-14-108 31-126 99s8 135 60 149c-24-6-31-8-31-8l-168-110c-34-9-55-46-46-80 2-9 7-17 12-23-7-12-12-26-15-40-27 1-51 19-59 46-9 34 11 69 45 78l-245-65c-34-9-54-43-45-77s41-54 73-46z m419-153h-128v-64l-320-320h128l192 128v-128h128v128l192-128h128l-320 320v64z m429 448c-18 68-70 110-122 96-69-18-98-28-186-51-51-14-79-80-61-148s74-115 125-102c87 23 117 33 186 51 51 14 76 85 58 154z m-70-90c-17-5-42 17-51 51s-4 66 13 70 42-17 51-51 4-66-13-70z" horiz-adv-x="929.875" />
+<glyph glyph-name="terminal" unicode="" d="M831 705H63c-35.35 0-64-28.65-64-64v-640c0-35.35 28.65-64 64-64h768c35.35 0 64 28.65 64 64V641C895 676.35 866.35 705 831 705zM127 257l128 128L127 513l64 64 192-192L191 193 127 257zM639 193H383v64h256V193z" horiz-adv-x="896" />
+<glyph glyph-name="three-bars" unicode="" d="M0 640v-128h768v128h-768z m0-384h768v128h-768v-128z m0-256h768v128h-768v-128z" horiz-adv-x="768" />
+<glyph glyph-name="tools" unicode="" d="M286.547 366.984c16.843-16.812 81.716-85.279 81.716-85.279l35.968 37.093-56.373 58.248L456.072 491.98c0 0-48.842 47.623-27.468 28.655 20.438 75.903 1.812 160.589-55.842 220.243C315.608 800.064 234.392 819.47 161.425 799.096l123.653-127.715-32.53-125.309-121.06-33.438L7.898 640.3820000000001c-19.718-75.436-0.969-159.339 56.311-218.556C124.302 359.703 210.83 341.453 286.547 366.984zM698.815 242.769L549.694 95.46100000000001l245.932-254.805c20.062-20.812 46.498-31.188 72.872-31.188 26.25 0 52.624 10.375 72.811 31.188 40.249 41.624 40.249 108.997 0 150.62L698.815 242.769zM1023.681 670.162L867.06 832.001 405.387 354.703l56.373-58.248L185.425 10.839000000000055l-63.154-33.749-89.217-145.559 22.719-23.562 140.839 92.247 32.655 65.312 276.336 285.554 56.404-58.248L1023.681 670.162z" horiz-adv-x="1024" />
+<glyph glyph-name="trashcan" unicode="" d="M704 704H448c0 0 0 24.057 0 32 0 17.673-14.327 32-32 32s-32-14.327-32-32c0-17.673 0-32 0-32H128c-35.346 0-64-28.654-64-64v-64c0-35.346 28.654-64 64-64v-576c0-35.346 28.654-64 64-64h448c35.346 0 64 28.654 64 64V512c35.346 0 64 28.654 64 64v64C768 675.346 739.346 704 704 704zM640-32c0-17.673-14.327-32-32-32H224c-17.673 0-32 14.327-32 32V512h64v-480c0-17.673 14.327-32 32-32s32 14.327 32 32l0.387 480H384v-480c0-17.673 14.327-32 32-32s32 14.327 32 32l0.387 480h64L512 32c0-17.673 14.327-32 32-32s32 14.327 32 32V512h64V-32zM704 592c0-8.837-7.163-16-16-16H144c-8.837 0-16 7.163-16 16v32c0 8.837 7.163 16 16 16h544c8.837 0 16-7.163 16-16V592z" horiz-adv-x="768" />
+<glyph glyph-name="triangle-down" unicode="" d="M0 448l383.75-383.75L767.5 448H0z" horiz-adv-x="767.5" />
+<glyph glyph-name="triangle-left" unicode="" d="M0 320.125l383.75-383.75v767.5L0 320.125z" horiz-adv-x="383.75" />
+<glyph glyph-name="triangle-right" unicode="" d="M0.062 703.75L383.812 320 0.062-63.75V703.75z" horiz-adv-x="383.875" />
+<glyph glyph-name="triangle-up" unicode="" d="M383.75 576L0 192.25h767.5L383.75 576z" horiz-adv-x="767.5" />
+<glyph glyph-name="unfold" unicode="" d="M384 448h128V640h128L448 832 256 640h128V448zM576 576v-64h224L672 384H224L96 512h224v64H0v-63.999L160 352 0 192v-64h320v64H96l128 128h448l128-128H576v-64h320v64L736 352l160 160.001V576H576zM512 256H384v-192H256l192-192 192 192H512V256z" horiz-adv-x="896" />
+<glyph glyph-name="unmute" unicode="" d="M128 448H0v-256h128l256-192h64V640h-64L128 448zM538.51 410.51c-12.496 12.497-32.758 12.497-45.255 0-12.496-12.496-12.496-32.758 0-45.255 24.994-24.993 24.994-65.516 0-90.51-12.496-12.496-12.496-32.758 0-45.255 12.497-12.496 32.759-12.496 45.255 0C588.497 279.47900000000004 588.497 360.523 538.51 410.51zM629.02 501.019c-12.495 12.497-32.758 12.497-45.255 0-12.495-12.496-12.495-32.758 0-45.255 74.981-74.98 74.981-196.548 0-271.528-12.495-12.497-12.495-32.76 0-45.256 12.497-12.496 32.76-12.496 45.255 0C728.994 238.95399999999995 728.994 401.045 629.02 501.019zM719.529 591.529c-12.497 12.497-32.76 12.497-45.255 0-12.496-12.496-12.496-32.758 0-45.255 124.968-124.968 124.968-327.58 0-452.548-12.496-12.497-12.496-32.759 0-45.255 12.495-12.497 32.758-12.497 45.255 0C869.49 198.433 869.49 441.568 719.529 591.529z" horiz-adv-x="896" />
+<glyph glyph-name="versions" unicode="" d="M0 128h128v64H64V448h64v64H0V128zM384 640v-640h512V640H384zM768 128H512V512h256V128zM192 64h128v64h-64V512h64v64H192V64z" horiz-adv-x="896" />
+<glyph glyph-name="x" unicode="" d="M640 512L512 640 320 448 128 640 0 512l192-192L0 128l128-128 192 192 192-192 128 128L448 320 640 512z" horiz-adv-x="640" />
+<glyph glyph-name="zap" unicode="⚡" d="M640 384H384L576 832 0 256h256L64-192 640 384z" horiz-adv-x="640" />
+</font>
+</defs>
+</svg>
diff --git a/src/main/resources/octicons/octicons.ttf b/src/main/resources/octicons/octicons.ttf
new file mode 100644
index 0000000..189ca28
--- /dev/null
+++ b/src/main/resources/octicons/octicons.ttf
Binary files differ
diff --git a/src/main/resources/octicons/octicons.woff b/src/main/resources/octicons/octicons.woff
new file mode 100644
index 0000000..2b770e4
--- /dev/null
+++ b/src/main/resources/octicons/octicons.woff
Binary files differ
diff --git a/src/main/resources/octicons/sprockets-octicons.scss b/src/main/resources/octicons/sprockets-octicons.scss
new file mode 100644
index 0000000..0e518fc
--- /dev/null
+++ b/src/main/resources/octicons/sprockets-octicons.scss
@@ -0,0 +1,230 @@
+@font-face {
+ font-family: 'octicons';
+ src: font-url('octicons.eot?#iefix') format('embedded-opentype'),
+ font-url('octicons.woff') format('woff'),
+ font-url('octicons.ttf') format('truetype'),
+ font-url('octicons.svg#octicons') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+
+// .octicon is optimized for 16px.
+// .mega-octicon is optimized for 32px but can be used larger.
+.octicon, .mega-octicon {
+ font: normal normal normal 16px/1 octicons;
+ display: inline-block;
+ text-decoration: none;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.mega-octicon { font-size: 32px; }
+
+.octicon-alert:before { content: '\f02d'} /* */
+.octicon-alignment-align:before { content: '\f08a'} /* */
+.octicon-alignment-aligned-to:before { content: '\f08e'} /* */
+.octicon-alignment-unalign:before { content: '\f08b'} /* */
+.octicon-arrow-down:before { content: '\f03f'} /* */
+.octicon-arrow-left:before { content: '\f040'} /* */
+.octicon-arrow-right:before { content: '\f03e'} /* */
+.octicon-arrow-small-down:before { content: '\f0a0'} /* */
+.octicon-arrow-small-left:before { content: '\f0a1'} /* */
+.octicon-arrow-small-right:before { content: '\f071'} /* */
+.octicon-arrow-small-up:before { content: '\f09f'} /* */
+.octicon-arrow-up:before { content: '\f03d'} /* */
+.octicon-beer:before { content: '\f069'} /* */
+.octicon-book:before { content: '\f007'} /* */
+.octicon-bookmark:before { content: '\f07b'} /* */
+.octicon-briefcase:before { content: '\f0d3'} /* */
+.octicon-broadcast:before { content: '\f048'} /* */
+.octicon-browser:before { content: '\f0c5'} /* */
+.octicon-bug:before { content: '\f091'} /* */
+.octicon-calendar:before { content: '\f068'} /* */
+.octicon-check:before { content: '\f03a'} /* */
+.octicon-checklist:before { content: '\f076'} /* */
+.octicon-chevron-down:before { content: '\f0a3'} /* */
+.octicon-chevron-left:before { content: '\f0a4'} /* */
+.octicon-chevron-right:before { content: '\f078'} /* */
+.octicon-chevron-up:before { content: '\f0a2'} /* */
+.octicon-circle-slash:before { content: '\f084'} /* */
+.octicon-circuit-board:before { content: '\f0d6'} /* */
+.octicon-clippy:before { content: '\f035'} /* */
+.octicon-clock:before { content: '\f046'} /* */
+.octicon-cloud-download:before { content: '\f00b'} /* */
+.octicon-cloud-upload:before { content: '\f00c'} /* */
+.octicon-code:before { content: '\f05f'} /* */
+.octicon-color-mode:before { content: '\f065'} /* */
+.octicon-comment-add:before,
+.octicon-comment:before { content: '\f02b'} /* */
+.octicon-comment-discussion:before { content: '\f04f'} /* */
+.octicon-credit-card:before { content: '\f045'} /* */
+.octicon-dash:before { content: '\f0ca'} /* */
+.octicon-dashboard:before { content: '\f07d'} /* */
+.octicon-database:before { content: '\f096'} /* */
+.octicon-device-camera:before { content: '\f056'} /* */
+.octicon-device-camera-video:before { content: '\f057'} /* */
+.octicon-device-desktop:before { content: '\f27c'} /* */
+.octicon-device-mobile:before { content: '\f038'} /* */
+.octicon-diff:before { content: '\f04d'} /* */
+.octicon-diff-added:before { content: '\f06b'} /* */
+.octicon-diff-ignored:before { content: '\f099'} /* */
+.octicon-diff-modified:before { content: '\f06d'} /* */
+.octicon-diff-removed:before { content: '\f06c'} /* */
+.octicon-diff-renamed:before { content: '\f06e'} /* */
+.octicon-ellipsis:before { content: '\f09a'} /* */
+.octicon-eye-unwatch:before,
+.octicon-eye-watch:before,
+.octicon-eye:before { content: '\f04e'} /* */
+.octicon-file-binary:before { content: '\f094'} /* */
+.octicon-file-code:before { content: '\f010'} /* */
+.octicon-file-directory:before { content: '\f016'} /* */
+.octicon-file-media:before { content: '\f012'} /* */
+.octicon-file-pdf:before { content: '\f014'} /* */
+.octicon-file-submodule:before { content: '\f017'} /* */
+.octicon-file-symlink-directory:before { content: '\f0b1'} /* */
+.octicon-file-symlink-file:before { content: '\f0b0'} /* */
+.octicon-file-text:before { content: '\f011'} /* */
+.octicon-file-zip:before { content: '\f013'} /* */
+.octicon-flame:before { content: '\f0d2'} /* */
+.octicon-fold:before { content: '\f0cc'} /* */
+.octicon-gear:before { content: '\f02f'} /* */
+.octicon-gift:before { content: '\f042'} /* */
+.octicon-gist:before { content: '\f00e'} /* */
+.octicon-gist-secret:before { content: '\f08c'} /* */
+.octicon-git-branch-create:before,
+.octicon-git-branch-delete:before,
+.octicon-git-branch:before { content: '\f020'} /* */
+.octicon-git-commit:before { content: '\f01f'} /* */
+.octicon-git-compare:before { content: '\f0ac'} /* */
+.octicon-git-merge:before { content: '\f023'} /* */
+.octicon-git-pull-request-abandoned:before,
+.octicon-git-pull-request:before { content: '\f009'} /* */
+.octicon-globe:before { content: '\f0b6'} /* */
+.octicon-graph:before { content: '\f043'} /* */
+.octicon-heart:before { content: '\2665'} /* ♥ */
+.octicon-history:before { content: '\f07e'} /* */
+.octicon-home:before { content: '\f08d'} /* */
+.octicon-horizontal-rule:before { content: '\f070'} /* */
+.octicon-hourglass:before { content: '\f09e'} /* */
+.octicon-hubot:before { content: '\f09d'} /* */
+.octicon-inbox:before { content: '\f0cf'} /* */
+.octicon-info:before { content: '\f059'} /* */
+.octicon-issue-closed:before { content: '\f028'} /* */
+.octicon-issue-opened:before { content: '\f026'} /* */
+.octicon-issue-reopened:before { content: '\f027'} /* */
+.octicon-jersey:before { content: '\f019'} /* */
+.octicon-jump-down:before { content: '\f072'} /* */
+.octicon-jump-left:before { content: '\f0a5'} /* */
+.octicon-jump-right:before { content: '\f0a6'} /* */
+.octicon-jump-up:before { content: '\f073'} /* */
+.octicon-key:before { content: '\f049'} /* */
+.octicon-keyboard:before { content: '\f00d'} /* */
+.octicon-law:before { content: '\f0d8'} /* */
+.octicon-light-bulb:before { content: '\f000'} /* */
+.octicon-link:before { content: '\f05c'} /* */
+.octicon-link-external:before { content: '\f07f'} /* */
+.octicon-list-ordered:before { content: '\f062'} /* */
+.octicon-list-unordered:before { content: '\f061'} /* */
+.octicon-location:before { content: '\f060'} /* */
+.octicon-gist-private:before,
+.octicon-mirror-private:before,
+.octicon-git-fork-private:before,
+.octicon-lock:before { content: '\f06a'} /* */
+.octicon-logo-github:before { content: '\f092'} /* */
+.octicon-mail:before { content: '\f03b'} /* */
+.octicon-mail-read:before { content: '\f03c'} /* */
+.octicon-mail-reply:before { content: '\f051'} /* */
+.octicon-mark-github:before { content: '\f00a'} /* */
+.octicon-markdown:before { content: '\f0c9'} /* */
+.octicon-megaphone:before { content: '\f077'} /* */
+.octicon-mention:before { content: '\f0be'} /* */
+.octicon-microscope:before { content: '\f089'} /* */
+.octicon-milestone:before { content: '\f075'} /* */
+.octicon-mirror-public:before,
+.octicon-mirror:before { content: '\f024'} /* */
+.octicon-mortar-board:before { content: '\f0d7'} /* */
+.octicon-move-down:before { content: '\f0a8'} /* */
+.octicon-move-left:before { content: '\f074'} /* */
+.octicon-move-right:before { content: '\f0a9'} /* */
+.octicon-move-up:before { content: '\f0a7'} /* */
+.octicon-mute:before { content: '\f080'} /* */
+.octicon-no-newline:before { content: '\f09c'} /* */
+.octicon-octoface:before { content: '\f008'} /* */
+.octicon-organization:before { content: '\f037'} /* */
+.octicon-package:before { content: '\f0c4'} /* */
+.octicon-paintcan:before { content: '\f0d1'} /* */
+.octicon-pencil:before { content: '\f058'} /* */
+.octicon-person-add:before,
+.octicon-person-follow:before,
+.octicon-person:before { content: '\f018'} /* */
+.octicon-pin:before { content: '\f041'} /* */
+.octicon-playback-fast-forward:before { content: '\f0bd'} /* */
+.octicon-playback-pause:before { content: '\f0bb'} /* */
+.octicon-playback-play:before { content: '\f0bf'} /* */
+.octicon-playback-rewind:before { content: '\f0bc'} /* */
+.octicon-plug:before { content: '\f0d4'} /* */
+.octicon-repo-create:before,
+.octicon-gist-new:before,
+.octicon-file-directory-create:before,
+.octicon-file-add:before,
+.octicon-plus:before { content: '\f05d'} /* */
+.octicon-podium:before { content: '\f0af'} /* */
+.octicon-primitive-dot:before { content: '\f052'} /* */
+.octicon-primitive-square:before { content: '\f053'} /* */
+.octicon-pulse:before { content: '\f085'} /* */
+.octicon-puzzle:before { content: '\f0c0'} /* */
+.octicon-question:before { content: '\f02c'} /* */
+.octicon-quote:before { content: '\f063'} /* */
+.octicon-radio-tower:before { content: '\f030'} /* */
+.octicon-repo-delete:before,
+.octicon-repo:before { content: '\f001'} /* */
+.octicon-repo-clone:before { content: '\f04c'} /* */
+.octicon-repo-force-push:before { content: '\f04a'} /* */
+.octicon-gist-fork:before,
+.octicon-repo-forked:before { content: '\f002'} /* */
+.octicon-repo-pull:before { content: '\f006'} /* */
+.octicon-repo-push:before { content: '\f005'} /* */
+.octicon-rocket:before { content: '\f033'} /* */
+.octicon-rss:before { content: '\f034'} /* */
+.octicon-ruby:before { content: '\f047'} /* */
+.octicon-screen-full:before { content: '\f066'} /* */
+.octicon-screen-normal:before { content: '\f067'} /* */
+.octicon-search-save:before,
+.octicon-search:before { content: '\f02e'} /* */
+.octicon-server:before { content: '\f097'} /* */
+.octicon-settings:before { content: '\f07c'} /* */
+.octicon-log-in:before,
+.octicon-sign-in:before { content: '\f036'} /* */
+.octicon-log-out:before,
+.octicon-sign-out:before { content: '\f032'} /* */
+.octicon-split:before { content: '\f0c6'} /* */
+.octicon-squirrel:before { content: '\f0b2'} /* */
+.octicon-star-add:before,
+.octicon-star-delete:before,
+.octicon-star:before { content: '\f02a'} /* */
+.octicon-steps:before { content: '\f0c7'} /* */
+.octicon-stop:before { content: '\f08f'} /* */
+.octicon-repo-sync:before,
+.octicon-sync:before { content: '\f087'} /* */
+.octicon-tag-remove:before,
+.octicon-tag-add:before,
+.octicon-tag:before { content: '\f015'} /* */
+.octicon-telescope:before { content: '\f088'} /* */
+.octicon-terminal:before { content: '\f0c8'} /* */
+.octicon-three-bars:before { content: '\f05e'} /* */
+.octicon-tools:before { content: '\f031'} /* */
+.octicon-trashcan:before { content: '\f0d0'} /* */
+.octicon-triangle-down:before { content: '\f05b'} /* */
+.octicon-triangle-left:before { content: '\f044'} /* */
+.octicon-triangle-right:before { content: '\f05a'} /* */
+.octicon-triangle-up:before { content: '\f0aa'} /* */
+.octicon-unfold:before { content: '\f039'} /* */
+.octicon-unmute:before { content: '\f0ba'} /* */
+.octicon-versions:before { content: '\f064'} /* */
+.octicon-remove-close:before,
+.octicon-x:before { content: '\f081'} /* */
+.octicon-zap:before { content: '\26A1'} /* ⚡ */
diff --git a/src/main/resources/sub32.png b/src/main/resources/sub32.png
new file mode 100644
index 0000000..ebcfe13
--- /dev/null
+++ b/src/main/resources/sub32.png
Binary files differ
diff --git a/src/site/design.mkd b/src/site/design.mkd
index cd4b1b7..9ef302c 100644
--- a/src/site/design.mkd
+++ b/src/site/design.mkd
@@ -57,6 +57,7 @@
- [jedis](https://github.com/xetorthio/jedis) (MIT)
- [Mina SSHD](https://mina.apache.org) (Apache 2.0)
- [pf4j](https://github.com/decebals/pf4j) (Apache 2.0)
+- [google-guice](https://code.google.com/p/google-guice) (Apache 2.0)
### Other Build Dependencies
- [Fancybox image viewer](http://fancybox.net) (MIT and GPL dual-licensed)
diff --git a/src/site/plugins_extensions.mkd b/src/site/plugins_extensions.mkd
index 9e0d170..82dd30b 100644
--- a/src/site/plugins_extensions.mkd
+++ b/src/site/plugins_extensions.mkd
@@ -338,6 +338,16 @@
}
@Override
+ public void onFork(RepositoryModel origin, RepositoryModel fork) {
+ log.info("{} forked to {}", origin, fork);
+ }
+
+ @Override
+ public void onRename(String oldName, RepositoryModel repo) {
+ log.info("{} renamed to {}", oldName, repo);
+ }
+
+ @Override
public void onDeletion(RepositoryModel repo) {
log.info("Gitblit deleted {}", repo);
}
diff --git a/src/site/roadmap.mkd b/src/site/roadmap.mkd
deleted file mode 100644
index ea64321..0000000
--- a/src/site/roadmap.mkd
+++ /dev/null
@@ -1,8 +0,0 @@
-## Roadmap
-
-This is not exactly a formal roadmap but it is a priority list of what might be implemented in future releases.
-This list is volatile and may not reflect what will be in the next release.
-
-* Add support for Project owners/administrators (ticket-75)
-* Add Project create/update pages
-* Integrate improvements for git-flow (ticket-55)
diff --git a/src/site/rpc.mkd b/src/site/rpc.mkd
index de4ed47..4b065bf 100644
--- a/src/site/rpc.mkd
+++ b/src/site/rpc.mkd
@@ -131,7 +131,7 @@
### Example: LIST_REPOSITORIES
-**url**: https://localhost/rpc?req=LIST_REPOSITORIES
+**url**: https://localhost/rpc/?req=LIST_REPOSITORIES
**response body**: Map<String, RepositoryModel> where the map key is the clone url of the repository
```json
@@ -183,7 +183,7 @@
The original repository name is specified in the *name* url parameter. The new name is set within the JSON object.
-**url**: https://localhost/rpc?req=EDIT_REPOSITORY&name=libraries/xmlapache.git
+**url**: https://localhost/rpc/?req=EDIT_REPOSITORY&name=libraries/xmlapache.git
**post body**: RepositoryModel
```json
@@ -211,7 +211,7 @@
```
### Example: LIST_USERS
-**url**: https://localhost/rpc?req=LIST_USERS
+**url**: https://localhost/rpc/?req=LIST_USERS
**response body**: List<UserModel>
```json
@@ -237,7 +237,7 @@
```
### Example: LIST_SETTINGS
-**url**: https://localhost/rpc?req=LIST_SETTINGS
+**url**: https://localhost/rpc/?req=LIST_SETTINGS
**response body**: ServerSettings
```json
@@ -268,7 +268,7 @@
```
### Example: LIST_STATUS
-**url**: https://localhost/rpc?req=LIST_STATUS
+**url**: https://localhost/rpc/?req=LIST_STATUS
**response body**: ServerStatus
```json
diff --git a/src/site/setup_authentication.mkd b/src/site/setup_authentication.mkd
index 87b6749..7113667 100644
--- a/src/site/setup_authentication.mkd
+++ b/src/site/setup_authentication.mkd
@@ -8,6 +8,7 @@
* Windows authentication
* PAM authentication
* Htpasswd authentication
+* HTTP header authentication
* Redmine auhentication
* Salesforce.com authentication
* Servlet container authentication
@@ -83,10 +84,16 @@
### PAM Authentication
-PAM authentication is based on the use of libpam4j and JNA. To use this service, your Gitblit server must be installed on a Linux/Unix/MacOSX machine and the user that Gitblit runs-as must have root permissions.
+PAM authentication is based on the use of libpam4j and JNA. To use this service, your Gitblit server must be installed on a Linux/Unix/MacOSX machine.
realm.authenticationProviders = pam
- realm.pam.serviceName = system-auth
+ realm.pam.serviceName = gitblit
+
+Then define a gitblit authentication policy in `/etc/pam.d/gitblit`
+
+ # PAM configuration for the gitblit service
+ # Standard Un*x authentication.
+ @include common-auth
### Htpasswd Authentication
@@ -95,6 +102,17 @@
realm.authenticationProviders = htpasswd
realm.htpasswd.userFile = /path/to/htpasswd
+### HTTP Header Authentication
+
+HTTP header authentication allows you to use existing authentication performed by a trusted frontend, such as a reverse proxy. Ensure that when used, gitblit is ONLY availabe via the trusted frontend, otherwise it is vulnerable to a user adding the header explicitly.
+
+By default, no user or team header is defined, which results in all authentication failing this mechanism. The user header can also be defined while leaving the team header undefined, which causes users to be authenticated from the headers, but team memberships to be maintained locally.
+
+ realm.httpheader.userheader = REMOTE_USER
+ realm.httpheader.teamheader = X-GitblitExample-GroupNames
+ realm.httpheader.teamseparator = ,
+ realm.httpheader.autoCreateAccounts = false
+
### Redmine Authentication
You may authenticate your users against a Redmine installation as long as your Redmine install has properly enabled [API authentication](http://www.redmine.org/projects/redmine/wiki/Rest_Api#Authentication). This user service only supports user authentication; it does not support team creation based on Redmine groups. Redmine administrators will also be Gitblit administrators.
diff --git a/src/site/setup_bugtraq.mkd b/src/site/setup_bugtraq.mkd
index ad4a75d..fcd45d4 100644
--- a/src/site/setup_bugtraq.mkd
+++ b/src/site/setup_bugtraq.mkd
@@ -24,7 +24,7 @@
logregex = "Change-Id:\\s*(I[A-Fa-f0-9]{40})"
[bugtraq "jira"]
- https://jira.atlassian.com/browse/%BUGID%
+ url = https://jira.atlassian.com/browse/%BUGID%
logregex = (JRA-\\d+)
[bugtraq "github"]
diff --git a/src/site/setup_fail2ban.mkd b/src/site/setup_fail2ban.mkd
new file mode 100644
index 0000000..c735968
--- /dev/null
+++ b/src/site/setup_fail2ban.mkd
@@ -0,0 +1,24 @@
+## Configure fail2ban for Gitblit-SSH
+
+This procedure uses [fail2ban](http://www.fail2ban.org/).
+
+First, create a new filter file `gitblit.conf` in filter directory (Debian/CentOS: `/etc/fail2ban/filter.d/`) or into `filter.conf` file. Here is an example:
+
+ [Definition]
+ failregex = Failed login attempt for .+, invalid credentials from <HOST>\s*$
+ could not authenticate .*? \(/<HOST>:[0-9]*\) for SSH using the supplied password$
+ ignoreregex =
+
+Then edit `jail.conf` to add "gitblit" service (Debian: `/etc/fail2ban/jail.conf`). For example:
+
+ [gitblit]
+ enabled = true
+ port = 443,29418
+ protocol = tcp
+ filter = gitblit
+ logpath = /var/log/gitblit.log
+
+
+Reload fail2ban config to apply (`fail2ban-client reload`).
+
+Check the status of the gitblit fail2ban jail with `fail2ban-client status gitblit`
diff --git a/src/site/setup_filestore.mkd b/src/site/setup_filestore.mkd
new file mode 100644
index 0000000..9ffc9c6
--- /dev/null
+++ b/src/site/setup_filestore.mkd
@@ -0,0 +1,61 @@
+## Configure Git Large File Storage
+
+Gitblit provides a filestore that supports the [Git Large File Storage (LFS) API](https://git-lfs.github.com/).
+
+### Server Configuration
+
+Gitblit is configured to work straight away. However you may want to update the following in `gitblit.properties`:
+
+<table class="table">
+<thead>
+<tr><th>parameter</th><th>value</th><th>description</th></tr>
+</thead>
+<tbody>
+<tr>
+ <th>filestore.storageFolder</th><td>${baseFolder}/lfs</td>
+ <td>The path on the server where filestore objects are to be saved.</td>
+</tr>
+<tr>
+ <th>filestore.maxUploadSize</th><td>-1</td>
+ <td>The maximum allowable size that can be uploaded to the filestore. Once a file is uploaded it will be unaffected by later changes in this property. The default of -1 indicates no limits.</td>
+</tr>
+</tbody>
+</table>
+
+
+### Limitations
+
+Gitblit currently provides a server-only implementation of the opensource Git LFS API.
+
+1. Files in the filestore are not currently searchable by Lucene.
+2. Mirroring a repository that uses Git LFS will only mirror the pointer files, not the large files.
+3. Federation - Only the pointer files, not the large files, are transfered.
+
+Items 2 & 3 are pending [JGit Git LFS client capabilities](https://bugs.eclipse.org/bugs/show_bug.cgi?id=470333).
+
+
+### How does it work?
+
+1. Files that should be handled by Git LFS are defined in the `.gitattributes` file.
+2. Git LFS installs a pre-commit hook when installed `git lfs install`.
+3. When a commit is made the pre-commit hook replaces the defined Git LFS files with a pointer file containing metadata about the file so that it can be found later.
+4. When a commit is pushed, the changeset is sent to the git repository and the large files are sent to the filestore.
+
+For further details check out the [Git LFS specification](https://github.com/github/git-lfs/blob/master/docs/spec.md).
+
+### Convert/Migrate existing repository
+
+It is possible to migrate an existing repository containing large files to one that leverages the filestore. However, commit hash history will be altered.
+
+The following command may be run on a local repository:
+
+ git filter-branch --prune-empty --tree-filter '
+ git lfs track "*.docx" "*.pdf" > /dev/null
+ git add .gitattributes
+ git ls-files | xargs -d "\n" git check-attr filter | grep "filter: lfs" | sed -r "s/(.*): filter: lfs/\1/" | xargs -d "\n" -r bash -c "git rm -f --cached \"\$@\"; git add \"\$@\"" bash \
+ ' --tag-name-filter cat -- --all
+
+
+### Further Considerations
+
+While [other Git LFS implementations are available](https://github.com/github/git-lfs/wiki/Implementations) as there is no current [JGit LFS client capability](https://bugs.eclipse.org/bugs/show_bug.cgi?id=470333), Gitblit will be unable to access them.
\ No newline at end of file
diff --git a/src/site/setup_go.mkd b/src/site/setup_go.mkd
index 8117244..c46e04b 100644
--- a/src/site/setup_go.mkd
+++ b/src/site/setup_go.mkd
@@ -1,7 +1,7 @@
## Gitblit GO Installation & Setup
1. Download and unzip Gitblit GO [${project.releaseVersion} (Windows)](%GCURL%gitblit-${project.releaseVersion}.zip) or [${project.releaseVersion} (Linux/OSX)](%GCURL%gitblit-${project.releaseVersion}.tar.gz).
-*Its best to eliminate spaces in the path name.*
+*It is best to eliminate spaces in the path name.*
2. The server itself is configured through a simple text file.
Open `data/gitblit.properties` in your favorite text editor and make sure to review and set:
- *server.httpPort* and *server.httpsPort*
@@ -9,7 +9,7 @@
**https** is strongly recommended because passwords are insecurely transmitted form your browser/git client using Basic authentication!
- *git.packedGitLimit* (set larger than the size of your largest repository)
3. Execute `authority.cmd` or `java -cp gitblit.jar com.gitblit.authority.Launcher --baseFolder data` from a command-line
-**NOTE:** The Authority is a Swing GUI application. Use of this tool is not required as Gitblit GO will startup and create SSL certificates itself, BUT use of this tool allows you to control the identification metadata used in the generated certificates. Skipping this step will result in certificates with default metadata.
+**NOTE:** The Authority is a Swing GUI application. Use of this tool is not required as Gitblit GO will startup and create SSL certificates itself, BUT use of this tool allows you to control the identification metadata used in the generated self-signed certificates. Skipping this step will result in certificates with default metadata.
1. fill out the fields in the *new certificate defaults* dialog
2. enter the store password used in *server.storePassword* when prompted. This generates an SSL certificate for **localhost**.
3. you may want to generate an SSL certificate for the hostname or ip address hostnames you are serving from
@@ -30,6 +30,11 @@
You can specify `GITBLIT_HOME` either as an environment variable or as a `-DGITBLIT_HOME` JVM system property.
+### Including Other Properties
+SINCE 1.7.0
+
+Gitblit supports loading it's settings from multiple properties files. You can achieve this using the `include=filename` key. This setting supports loading multiple files using a *comma* as the delimiter. They are processed in the order defined and they may be nested (i.e. your included properties may include properties, etc, etc).
+
### Creating your own Self-Signed SSL Certificate
Gitblit GO (and Gitblit Certificate Authority) automatically generates a Certificate Authority (CA) certificate and an ssl certificate signed by this CA certificate that is bound to *localhost*.
diff --git a/src/site/setup_proxy.mkd b/src/site/setup_proxy.mkd
index 4ae8987..4cf263d 100644
--- a/src/site/setup_proxy.mkd
+++ b/src/site/setup_proxy.mkd
@@ -46,7 +46,7 @@
#ProxyPassreverse /gitblit http://localhost:8080/gitblit
# If your httpd frontend is https but you are proxying http Gitblit WAR or GO
-#Header edit Location ^http://([^⁄]+)/gitblit/ https://$1/gitblit/
+#Header edit Location ^http://([^/]+)/gitblit/ https://$1/gitblit/
# Additionally you will want to tell Gitblit the original scheme and port
#RequestHeader set X-Forwarded-Proto https
diff --git a/src/site/setup_transport_http.mkd b/src/site/setup_transport_http.mkd
index fd611d4..4de7596 100644
--- a/src/site/setup_transport_http.mkd
+++ b/src/site/setup_transport_http.mkd
@@ -5,7 +5,7 @@
You must tell Git/JGit not to verify the self-signed certificate in order to perform any remote Git operations.
**NOTE:**
-The default self-signed certificate generated by Gitlbit GO is bound to *localhost*.
+The default self-signed certificate generated by Gitblit GO is bound to *localhost*.
If you are using Eclipse/EGit/JGit clients, you will have to generate your own certificate that specifies the exact hostname used in your clone/push url.
You must do this because Eclipse/EGit/JGit (< 3.0) always verifies certificate hostnames, regardless of the *http.sslVerify=false* client-side setting.
@@ -17,6 +17,16 @@
- **Command-line Git** ([Git-Config Manual Page](http://www.kernel.org/pub/software/scm/git/docs/git-config.html))
<pre>git config --global --bool --add http.sslVerify false</pre>
+**NOTE:**
+When generating self-signed certificates, the default Java TLS settings will be used. These default settings will generate a weak Diffie-Hellman key.
+#### Java 8
+The default is a 1024 bit DH key.
+You can up the number of bits used by appending the following command line parameter when starting Gitblit:
+<pre>-Djdk.tls.ephemeralDHKeySize=2048</pre>
+2048 bits is the maximum (Java limitation), and is still considered secure as of this writing.
+#### Java 7
+The default is a 768 bit key. <b>This is hardcoded in Java 7 and cannot be changed.</b>. It is very weak. If you require longer DH keys, use Java 8.
+
### Http Post Buffer Size
You may find the default post buffer of your git client is too small to push large deltas to Gitblit. Sometimes this can be observed on your client as *hanging* during a push. Other times it can be observed by git erroring out with a message like: error: RPC failed; result=52, HTTP code = 0.
diff --git a/src/site/setup_war.mkd b/src/site/setup_war.mkd
index a060bae..21492b2 100644
--- a/src/site/setup_war.mkd
+++ b/src/site/setup_war.mkd
@@ -31,3 +31,8 @@
1. Open TOMCAT_HOME/conf/context.xml
2. Insert an *Environment* node within the *Context* node.<pre><Environment name="baseFolder" type="java.lang.String" value="c:/projects/git/gitblit/data" override="false" /></pre>
+
+### Including Other Properties
+SINCE 1.7.0
+
+Gitblit supports loading it's settings from multiple properties files. You can achieve this using the `include=filename` key. This setting supports loading multiple files using a *comma* as the delimiter. They are processed in the order defined and they may be nested (i.e. your included properties may include properties, etc, etc).
diff --git a/src/site/siteindex.mkd b/src/site/siteindex.mkd
index ae954bf..aec5c42 100644
--- a/src/site/siteindex.mkd
+++ b/src/site/siteindex.mkd
@@ -23,12 +23,9 @@
<tbody>
<tr><th>License</th><td><a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a></td></tr>
<tr><th>Sources</th><td><a href="${project.scmUrl}">GitHub</a></td></tr>
- <tr><th>Issues</th><td><a href="${project.issuesUrl}">GoogleCode</a></td></tr>
+ <tr><th>Issues</th><td><a href="${project.issuesUrl}">GitHub</a></td></tr>
<tr><th>Discussion</th><td><a href="${project.forumUrl}">Gitblit Group</a></td></tr>
- <tr><th>Google+</th><td><a href="${project.socialNetworkUrl}">Gitblit+</a></td></tr>
<tr><th>Ohloh</th><td><a target="_top" href="http://www.ohloh.net/p/gitblit"><img border="0" width="100" height="16" src="http://www.ohloh.net/p/gitblit/widgets/project_thin_badge.gif" alt="Ohloh project report for Gitblit" /></a></td></tr>
- <tr><th>Donations</th><td>If you enjoy Gitblit and want to support its development, please consider making a donation to <a href="http://www.stjude.org">St. Jude Children's Research Hospital</a>.
- <a href="http://www.stjude.org" alt="St. Jude Children's Research Hospital"><img style="padding-top:10px;" src="stjude_150x150.gif"/></a></td></tr>
</tbody>
</table>
</div>
diff --git a/src/site/tickets_overview.mkd b/src/site/tickets_overview.mkd
index 14e4ab9..10d0e18 100644
--- a/src/site/tickets_overview.mkd
+++ b/src/site/tickets_overview.mkd
@@ -70,7 +70,7 @@
1. The organizational unit of the Gitblit Tickets feature is the *ticket*.
2. A *ticket* can be used to report a bug, request an enhancement, ask a question, etc. A ticket can also be used to collaborate on a *patchset* that addresses the request.
3. A *patchset* is a series of commits from a merge base that exists in the target branch of your repository to the tip of the patchset. A patchset may only contain a single commit, or it may contain dozens. This is similar to the commits in a *Pull Request*. One important distinction here is that in Gitblit, each *Patchset* is developed on a separate branch and can be completely rewritten without losing the previous patchsets (this creates a new patchset).
-4. A *ticket* monitors the development of *patchsets* by tracking *revisions* to *patchsets*. The ticket alslo monitors rewritten patchsets. Each *patchset* is developed on it's own Git branch.
+4. A *ticket* monitors the development of *patchsets* by tracking *revisions* to *patchsets*. The ticket also monitors rewritten patchsets. Each *patchset* is developed on it's own Git branch.
Tracking *patchsets* is similar in concept to Gerrit, but there is a critical difference. In Gerrit, *every* commit in the *patchset* has it's own ticket **AND** Git branch. In Gerrit, *patchsets* can be easily rewritten and for each rewritten commit, a new branch ref is created. This leads to an explosion in refs for the repository over time. In Gitblit, only the tip of the *patchset* gets a branch ref and this branch ref is updated, like a regular branch, unless a rewrite is detected.
diff --git a/src/site/upgrade_go.mkd b/src/site/upgrade_go.mkd
index 54d7ab5..a009258 100644
--- a/src/site/upgrade_go.mkd
+++ b/src/site/upgrade_go.mkd
@@ -1,3 +1,17 @@
+## Upgrading Gitblit GO (1.7.0+)
+
+The default `gitblit.properties` file has been split into two files: `gitblit.properties`, which is the recommended file for setting your configuration, and `defaults.properties` which are Gitblit's default settings.
+
+ # Include Gitblit's 'defaults.properties' within your configuration.
+ #
+ # COMMA-DELIMITED
+ # SINCE 1.7.0
+ include = defaults.properties
+
+Notice that the default settings are *included* by your `gitblit.properties` file. The disadvantage to this approach is you must flip between discovering/reading the settings in `defaults.properties` and setting them in `gitblit.properties`, but there are some clear advantages too. This setup is not required. You may continue to keep all your settings in `gitblit.properties` like before.
+
+Additionally you may find it useful if you are maintaining several Gitblit instances to share common properties files.
+
## Upgrading Gitblit GO (1.2.1+)
1. Unzip Gitblit GO to a new folder
diff --git a/src/site/upgrade_war.mkd b/src/site/upgrade_war.mkd
index 34ffd75..4165ca1 100644
--- a/src/site/upgrade_war.mkd
+++ b/src/site/upgrade_war.mkd
@@ -1,3 +1,21 @@
+## Upgrading Gitblit WAR (1.7.0+)
+
+The default `gitblit.properties` file has been split into two files: `gitblit.properties`, which is the recommended file for setting your configuration, and `defaults.properties` which are Gitblit's default settings.
+
+ # Include Gitblit's 'defaults.properties' within your configuration.
+ #
+ # COMMA-DELIMITED
+ # SINCE 1.7.0
+ include = defaults.properties
+
+Notice that the default settings are *included* by your `gitblit.properties` file. The disadvantage to this approach is you must flip between discovering/reading the settings in `defaults.properties` and setting them in `gitblit.properties`, but there are some clear advantages too. This setup is not required. You may continue to keep all your settings in `gitblit.properties` like before.
+
+Additionally you may find it useful if you are maintaining several Gitblit instances to share common properties files.
+
+## Upgrading Gitblit WAR (1.4.0+)
+
+The *baseFolder* context parameter has been replaced with a *baseFolder* JNDI env-entry. This means you can define the *baseFolder* from the administrative console of your servlet container and not have to manipulate anything in the web.xml file.
+
## Upgrading Gitblit WAR (1.2.1+)
1. Make sure your `WEB-INF/web.xml` *baseFolder* context parameter is not `${contextFolder}/WEB-INF/data`!
If it is, move your `WEB-INF/data` folder to a location writeable by your servlet container.
@@ -5,7 +23,6 @@
3. Edit the new WAR's `WEB-INF/web.xml` file and set the *baseFolder* context parameter to your external baseFolder.
4. Review and optionally apply any new settings as indicated in the [release log](releases.html) to `${baseFolder}/gitblit.properties`.
-
## Upgrading Gitblit WAR (pre-1.2.1)
1. Create a `data` as outlined in step 1 of *Upgrading Gitblit GO (pre-1.2.1)*
@@ -14,7 +31,3 @@
4. Copy the new WAR's `WEB-INF/data/gitblit.properties` file to your data folder
5. Manually apply any changes you made to your original web.xml file to the gitblit.properties file you copied to your data folder
6. Edit the new WAR's `WEB-INF/web.xml` file and set the *baseFolder* context parameter to your external baseFolder.
-
-## Upgrading Gitblit WAR (1.4.0+)
-
-The *baseFolder* context parameter has been replaced with a *baseFolder* JNDI env-entry. This means you can define the *baseFolder* from the administrative console of your servlet container and not have to manipulate anything in the web.xml file.
\ No newline at end of file
diff --git a/src/test/java/com/gitblit/tests/AuthenticationManagerTest.java b/src/test/java/com/gitblit/tests/AuthenticationManagerTest.java
index 0cdee6c..f8dc888 100644
--- a/src/test/java/com/gitblit/tests/AuthenticationManagerTest.java
+++ b/src/test/java/com/gitblit/tests/AuthenticationManagerTest.java
@@ -15,15 +15,44 @@
*/
package com.gitblit.tests;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
import org.junit.Test;
+import com.gitblit.IUserService;
+import com.gitblit.Keys;
import com.gitblit.manager.AuthenticationManager;
import com.gitblit.manager.IAuthenticationManager;
-import com.gitblit.manager.IUserManager;
+import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.RuntimeManager;
import com.gitblit.manager.UserManager;
+import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.tests.mock.MemorySettings;
import com.gitblit.utils.XssFilter;
@@ -35,35 +64,647 @@
* @author James Moger
*
*/
+@SuppressWarnings("deprecation")
public class AuthenticationManagerTest extends GitblitUnitTest {
- IUserManager users;
+ UserManager users;
- MemorySettings getSettings() {
- return new MemorySettings(new HashMap<String, Object>());
- }
+ private static final class DummyHttpServletRequest implements HttpServletRequest {
- IAuthenticationManager newAuthenticationManager() {
- XssFilter xssFilter = new AllowXssFilter();
- RuntimeManager runtime = new RuntimeManager(getSettings(), xssFilter, GitBlitSuite.BASEFOLDER).start();
- users = new UserManager(runtime, null).start();
- AuthenticationManager auth = new AuthenticationManager(runtime, users).start();
- return auth;
- }
+ @Override
+ public Object getAttribute(String name) {
+ return null;
+ }
- @Test
- public void testAuthenticate() throws Exception {
- IAuthenticationManager auth = newAuthenticationManager();
+ @Override
+ public Enumeration<String> getAttributeNames() {
+ return null;
+ }
- UserModel user = new UserModel("sunnyjim");
+ @Override
+ public String getCharacterEncoding() {
+ return null;
+ }
+
+ @Override
+ public void setCharacterEncoding(String env)
+ throws UnsupportedEncodingException {
+ }
+
+ @Override
+ public int getContentLength() {
+ return 0;
+ }
+
+ @Override
+ public long getContentLengthLong() {
+ return 0;
+ }
+
+ @Override
+ public String getContentType() {
+ return null;
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ return null;
+ }
+
+ @Override
+ public String getParameter(String name) {
+ return null;
+ }
+
+ @Override
+ public Enumeration<String> getParameterNames() {
+ return null;
+ }
+
+ @Override
+ public String[] getParameterValues(String name) {
+ return null;
+ }
+
+ @Override
+ public Map<String, String[]> getParameterMap() {
+ return null;
+ }
+
+ @Override
+ public String getProtocol() {
+ return null;
+ }
+
+ @Override
+ public String getScheme() {
+ return null;
+ }
+
+ @Override
+ public String getServerName() {
+ return null;
+ }
+
+ @Override
+ public int getServerPort() {
+ return 0;
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ return null;
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ return null;
+ }
+
+ @Override
+ public String getRemoteHost() {
+ return null;
+ }
+
+ @Override
+ public void setAttribute(String name, Object o) {
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ }
+
+ @Override
+ public Locale getLocale() {
+ return null;
+ }
+
+ @Override
+ public Enumeration<Locale> getLocales() {
+ return null;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path) {
+ return null;
+ }
+
+ @Override
+ public String getRealPath(String path) {
+ return null;
+ }
+
+ @Override
+ public int getRemotePort() {
+ return 0;
+ }
+
+ @Override
+ public String getLocalName() {
+ return null;
+ }
+
+ @Override
+ public String getLocalAddr() {
+ return null;
+ }
+
+ @Override
+ public int getLocalPort() {
+ return 0;
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return null;
+ }
+
+ @Override
+ public AsyncContext startAsync() throws IllegalStateException {
+ return null;
+ }
+
+ @Override
+ public AsyncContext startAsync(ServletRequest servletRequest,
+ ServletResponse servletResponse)
+ throws IllegalStateException {
+ return null;
+ }
+
+ @Override
+ public boolean isAsyncStarted() {
+ return false;
+ }
+
+ @Override
+ public boolean isAsyncSupported() {
+ return false;
+ }
+
+ @Override
+ public AsyncContext getAsyncContext() {
+ return null;
+ }
+
+ @Override
+ public DispatcherType getDispatcherType() {
+ return null;
+ }
+
+ @Override
+ public String getAuthType() {
+ return null;
+ }
+
+ @Override
+ public Cookie[] getCookies() {
+ return null;
+ }
+
+ @Override
+ public long getDateHeader(String name) {
+ return 0;
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return null;
+ }
+
+ @Override
+ public Enumeration<String> getHeaders(String name) {
+ return null;
+ }
+
+ @Override
+ public Enumeration<String> getHeaderNames() {
+ return null;
+ }
+
+ @Override
+ public int getIntHeader(String name) {
+ return 0;
+ }
+
+ @Override
+ public String getMethod() {
+ return null;
+ }
+
+ @Override
+ public String getPathInfo() {
+ return null;
+ }
+
+ @Override
+ public String getPathTranslated() {
+ return null;
+ }
+
+ @Override
+ public String getContextPath() {
+ return null;
+ }
+
+ @Override
+ public String getQueryString() {
+ return null;
+ }
+
+ @Override
+ public String getRemoteUser() {
+ return null;
+ }
+
+ @Override
+ public boolean isUserInRole(String role) {
+ if(role != null && "admin".equals(role)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return new Principal(){
+ @Override
+ public String getName() {
+ return "sunnyjim";
+ }
+
+ };
+ }
+
+ @Override
+ public String getRequestedSessionId() {
+ return null;
+ }
+
+ @Override
+ public String getRequestURI() {
+ return null;
+ }
+
+ @Override
+ public StringBuffer getRequestURL() {
+ return null;
+ }
+
+ @Override
+ public String getServletPath() {
+ return null;
+ }
+
+ @Override
+ public HttpSession getSession(boolean create) {
+ return null;
+ }
+
+ final Map<String, Object> sessionAttributes = new HashMap<String, Object>();
+ @Override
+ public HttpSession getSession() {
+ return new HttpSession() {
+
+ @Override
+ public long getCreationTime() {
+ return 0;
+ }
+
+ @Override
+ public String getId() {
+ return null;
+ }
+
+ @Override
+ public long getLastAccessedTime() {
+ return 0;
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return null;
+ }
+
+ @Override
+ public void setMaxInactiveInterval(int interval) {
+ }
+
+ @Override
+ public int getMaxInactiveInterval() {
+ return 0;
+ }
+
+ @Override
+ public HttpSessionContext getSessionContext() {
+ return null;
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return sessionAttributes.get(name);
+ }
+
+ @Override
+ public Object getValue(String name) {
+ return null;
+ }
+
+ @Override
+ public Enumeration<String> getAttributeNames() {
+ return Collections.enumeration(sessionAttributes.keySet());
+ }
+
+ @Override
+ public String[] getValueNames() {
+ return null;
+ }
+
+ @Override
+ public void setAttribute(String name,
+ Object value) {
+ }
+
+ @Override
+ public void putValue(String name, Object value) {
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ }
+
+ @Override
+ public void removeValue(String name) {
+ }
+
+ @Override
+ public void invalidate() {
+ }
+
+ @Override
+ public boolean isNew() {
+ return false;
+ }
+
+ };
+ }
+
+ @Override
+ public String changeSessionId() {
+ return null;
+ }
+
+ @Override
+ public boolean isRequestedSessionIdValid() {
+ return false;
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromCookie() {
+ return false;
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromURL() {
+ return false;
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromUrl() {
+ return false;
+ }
+
+ @Override
+ public boolean authenticate(HttpServletResponse response)
+ throws IOException, ServletException {
+ return false;
+ }
+
+ @Override
+ public void login(String username, String password)
+ throws ServletException {
+ }
+
+ @Override
+ public void logout() throws ServletException {
+ }
+
+ @Override
+ public Collection<Part> getParts() throws IOException,
+ ServletException {
+ return null;
+ }
+
+ @Override
+ public Part getPart(String name) throws IOException,
+ ServletException {
+ return null;
+ }
+
+ @Override
+ public <T extends HttpUpgradeHandler> T upgrade(
+ Class<T> handlerClass) throws IOException,
+ ServletException {
+ return null;
+ }
+
+ }
+
+ HashMap<String, Object> settings = new HashMap<String, Object>();
+
+ MemorySettings getSettings() {
+ return new MemorySettings(settings);
+ }
+
+ IAuthenticationManager newAuthenticationManager() {
+ XssFilter xssFilter = new AllowXssFilter();
+ RuntimeManager runtime = new RuntimeManager(getSettings(), xssFilter, GitBlitSuite.BASEFOLDER).start();
+ users = new UserManager(runtime, null).start();
+ final Map<String, UserModel> virtualUsers = new HashMap<String, UserModel>();
+ users.setUserService(new IUserService() {
+
+ @Override
+ public void setup(IRuntimeManager runtimeManager) {
+ }
+
+ @Override
+ public String getCookie(UserModel model) {
+ return null;
+ }
+
+ @Override
+ public UserModel getUserModel(char[] cookie) {
+ return null;
+ }
+
+ @Override
+ public UserModel getUserModel(String username) {
+ return virtualUsers.get(username);
+ }
+
+ @Override
+ public boolean updateUserModel(UserModel model) {
+ virtualUsers.put(model.username, model);
+ return true;
+ }
+
+ @Override
+ public boolean updateUserModels(Collection<UserModel> models) {
+ return false;
+ }
+
+ @Override
+ public boolean updateUserModel(String username, UserModel model) {
+ virtualUsers.put(username, model);
+ return true;
+ }
+
+ @Override
+ public boolean deleteUserModel(UserModel model) {
+ return false;
+ }
+
+ @Override
+ public boolean deleteUser(String username) {
+ return false;
+ }
+
+ @Override
+ public List<String> getAllUsernames() {
+ return null;
+ }
+
+ @Override
+ public List<UserModel> getAllUsers() {
+ return null;
+ }
+
+ @Override
+ public List<String> getAllTeamNames() {
+ return null;
+ }
+
+ @Override
+ public List<TeamModel> getAllTeams() {
+ return null;
+ }
+
+ @Override
+ public List<String> getTeamNamesForRepositoryRole(String role) {
+ return null;
+ }
+
+ @Override
+ public TeamModel getTeamModel(String teamname) {
+ return null;
+ }
+
+ @Override
+ public boolean updateTeamModel(TeamModel model) {
+ return false;
+ }
+
+ @Override
+ public boolean updateTeamModels(Collection<TeamModel> models) {
+ return false;
+ }
+
+ @Override
+ public boolean updateTeamModel(String teamname, TeamModel model) {
+ return false;
+ }
+
+ @Override
+ public boolean deleteTeamModel(TeamModel model) {
+ return false;
+ }
+
+ @Override
+ public boolean deleteTeam(String teamname) {
+ return false;
+ }
+
+ @Override
+ public List<String> getUsernamesForRepositoryRole(String role) {
+ return null;
+ }
+
+ @Override
+ public boolean renameRepositoryRole(String oldRole,
+ String newRole) {
+ return false;
+ }
+
+ @Override
+ public boolean deleteRepositoryRole(String role) {
+ return false;
+ }
+
+ });
+ AuthenticationManager auth = new AuthenticationManager(runtime, users).start();
+ return auth;
+ }
+
+ @Test
+ public void testAuthenticate() throws Exception {
+ IAuthenticationManager auth = newAuthenticationManager();
+
+ UserModel user = new UserModel("sunnyjim");
user.password = "password";
users.updateUserModel(user);
- assertNotNull(auth.authenticate(user.username, user.password.toCharArray()));
+ assertNotNull(auth.authenticate(user.username, user.password.toCharArray(), null));
user.disabled = true;
users.updateUserModel(user);
- assertNull(auth.authenticate(user.username, user.password.toCharArray()));
+ assertNull(auth.authenticate(user.username, user.password.toCharArray(), null));
users.deleteUserModel(user);
- }
+ }
+
+ @Test
+ public void testContenairAuthenticate() throws Exception {
+ settings.put(Keys.realm.container.autoCreateAccounts, "true");
+ settings.put(Keys.realm.container.autoAccounts.displayName, "displayName");
+ settings.put(Keys.realm.container.autoAccounts.emailAddress, "emailAddress");
+ settings.put(Keys.realm.container.autoAccounts.adminRole, "admin");
+ settings.put(Keys.realm.container.autoAccounts.locale, "locale");
+
+ DummyHttpServletRequest request = new DummyHttpServletRequest();
+ request.sessionAttributes.put("displayName", "Sunny Jim");
+ request.sessionAttributes.put("emailAddress", "Jim.Sunny@gitblit.com");
+ request.sessionAttributes.put("locale", "it");
+
+ IAuthenticationManager auth = newAuthenticationManager();
+
+ UserModel user = auth.authenticate(request);
+
+ assertTrue(user.canAdmin);
+ assertEquals("Sunny Jim", user.displayName);
+ assertEquals("Jim.Sunny@gitblit.com", user.emailAddress);
+ assertEquals(Locale.ITALIAN, user.getPreferences().getLocale());
+ }
+
+ @Test
+ public void testContenairAuthenticateEmpty() throws Exception {
+ settings.put(Keys.realm.container.autoCreateAccounts, "true");
+ settings.put(Keys.realm.container.autoAccounts.displayName, "displayName");
+ settings.put(Keys.realm.container.autoAccounts.emailAddress, "emailAddress");
+ settings.put(Keys.realm.container.autoAccounts.adminRole, "notAdmin");
+
+ DummyHttpServletRequest request = new DummyHttpServletRequest();
+
+ IAuthenticationManager auth = newAuthenticationManager();
+
+ UserModel user = auth.authenticate(request);
+
+ assertFalse(user.canAdmin);
+ assertEquals("sunnyjim", user.displayName);
+ assertNull(user.emailAddress);
+ assertNull(user.getPreferences().getLocale());
+ }
+
}
diff --git a/src/test/java/com/gitblit/tests/DiffUtilsTest.java b/src/test/java/com/gitblit/tests/DiffUtilsTest.java
index 9d627b8..e8e839a 100644
--- a/src/test/java/com/gitblit/tests/DiffUtilsTest.java
+++ b/src/test/java/com/gitblit/tests/DiffUtilsTest.java
@@ -23,6 +23,7 @@
import com.gitblit.models.AnnotatedLine;
import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.DiffUtils.DiffComparator;
import com.gitblit.utils.DiffUtils.DiffOutputType;
import com.gitblit.utils.JGitUtils;
@@ -40,7 +41,7 @@
Repository repository = GitBlitSuite.getHelloworldRepository();
RevCommit commit = JGitUtils.getCommit(repository,
"1d0c2933a4ae69c362f76797d42d6bd182d05176");
- String diff = DiffUtils.getCommitDiff(repository, commit, DiffOutputType.PLAIN).content;
+ String diff = DiffUtils.getCommitDiff(repository, commit, DiffComparator.SHOW_WHITESPACE, DiffOutputType.PLAIN, 3).content;
repository.close();
assertTrue(diff != null && diff.length() > 0);
String expected = "- system.out.println(\"Hello World\");\n+ System.out.println(\"Hello World\"";
@@ -54,7 +55,7 @@
"8baf6a833b5579384d9b9ceb8a16b5d0ea2ec4ca");
RevCommit commit = JGitUtils.getCommit(repository,
"1d0c2933a4ae69c362f76797d42d6bd182d05176");
- String diff = DiffUtils.getDiff(repository, baseCommit, commit, DiffOutputType.PLAIN).content;
+ String diff = DiffUtils.getDiff(repository, baseCommit, commit, DiffComparator.SHOW_WHITESPACE, DiffOutputType.PLAIN, 3).content;
repository.close();
assertTrue(diff != null && diff.length() > 0);
String expected = "- system.out.println(\"Hello World\");\n+ System.out.println(\"Hello World\"";
@@ -66,7 +67,7 @@
Repository repository = GitBlitSuite.getHelloworldRepository();
RevCommit commit = JGitUtils.getCommit(repository,
"1d0c2933a4ae69c362f76797d42d6bd182d05176");
- String diff = DiffUtils.getDiff(repository, commit, "java.java", DiffOutputType.PLAIN).content;
+ String diff = DiffUtils.getDiff(repository, commit, "java.java", DiffComparator.SHOW_WHITESPACE, DiffOutputType.PLAIN, 3).content;
repository.close();
assertTrue(diff != null && diff.length() > 0);
String expected = "- system.out.println(\"Hello World\");\n+ System.out.println(\"Hello World\"";
diff --git a/src/test/java/com/gitblit/tests/FilestoreManagerTest.java b/src/test/java/com/gitblit/tests/FilestoreManagerTest.java
new file mode 100644
index 0000000..c76e9dd
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/FilestoreManagerTest.java
@@ -0,0 +1,547 @@
+package com.gitblit.tests;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Keys;
+import com.gitblit.models.FilestoreModel.Status;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.FileUtils;
+
+
+/**
+ * Test of the filestore manager and confirming filesystem updated
+ *
+ * @author Paul Martin
+ *
+ */
+public class FilestoreManagerTest extends GitblitUnitTest {
+
+ private static final AtomicBoolean started = new AtomicBoolean(false);
+
+ private static final BlobInfo blob_zero = new BlobInfo(0);
+ private static final BlobInfo blob_512KB = new BlobInfo(512*FileUtils.KB);
+ private static final BlobInfo blob_6MB = new BlobInfo(6*FileUtils.MB);
+
+ private static int download_limit_default = -1;
+ private static int download_limit_test = 5*FileUtils.MB;
+
+ private static final String invalid_hash_empty = "";
+ private static final String invalid_hash_major = "INVALID_HASH";
+ private static final String invalid_hash_regex_attack = blob_512KB.hash.replace('a', '*');
+ private static final String invalid_hash_one_long = blob_512KB.hash.concat("a");
+ private static final String invalid_hash_one_short = blob_512KB.hash.substring(1);
+
+
+
+ @BeforeClass
+ public static void startGitblit() throws Exception {
+ started.set(GitBlitSuite.startGitblit());
+ }
+
+ @AfterClass
+ public static void stopGitblit() throws Exception {
+ if (started.get()) {
+ GitBlitSuite.stopGitblit();
+ }
+ }
+
+
+
+ @Test
+ public void testAdminAccess() throws Exception {
+
+ FileUtils.delete(filestore().getStorageFolder());
+ filestore().clearFilestoreCache();
+
+ RepositoryModel r = new RepositoryModel("myrepo.git", null, null, new Date());
+ ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
+
+ UserModel u = new UserModel("admin");
+ u.canAdmin = true;
+
+ //No upload limit
+ settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_default);
+
+ //Invalid hash tests
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_major, u, r, streamOut));
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_regex_attack, u, r, streamOut));
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_long, u, r, streamOut));
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_short, u, r, streamOut));
+
+ // Download prior to upload
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+
+ //Bad input is rejected with no upload taking place
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_empty, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_major, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_regex_attack, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_long, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_short, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ assertEquals(Status.Error_Invalid_Size, filestore().uploadBlob(blob_512KB.hash, -1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, 0, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ assertEquals(Status.Error_Hash_Mismatch, filestore().uploadBlob(blob_zero.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ //Confirm no upload with bad input
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+
+ //Good input will accept the upload
+ assertEquals(Status.Available, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+ assertArrayEquals(blob_512KB.blob, streamOut.toByteArray());
+
+ //Subsequent failed uploads do not affect file
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ streamOut.reset();
+ assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+ assertArrayEquals(blob_512KB.blob, streamOut.toByteArray());
+
+ //Zero length upload is valid
+ assertEquals(Status.Available, filestore().uploadBlob(blob_zero.hash, blob_zero.length, u, r, new ByteArrayInputStream(blob_zero.blob)));
+
+ streamOut.reset();
+ assertEquals(Status.Available, filestore().downloadBlob(blob_zero.hash, u, r, streamOut));
+ assertArrayEquals(blob_zero.blob, streamOut.toByteArray());
+
+
+ //Pre-informed upload identifies identical errors as immediate upload
+ assertEquals(Status.Upload_Pending, filestore().addObject(blob_6MB.hash, blob_6MB.length, u, r));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_empty, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_major, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_regex_attack, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_long, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_short, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, 0, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length-1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length+1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+
+ //Good input will accept the upload
+ assertEquals(Status.Available, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Available, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+ assertArrayEquals(blob_6MB.blob, streamOut.toByteArray());
+
+ //Confirm the relevant files exist
+ assertTrue("Admin did not save zero length file!", filestore().getStoragePath(blob_zero.hash).exists());
+ assertTrue("Admin did not save 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+ assertTrue("Admin did not save 6MB file!", filestore().getStoragePath(blob_6MB.hash).exists());
+
+ //Clear the files and cache to test upload limit property
+ FileUtils.delete(filestore().getStorageFolder());
+ filestore().clearFilestoreCache();
+
+ settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_test);
+
+ assertEquals(Status.Available, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+ assertArrayEquals(blob_512KB.blob, streamOut.toByteArray());
+
+ assertEquals(Status.Error_Exceeds_Size_Limit, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+
+ assertTrue("Admin did not save 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+ assertFalse("Admin saved 6MB file despite (over filesize limit)!", filestore().getStoragePath(blob_6MB.hash).exists());
+
+ }
+
+ @Test
+ public void testAuthenticatedAccess() throws Exception {
+
+ FileUtils.delete(filestore().getStorageFolder());
+ filestore().clearFilestoreCache();
+
+ RepositoryModel r = new RepositoryModel("myrepo.git", null, null, new Date());
+ r.authorizationControl = AuthorizationControl.AUTHENTICATED;
+ r.accessRestriction = AccessRestrictionType.VIEW;
+
+ ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
+
+ UserModel u = new UserModel("test");
+
+ //No upload limit
+ settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_default);
+
+ //Invalid hash tests
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_major, u, r, streamOut));
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_regex_attack, u, r, streamOut));
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_long, u, r, streamOut));
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_short, u, r, streamOut));
+
+ // Download prior to upload
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+
+ //Bad input is rejected with no upload taking place
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_empty, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_major, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_regex_attack, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_long, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_short, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ assertEquals(Status.Error_Invalid_Size, filestore().uploadBlob(blob_512KB.hash, -1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, 0, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ assertEquals(Status.Error_Hash_Mismatch, filestore().uploadBlob(blob_zero.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ //Confirm no upload with bad input
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+
+ //Good input will accept the upload
+ assertEquals(Status.Available, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+ assertArrayEquals(blob_512KB.blob, streamOut.toByteArray());
+
+ //Subsequent failed uploads do not affect file
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ streamOut.reset();
+ assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+ assertArrayEquals(blob_512KB.blob, streamOut.toByteArray());
+
+ //Zero length upload is valid
+ assertEquals(Status.Available, filestore().uploadBlob(blob_zero.hash, blob_zero.length, u, r, new ByteArrayInputStream(blob_zero.blob)));
+
+ streamOut.reset();
+ assertEquals(Status.Available, filestore().downloadBlob(blob_zero.hash, u, r, streamOut));
+ assertArrayEquals(blob_zero.blob, streamOut.toByteArray());
+
+
+ //Pre-informed upload identifies identical errors as immediate upload
+ assertEquals(Status.Upload_Pending, filestore().addObject(blob_6MB.hash, blob_6MB.length, u, r));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_empty, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_major, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_regex_attack, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_long, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_short, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, 0, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length-1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length+1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+
+ //Good input will accept the upload
+ assertEquals(Status.Available, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Available, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+ assertArrayEquals(blob_6MB.blob, streamOut.toByteArray());
+
+ //Confirm the relevant files exist
+ assertTrue("Authenticated user did not save zero length file!", filestore().getStoragePath(blob_zero.hash).exists());
+ assertTrue("Authenticated user did not save 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+ assertTrue("Authenticated user did not save 6MB file!", filestore().getStoragePath(blob_6MB.hash).exists());
+
+ //Clear the files and cache to test upload limit property
+ FileUtils.delete(filestore().getStorageFolder());
+ filestore().clearFilestoreCache();
+
+ settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_test);
+
+ assertEquals(Status.Available, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+ assertArrayEquals(blob_512KB.blob, streamOut.toByteArray());
+
+ assertEquals(Status.Error_Exceeds_Size_Limit, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+
+ assertTrue("Authenticated user did not save 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+ assertFalse("Authenticated user saved 6MB file (over filesize limit)!", filestore().getStoragePath(blob_6MB.hash).exists());
+
+ }
+
+ @Test
+ public void testAnonymousAccess() throws Exception {
+
+ FileUtils.delete(filestore().getStorageFolder());
+ filestore().clearFilestoreCache();
+
+ RepositoryModel r = new RepositoryModel("myrepo.git", null, null, new Date());
+ r.authorizationControl = AuthorizationControl.NAMED;
+ r.accessRestriction = AccessRestrictionType.CLONE;
+
+ ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
+
+ UserModel u = UserModel.ANONYMOUS;
+
+ //No upload limit
+ settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_default);
+
+ //Invalid hash tests
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_major, u, r, streamOut));
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_regex_attack, u, r, streamOut));
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_long, u, r, streamOut));
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_short, u, r, streamOut));
+
+ // Download prior to upload
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+
+ //Bad input is rejected with no upload taking place
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_empty, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_major, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_regex_attack, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_one_long, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_one_short, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, -1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, 0, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_zero.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ //Confirm no upload with bad input
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+
+ //Good input will accept the upload
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+
+ //Subsequent failed uploads do not affect file
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+
+ //Zero length upload is valid
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_zero.hash, blob_zero.length, u, r, new ByteArrayInputStream(blob_zero.blob)));
+
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_zero.hash, u, r, streamOut));
+
+
+ //Pre-informed upload identifies identical errors as immediate upload
+ assertEquals(Status.AuthenticationRequired, filestore().addObject(blob_6MB.hash, blob_6MB.length, u, r));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_empty, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_major, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_regex_attack, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_one_long, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_one_short, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, -1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, 0, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length-1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length+1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+
+ //Good input will accept the upload
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+
+ //Confirm the relevant files do not exist
+ assertFalse("Anonymous user saved zero length file!", filestore().getStoragePath(blob_zero.hash).exists());
+ assertFalse("Anonymous user 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+ assertFalse("Anonymous user 6MB file!", filestore().getStoragePath(blob_6MB.hash).exists());
+
+ //Clear the files and cache to test upload limit property
+ FileUtils.delete(filestore().getStorageFolder());
+ filestore().clearFilestoreCache();
+
+ settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_test);
+
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+
+ assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+
+ assertFalse("Anonymous user saved 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+ assertFalse("Anonymous user saved 6MB file (over filesize limit)!", filestore().getStoragePath(blob_6MB.hash).exists());
+
+ }
+
+ @Test
+ public void testUnauthorizedAccess() throws Exception {
+
+ FileUtils.delete(filestore().getStorageFolder());
+ filestore().clearFilestoreCache();
+
+ RepositoryModel r = new RepositoryModel("myrepo.git", null, null, new Date());
+ r.authorizationControl = AuthorizationControl.NAMED;
+ r.accessRestriction = AccessRestrictionType.VIEW;
+
+ ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
+
+ UserModel u = new UserModel("test");
+ u.setRepositoryPermission(r.name, AccessPermission.CLONE);
+
+ //No upload limit
+ settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_default);
+
+ //Invalid hash tests
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_major, u, r, streamOut));
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_regex_attack, u, r, streamOut));
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_long, u, r, streamOut));
+ assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_short, u, r, streamOut));
+
+ // Download prior to upload
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+
+ //Bad input is rejected with no upload taking place
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_major, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_regex_attack, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_one_long, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_empty, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_one_short, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, -1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, 0, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_zero.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ //Confirm no upload with bad input
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+
+ //Good input will accept the upload
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+
+ //Subsequent failed uploads do not affect file
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+
+ //Zero length upload is valid
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_zero.hash, blob_zero.length, u, r, new ByteArrayInputStream(blob_zero.blob)));
+
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_zero.hash, u, r, streamOut));
+
+
+ //Pre-informed upload identifies identical errors as immediate upload
+ assertEquals(Status.Error_Unauthorized, filestore().addObject(blob_6MB.hash, blob_6MB.length, u, r));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_empty, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_major, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_regex_attack, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_one_long, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_one_short, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, -1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, 0, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length-1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length+1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+
+ //Good input will accept the upload
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+
+ //Confirm the relevant files exist
+ assertFalse("Unauthorized user saved zero length file!", filestore().getStoragePath(blob_zero.hash).exists());
+ assertFalse("Unauthorized user saved 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+ assertFalse("Unauthorized user saved 6MB file!", filestore().getStoragePath(blob_6MB.hash).exists());
+
+ //Clear the files and cache to test upload limit property
+ FileUtils.delete(filestore().getStorageFolder());
+ filestore().clearFilestoreCache();
+
+ settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_test);
+
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+
+ assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+ streamOut.reset();
+ assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+
+ assertFalse("Unauthorized user saved 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+ assertFalse("Unauthorized user saved 6MB file (over filesize limit)!", filestore().getStoragePath(blob_6MB.hash).exists());
+
+ }
+
+}
+
+/*
+ * Test helper structure to create blobs of a given size
+ */
+final class BlobInfo {
+ public byte[] blob;
+ public String hash;
+ public int length;
+
+ public BlobInfo(int nBytes) {
+ blob = new byte[nBytes];
+ new java.util.Random().nextBytes(blob);
+ hash = DigestUtils.sha256Hex(blob);
+ length = nBytes;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/gitblit/tests/FilestoreServletTest.java b/src/test/java/com/gitblit/tests/FilestoreServletTest.java
new file mode 100644
index 0000000..4e4b056
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/FilestoreServletTest.java
@@ -0,0 +1,355 @@
+package com.gitblit.tests;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.gitblit.Keys;
+import com.gitblit.manager.FilestoreManager;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.models.FilestoreModel.Status;
+import com.gitblit.servlet.FilestoreServlet;
+import com.gitblit.utils.FileUtils;
+
+public class FilestoreServletTest extends GitblitUnitTest {
+
+ private static final AtomicBoolean started = new AtomicBoolean(false);
+
+ private static final String SHA256_EG = "9a712c5d4037503a2d5ee1d07ad191eb99d051e84cbb020c171a5ae19bbe3cbd";
+
+ private static final String repoName = "helloworld.git";
+
+ private static final String repoLfs = "/r/" + repoName + "/info/lfs/objects/";
+
+ @BeforeClass
+ public static void startGitblit() throws Exception {
+ started.set(GitBlitSuite.startGitblit());
+ }
+
+ @AfterClass
+ public static void stopGitblit() throws Exception {
+ if (started.get()) {
+ GitBlitSuite.stopGitblit();
+ }
+ }
+
+
+ @Test
+ public void testRegexGroups() throws Exception {
+
+ Pattern p = Pattern.compile(FilestoreServlet.REGEX_PATH);
+
+ String basicUrl = "https://localhost:8080/r/test.git/info/lfs/objects/";
+ String batchUrl = basicUrl + "batch";
+ String oidUrl = basicUrl + SHA256_EG;
+
+ Matcher m = p.matcher(batchUrl);
+ assertTrue(m.find());
+ assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI));
+ assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX));
+ assertEquals("test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY));
+ assertEquals("batch", m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT));
+
+ m = p.matcher(oidUrl);
+ assertTrue(m.find());
+ assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI));
+ assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX));
+ assertEquals("test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY));
+ assertEquals(SHA256_EG, m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT));
+ }
+
+ @Test
+ public void testRegexGroupsNestedRepo() throws Exception {
+
+ Pattern p = Pattern.compile(FilestoreServlet.REGEX_PATH);
+
+ String basicUrl = "https://localhost:8080/r/nested/test.git/info/lfs/objects/";
+ String batchUrl = basicUrl + "batch";
+ String oidUrl = basicUrl + SHA256_EG;
+
+ Matcher m = p.matcher(batchUrl);
+ assertTrue(m.find());
+ assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI));
+ assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX));
+ assertEquals("nested/test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY));
+ assertEquals("batch", m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT));
+
+ m = p.matcher(oidUrl);
+ assertTrue(m.find());
+ assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI));
+ assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX));
+ assertEquals("nested/test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY));
+ assertEquals(SHA256_EG, m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT));
+ }
+
+ @Test
+ public void testDownload() throws Exception {
+
+ FileUtils.delete(filestore().getStorageFolder());
+ filestore().clearFilestoreCache();
+
+ RepositoryModel r = gitblit().getRepositoryModel(repoName);
+
+ UserModel u = new UserModel("admin");
+ u.canAdmin = true;
+
+ //No upload limit
+ settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);
+
+ final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
+
+ //Emulate a pre-existing Git-LFS repository by using using internal pre-tested methods
+ assertEquals(Status.Available, filestore().uploadBlob(blob.hash, blob.length, u, r, new ByteArrayInputStream(blob.blob)));
+
+ final String downloadURL = GitBlitSuite.url + repoLfs + blob.hash;
+
+ HttpClient client = HttpClientBuilder.create().build();
+ HttpGet request = new HttpGet(downloadURL);
+
+ // add request header
+ request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
+ HttpResponse response = client.execute(request);
+
+ assertEquals(200, response.getStatusLine().getStatusCode());
+
+ String content = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+
+ String expectedContent = String.format("{%s:%s,%s:%d,%s:{%s:{%s:%s}}}",
+ "\"oid\"", "\"" + blob.hash + "\"",
+ "\"size\"", blob.length,
+ "\"actions\"",
+ "\"download\"",
+ "\"href\"", "\"" + downloadURL + "\"");
+
+ assertEquals(expectedContent, content);
+
+
+ //Now try the binary download
+ request.removeHeaders(HttpHeaders.ACCEPT);
+ response = client.execute(request);
+
+ assertEquals(200, response.getStatusLine().getStatusCode());
+
+ byte[] dlData = IOUtils.toByteArray(response.getEntity().getContent());
+
+ assertArrayEquals(blob.blob, dlData);
+
+ }
+
+ @Test
+ public void testDownloadMultiple() throws Exception {
+
+ FileUtils.delete(filestore().getStorageFolder());
+ filestore().clearFilestoreCache();
+
+ RepositoryModel r = gitblit().getRepositoryModel(repoName);
+
+ UserModel u = new UserModel("admin");
+ u.canAdmin = true;
+
+ //No upload limit
+ settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);
+
+ final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
+
+ //Emulate a pre-existing Git-LFS repository by using using internal pre-tested methods
+ assertEquals(Status.Available, filestore().uploadBlob(blob.hash, blob.length, u, r, new ByteArrayInputStream(blob.blob)));
+
+ final String batchURL = GitBlitSuite.url + repoLfs + "batch";
+ final String downloadURL = GitBlitSuite.url + repoLfs + blob.hash;
+
+ HttpClient client = HttpClientBuilder.create().build();
+ HttpPost request = new HttpPost(batchURL);
+
+ // add request header
+ request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
+ request.addHeader(HttpHeaders.CONTENT_ENCODING, FilestoreServlet.GIT_LFS_META_MIME);
+
+ String content = String.format("{%s:%s,%s:[{%s:%s,%s:%d},{%s:%s,%s:%d}]}",
+ "\"operation\"", "\"download\"",
+ "\"objects\"",
+ "\"oid\"", "\"" + blob.hash + "\"",
+ "\"size\"", blob.length,
+ "\"oid\"", "\"" + SHA256_EG + "\"",
+ "\"size\"", 0);
+
+ HttpEntity entity = new ByteArrayEntity(content.getBytes("UTF-8"));
+ request.setEntity(entity);
+
+ HttpResponse response = client.execute(request);
+
+ String responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+ assertEquals(200, response.getStatusLine().getStatusCode());
+
+ String expectedContent = String.format("{%s:[{%s:%s,%s:%d,%s:{%s:{%s:%s}}},{%s:%s,%s:%d,%s:{%s:%s,%s:%d}}]}",
+ "\"objects\"",
+ "\"oid\"", "\"" + blob.hash + "\"",
+ "\"size\"", blob.length,
+ "\"actions\"",
+ "\"download\"",
+ "\"href\"", "\"" + downloadURL + "\"",
+ "\"oid\"", "\"" + SHA256_EG + "\"",
+ "\"size\"", 0,
+ "\"error\"",
+ "\"message\"", "\"Object not available\"",
+ "\"code\"", 404
+ );
+
+ assertEquals(expectedContent, responseMessage);
+ }
+
+ @Test
+ public void testDownloadUnavailable() throws Exception {
+
+ FileUtils.delete(filestore().getStorageFolder());
+ filestore().clearFilestoreCache();
+
+ //No upload limit
+ settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);
+
+ final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
+
+ final String downloadURL = GitBlitSuite.url + repoLfs + blob.hash;
+
+ HttpClient client = HttpClientBuilder.create().build();
+ HttpGet request = new HttpGet(downloadURL);
+
+ // add request header
+ request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
+ HttpResponse response = client.execute(request);
+
+ assertEquals(404, response.getStatusLine().getStatusCode());
+
+ String content = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+
+ String expectedError = String.format("{%s:%s,%s:%d}",
+ "\"message\"", "\"Object not available\"",
+ "\"code\"", 404);
+
+ assertEquals(expectedError, content);
+ }
+
+ @Test
+ public void testUpload() throws Exception {
+
+ FileUtils.delete(filestore().getStorageFolder());
+ filestore().clearFilestoreCache();
+
+ RepositoryModel r = gitblit().getRepositoryModel(repoName);
+
+ UserModel u = new UserModel("admin");
+ u.canAdmin = true;
+
+ //No upload limit
+ settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);
+
+ final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
+
+ final String expectedUploadURL = GitBlitSuite.url + repoLfs + blob.hash;
+ final String initialUploadURL = GitBlitSuite.url + repoLfs + "batch";
+
+ HttpClient client = HttpClientBuilder.create().build();
+ HttpPost request = new HttpPost(initialUploadURL);
+
+ // add request header
+ request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
+ request.addHeader(HttpHeaders.CONTENT_ENCODING, FilestoreServlet.GIT_LFS_META_MIME);
+
+ String content = String.format("{%s:%s,%s:[{%s:%s,%s:%d}]}",
+ "\"operation\"", "\"upload\"",
+ "\"objects\"",
+ "\"oid\"", "\"" + blob.hash + "\"",
+ "\"size\"", blob.length);
+
+ HttpEntity entity = new ByteArrayEntity(content.getBytes("UTF-8"));
+ request.setEntity(entity);
+
+ HttpResponse response = client.execute(request);
+ String responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+ assertEquals(200, response.getStatusLine().getStatusCode());
+
+ String expectedContent = String.format("{%s:[{%s:%s,%s:%d,%s:{%s:{%s:%s}}}]}",
+ "\"objects\"",
+ "\"oid\"", "\"" + blob.hash + "\"",
+ "\"size\"", blob.length,
+ "\"actions\"",
+ "\"upload\"",
+ "\"href\"", "\"" + expectedUploadURL + "\"");
+
+ assertEquals(expectedContent, responseMessage);
+
+
+ //Now try to upload the binary download
+ HttpPut putRequest = new HttpPut(expectedUploadURL);
+ putRequest.setEntity(new ByteArrayEntity(blob.blob));
+ response = client.execute(putRequest);
+
+ responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+
+ assertEquals(200, response.getStatusLine().getStatusCode());
+
+ //Confirm behind the scenes that it is available
+ ByteArrayOutputStream savedBlob = new ByteArrayOutputStream();
+ assertEquals(Status.Available, filestore().downloadBlob(blob.hash, u, r, savedBlob));
+ assertArrayEquals(blob.blob, savedBlob.toByteArray());
+ }
+
+ @Test
+ public void testMalformedUpload() throws Exception {
+
+ FileUtils.delete(filestore().getStorageFolder());
+ filestore().clearFilestoreCache();
+
+ //No upload limit
+ settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);
+
+ final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
+
+ final String initialUploadURL = GitBlitSuite.url + repoLfs + "batch";
+
+ HttpClient client = HttpClientBuilder.create().build();
+ HttpPost request = new HttpPost(initialUploadURL);
+
+ // add request header
+ request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
+ request.addHeader(HttpHeaders.CONTENT_ENCODING, FilestoreServlet.GIT_LFS_META_MIME);
+
+ //Malformed JSON, comma instead of colon and unquoted strings
+ String content = String.format("{%s:%s,%s:[{%s:%s,%s,%d}]}",
+ "operation", "upload",
+ "objects",
+ "oid", blob.hash,
+ "size", blob.length);
+
+ HttpEntity entity = new ByteArrayEntity(content.getBytes("UTF-8"));
+ request.setEntity(entity);
+
+ HttpResponse response = client.execute(request);
+ String responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+ assertEquals(400, response.getStatusLine().getStatusCode());
+
+ String expectedError = String.format("{%s:%s,%s:%d}",
+ "\"message\"", "\"Malformed Git-LFS request\"",
+ "\"code\"", 400);
+
+ assertEquals(expectedError, responseMessage);
+ }
+
+}
diff --git a/src/test/java/com/gitblit/tests/GitBlitSuite.java b/src/test/java/com/gitblit/tests/GitBlitSuite.java
index 5a7dcea..b01c82c 100644
--- a/src/test/java/com/gitblit/tests/GitBlitSuite.java
+++ b/src/test/java/com/gitblit/tests/GitBlitSuite.java
@@ -63,9 +63,10 @@
GitBlitTest.class, FederationTests.class, RpcTests.class, GitServletTest.class, GitDaemonTest.class,
SshDaemonTest.class, GroovyScriptTest.class, LuceneExecutorTest.class, RepositoryModelTest.class,
FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdAuthenticationTest.class,
- ModelUtilsTest.class, JnaUtilsTest.class, LdapSyncServiceTest.class, FileTicketServiceTest.class,
+ ModelUtilsTest.class, JnaUtilsTest.class, LdapSyncServiceTest.class, FileTicketServiceTest.class,
BranchTicketServiceTest.class, RedisTicketServiceTest.class, AuthenticationManagerTest.class,
- SshKeysDispatcherTest.class })
+ SshKeysDispatcherTest.class, UITicketTest.class, PathUtilsTest.class, SshKerberosAuthenticationTest.class,
+ GravatarTest.class, FilestoreManagerTest.class, FilestoreServletTest.class })
public class GitBlitSuite {
public static final File BASEFOLDER = new File("data");
@@ -110,11 +111,11 @@
return getRepository("test/gitective.git");
}
- public static Repository getTicketsTestRepository() {
- JGitUtils.createRepository(REPOSITORIES, "gb-tickets.git").close();
- return getRepository("gb-tickets.git");
- }
-
+ public static Repository getTicketsTestRepository() {
+ JGitUtils.createRepository(REPOSITORIES, "gb-tickets.git").close();
+ return getRepository("gb-tickets.git");
+ }
+
private static Repository getRepository(String name) {
try {
File gitDir = FileKey.resolve(new File(REPOSITORIES, name), FS.DETECTED);
diff --git a/src/test/java/com/gitblit/tests/GitBlitTest.java b/src/test/java/com/gitblit/tests/GitBlitTest.java
index 9eaae7f..e873ced 100644
--- a/src/test/java/com/gitblit/tests/GitBlitTest.java
+++ b/src/test/java/com/gitblit/tests/GitBlitTest.java
@@ -176,7 +176,7 @@
@Test
public void testAuthentication() throws Exception {
- assertTrue(authentication().authenticate("admin", "admin".toCharArray()) != null);
+ assertTrue(authentication().authenticate("admin", "admin".toCharArray(), null) != null);
}
@Test
diff --git a/src/test/java/com/gitblit/tests/GitblitUnitTest.java b/src/test/java/com/gitblit/tests/GitblitUnitTest.java
index 9dceaaf..58bc60e 100644
--- a/src/test/java/com/gitblit/tests/GitblitUnitTest.java
+++ b/src/test/java/com/gitblit/tests/GitblitUnitTest.java
@@ -18,6 +18,7 @@
import com.gitblit.IStoredSettings;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IFilestoreManager;
import com.gitblit.manager.IGitblit;
import com.gitblit.manager.INotificationManager;
import com.gitblit.manager.IProjectManager;
@@ -64,4 +65,8 @@
public static IGitblit gitblit() {
return GitblitContext.getManager(IGitblit.class);
}
+
+ public static IFilestoreManager filestore() {
+ return GitblitContext.getManager(IFilestoreManager.class);
+ }
}
diff --git a/src/test/java/com/gitblit/tests/GravatarTest.java b/src/test/java/com/gitblit/tests/GravatarTest.java
new file mode 100644
index 0000000..ba989d5
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/GravatarTest.java
@@ -0,0 +1,74 @@
+package com.gitblit.tests;
+
+import org.junit.Test;
+
+import com.gitblit.AvatarGenerator;
+import com.gitblit.GravatarGenerator;
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.guice.AvatarGeneratorProvider;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.RuntimeManager;
+import com.gitblit.tests.mock.MemorySettings;
+import com.gitblit.utils.ActivityUtils;
+import com.gitblit.utils.XssFilter;
+import com.gitblit.utils.XssFilter.AllowXssFilter;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+public class GravatarTest extends GitblitUnitTest {
+
+ public static class AvatarModule extends AbstractModule {
+ private final IStoredSettings settings;
+
+ AvatarModule(IStoredSettings settings) {
+ this.settings = settings;
+ }
+
+ @Override
+ protected void configure() {
+ bind(IStoredSettings.class).toInstance(settings);
+ bind(XssFilter.class).to(AllowXssFilter.class);
+ bind(IRuntimeManager.class).to(RuntimeManager.class);
+ bind(AvatarGenerator.class).toProvider(AvatarGeneratorProvider.class);
+ }
+ }
+
+ @Test
+ public void gravatarIdenticonTest() {
+ IStoredSettings settings = new MemorySettings();
+ settings.overrideSetting(Keys.web.avatarClass, GravatarGenerator.class.getName());
+
+ Injector injector = Guice.createInjector(new AvatarModule(settings));
+ AvatarGenerator avatarGenerator = injector.getInstance(AvatarGenerator.class);
+
+ String username = "username";
+ String emailAddress = "emailAddress";
+ int width = 10;
+
+ String url = avatarGenerator.getURL(username, emailAddress, true, width);
+ assertNotNull(url);
+
+ assertEquals(ActivityUtils.getGravatarIdenticonUrl(emailAddress, width), url);
+ }
+
+ @Test
+ public void gravatarThumbnailTest() {
+ IStoredSettings settings = new MemorySettings();
+ settings.overrideSetting(Keys.web.avatarClass, GravatarGenerator.class.getName());
+
+ Injector injector = Guice.createInjector(new AvatarModule(settings));
+ AvatarGenerator avatarGenerator = injector.getInstance(AvatarGenerator.class);
+
+ String username = "username";
+ String emailAddress = "emailAddress";
+ int width = 10;
+
+ String url = avatarGenerator.getURL(username, emailAddress, false, width);
+ assertNotNull(url);
+
+ assertEquals(ActivityUtils.getGravatarThumbnailUrl(emailAddress, width), url);
+ }
+
+}
diff --git a/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java b/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java
index e2bb764..26a49b2 100644
--- a/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java
+++ b/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java
@@ -200,43 +200,43 @@
public void testAuthenticationManager()
{
MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true");
- UserModel user = auth.authenticate("user1", "pass1".toCharArray());
+ UserModel user = auth.authenticate("user1", "pass1".toCharArray(), null);
assertNotNull(user);
assertEquals("user1", user.username);
- user = auth.authenticate("user2", "pass2".toCharArray());
+ user = auth.authenticate("user2", "pass2".toCharArray(), null);
assertNotNull(user);
assertEquals("user2", user.username);
// Test different encryptions
- user = auth.authenticate("plain", "passWord".toCharArray());
+ user = auth.authenticate("plain", "passWord".toCharArray(), null);
assertNotNull(user);
assertEquals("plain", user.username);
MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false");
- user = auth.authenticate("crypt", "password".toCharArray());
+ user = auth.authenticate("crypt", "password".toCharArray(), null);
assertNotNull(user);
assertEquals("crypt", user.username);
- user = auth.authenticate("md5", "password".toCharArray());
+ user = auth.authenticate("md5", "password".toCharArray(), null);
assertNotNull(user);
assertEquals("md5", user.username);
- user = auth.authenticate("sha", "password".toCharArray());
+ user = auth.authenticate("sha", "password".toCharArray(), null);
assertNotNull(user);
assertEquals("sha", user.username);
// Test leading and trailing whitespace
- user = auth.authenticate("trailing", "whitespace".toCharArray());
+ user = auth.authenticate("trailing", "whitespace".toCharArray(), null);
assertNotNull(user);
assertEquals("trailing", user.username);
- user = auth.authenticate("tabbed", "frontAndBack".toCharArray());
+ user = auth.authenticate("tabbed", "frontAndBack".toCharArray(), null);
assertNotNull(user);
assertEquals("tabbed", user.username);
- user = auth.authenticate("leading", "whitespace".toCharArray());
+ user = auth.authenticate("leading", "whitespace".toCharArray(), null);
assertNotNull(user);
assertEquals("leading", user.username);
}
@@ -323,55 +323,55 @@
{
UserModel user = null;
MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true");
- user = auth.authenticate("user1", "".toCharArray());
+ user = auth.authenticate("user1", "".toCharArray(), null);
assertNull("User 'user1' falsely authenticated.", user);
- user = auth.authenticate("user1", "pass2".toCharArray());
+ user = auth.authenticate("user1", "pass2".toCharArray(), null);
assertNull("User 'user1' falsely authenticated.", user);
- user = auth.authenticate("user2", "lalala".toCharArray());
+ user = auth.authenticate("user2", "lalala".toCharArray(), null);
assertNull("User 'user2' falsely authenticated.", user);
- user = auth.authenticate("user3", "disabled".toCharArray());
+ user = auth.authenticate("user3", "disabled".toCharArray(), null);
assertNull("User 'user3' falsely authenticated.", user);
- user = auth.authenticate("user4", "disabled".toCharArray());
+ user = auth.authenticate("user4", "disabled".toCharArray(), null);
assertNull("User 'user4' falsely authenticated.", user);
- user = auth.authenticate("plain", "text".toCharArray());
+ user = auth.authenticate("plain", "text".toCharArray(), null);
assertNull("User 'plain' falsely authenticated.", user);
- user = auth.authenticate("plain", "password".toCharArray());
+ user = auth.authenticate("plain", "password".toCharArray(), null);
assertNull("User 'plain' falsely authenticated.", user);
MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false");
- user = auth.authenticate("crypt", "".toCharArray());
+ user = auth.authenticate("crypt", "".toCharArray(), null);
assertNull("User 'cyrpt' falsely authenticated.", user);
- user = auth.authenticate("crypt", "passwd".toCharArray());
+ user = auth.authenticate("crypt", "passwd".toCharArray(), null);
assertNull("User 'crypt' falsely authenticated.", user);
- user = auth.authenticate("md5", "".toCharArray());
+ user = auth.authenticate("md5", "".toCharArray(), null);
assertNull("User 'md5' falsely authenticated.", user);
- user = auth.authenticate("md5", "pwd".toCharArray());
+ user = auth.authenticate("md5", "pwd".toCharArray(), null);
assertNull("User 'md5' falsely authenticated.", user);
- user = auth.authenticate("sha", "".toCharArray());
+ user = auth.authenticate("sha", "".toCharArray(), null);
assertNull("User 'sha' falsely authenticated.", user);
- user = auth.authenticate("sha", "letmein".toCharArray());
+ user = auth.authenticate("sha", "letmein".toCharArray(), null);
assertNull("User 'sha' falsely authenticated.", user);
- user = auth.authenticate(" tabbed", "frontAndBack".toCharArray());
+ user = auth.authenticate(" tabbed", "frontAndBack".toCharArray(), null);
assertNull("User 'tabbed' falsely authenticated.", user);
- user = auth.authenticate(" leading", "whitespace".toCharArray());
+ user = auth.authenticate(" leading", "whitespace".toCharArray(), null);
assertNull("User 'leading' falsely authenticated.", user);
}
diff --git a/src/test/java/com/gitblit/tests/JGitUtilsTest.java b/src/test/java/com/gitblit/tests/JGitUtilsTest.java
index c2cfb33..c273e86 100644
--- a/src/test/java/com/gitblit/tests/JGitUtilsTest.java
+++ b/src/test/java/com/gitblit/tests/JGitUtilsTest.java
@@ -476,6 +476,15 @@
}
@Test
+ public void testFilesInPath2() throws Exception {
+ assertEquals(0, JGitUtils.getFilesInPath2(null, null, null).size());
+ Repository repository = GitBlitSuite.getHelloworldRepository();
+ List<PathModel> files = JGitUtils.getFilesInPath2(repository, null, null);
+ repository.close();
+ assertTrue(files.size() > 10);
+ }
+
+ @Test
public void testDocuments() throws Exception {
Repository repository = GitBlitSuite.getTicgitRepository();
List<String> extensions = Arrays.asList(new String[] { ".mkd", ".md" });
@@ -576,16 +585,16 @@
@Test
public void testZip() throws Exception {
- assertFalse(CompressionUtils.zip(null, null, null, null));
+ assertFalse(CompressionUtils.zip(null, null, null, null, null));
Repository repository = GitBlitSuite.getHelloworldRepository();
File zipFileA = new File(GitBlitSuite.REPOSITORIES, "helloworld.zip");
FileOutputStream fosA = new FileOutputStream(zipFileA);
- boolean successA = CompressionUtils.zip(repository, null, Constants.HEAD, fosA);
+ boolean successA = CompressionUtils.zip(repository, null, null, Constants.HEAD, fosA);
fosA.close();
File zipFileB = new File(GitBlitSuite.REPOSITORIES, "helloworld-java.zip");
FileOutputStream fosB = new FileOutputStream(zipFileB);
- boolean successB = CompressionUtils.zip(repository, "java.java", Constants.HEAD, fosB);
+ boolean successB = CompressionUtils.zip(repository, null, "java.java", Constants.HEAD, fosB);
fosB.close();
repository.close();
diff --git a/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java b/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java
index 7c84ecc..84dd138 100644
--- a/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java
+++ b/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java
@@ -240,23 +240,23 @@
@Test
public void testAuthenticationManager() {
- UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray());
+ UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray(), null);
assertNotNull(userOneModel);
assertNotNull(userOneModel.getTeam("git_admins"));
assertNotNull(userOneModel.getTeam("git_users"));
assertTrue(userOneModel.canAdmin);
- UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray());
+ UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray(), null);
assertNull(userOneModelFailedAuth);
- UserModel userTwoModel = auth.authenticate("UserTwo", "userTwoPassword".toCharArray());
+ UserModel userTwoModel = auth.authenticate("UserTwo", "userTwoPassword".toCharArray(), null);
assertNotNull(userTwoModel);
assertNotNull(userTwoModel.getTeam("git_users"));
assertNull(userTwoModel.getTeam("git_admins"));
assertNotNull(userTwoModel.getTeam("git admins"));
assertTrue(userTwoModel.canAdmin);
- UserModel userThreeModel = auth.authenticate("UserThree", "userThreePassword".toCharArray());
+ UserModel userThreeModel = auth.authenticate("UserThree", "userThreePassword".toCharArray(), null);
assertNotNull(userThreeModel);
assertNotNull(userThreeModel.getTeam("git_users"));
assertNull(userThreeModel.getTeam("git_admins"));
@@ -269,10 +269,10 @@
settings.put(Keys.realm.ldap.username, "");
settings.put(Keys.realm.ldap.password, "");
- UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray());
+ UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray(), null);
assertNotNull(userOneModel);
- UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray());
+ UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray(), null);
assertNull(userOneModelFailedAuth);
}
diff --git a/src/test/java/com/gitblit/tests/PathUtilsTest.java b/src/test/java/com/gitblit/tests/PathUtilsTest.java
new file mode 100644
index 0000000..209b8ee
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/PathUtilsTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.tests;
+
+import com.gitblit.utils.PathUtils;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+public class PathUtilsTest extends GitblitUnitTest {
+
+ private static final String[][][] testData = {
+
+ {
+ // Folder contents
+ {".gitignore","src/main/java/a.java", "src/main/java/b.java", "docs/c.md"},
+ // Expected after compressing
+ {".gitignore", "src/main/java/", "docs/"}
+ },
+
+ {
+ {".gitignore","src/main/java/a.java", "src/main/b.java", "docs/c.md"},
+ {".gitignore", "src/main/", "docs/"}
+ },
+
+ {
+ {".gitignore","src/x.java","src/main/java/a.java", "src/main/java/b.java", "docs/c.md"},
+ {".gitignore", "src/", "docs/"}
+ },
+ };
+
+
+
+
+ @Test
+ public void testCompressPaths() throws Exception {
+
+ for (String[][] test : testData ) {
+ assertArrayEquals(test[1], PathUtils.compressPaths(Arrays.asList(test[0])).toArray(new String[]{}));
+ }
+
+ }
+
+ @Test
+ public void testGetLastPathComponent() {
+ assertEquals(PathUtils.getLastPathComponent("/a/b/c/d/e.out"), "e.out");
+ assertEquals(PathUtils.getLastPathComponent("e.out"), "e.out");
+ assertEquals(PathUtils.getLastPathComponent("/a/b/c/d/"), "d");
+ assertEquals(PathUtils.getLastPathComponent("/a/b/c/d"), "d");
+ assertEquals(PathUtils.getLastPathComponent("/"), "/");
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java b/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java
index ad773b7..7136fa7 100644
--- a/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java
+++ b/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java
@@ -65,7 +65,7 @@
@Test
public void testAuthenticationManager() throws Exception {
AuthenticationManager auth = newAuthenticationManager();
- UserModel userModel = auth.authenticate("RedmineAdminId", "RedmineAPIKey".toCharArray());
+ UserModel userModel = auth.authenticate("RedmineAdminId", "RedmineAPIKey".toCharArray(), null);
assertThat(userModel.getName(), is("redmineadminid"));
assertThat(userModel.getDisplayName(), is("baz foo"));
assertThat(userModel.emailAddress, is("baz@example.com"));
diff --git a/src/test/java/com/gitblit/tests/SshDaemonTest.java b/src/test/java/com/gitblit/tests/SshDaemonTest.java
index dcaeaff..c5deb7d 100644
--- a/src/test/java/com/gitblit/tests/SshDaemonTest.java
+++ b/src/test/java/com/gitblit/tests/SshDaemonTest.java
@@ -19,8 +19,8 @@
import java.text.MessageFormat;
import java.util.List;
-import org.apache.sshd.ClientSession;
-import org.apache.sshd.SshClient;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.revwalk.RevCommit;
diff --git a/src/test/java/com/gitblit/tests/SshKerberosAuthenticationTest.java b/src/test/java/com/gitblit/tests/SshKerberosAuthenticationTest.java
new file mode 100644
index 0000000..4ba16b6
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/SshKerberosAuthenticationTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2015 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.tests;
+
+import org.apache.sshd.server.auth.gss.GSSAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import com.gitblit.manager.AuthenticationManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IUserManager;
+import com.gitblit.models.UserModel;
+import com.gitblit.tests.mock.MemorySettings;
+import com.gitblit.transport.ssh.SshDaemonClient;
+import com.gitblit.transport.ssh.SshKrbAuthenticator;
+
+public class SshKerberosAuthenticationTest extends GitblitUnitTest {
+
+ private static class UserModelWrapper {
+ public UserModel um;
+ }
+
+ @Test
+ public void testUserManager() {
+ IRuntimeManager rm = Mockito.mock(IRuntimeManager.class);
+
+ //Build an UserManager that can build a UserModel
+ IUserManager im = Mockito.mock(IUserManager.class);
+ Mockito.doAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ String user = (String) args[0];
+ return new UserModel(user);
+ }
+ }).when(im).getUserModel(Mockito.anyString());
+
+ AuthenticationManager am = new AuthenticationManager(rm, im);
+
+ GSSAuthenticator gssAuthenticator = new SshKrbAuthenticator(new MemorySettings(), am);
+
+ ServerSession session = Mockito.mock(ServerSession.class);
+
+ //Build an SshDaemonClient that can set and get the UserModel
+ final UserModelWrapper umw = new UserModelWrapper();
+ SshDaemonClient client = Mockito.mock(SshDaemonClient.class);
+ Mockito.when(client.getUser()).thenReturn(umw.um);
+ Mockito.doAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ UserModel um = (UserModel) args[0];
+ umw.um = um;
+ return null;
+ }
+ }).when(client).setUser(Mockito.any(UserModel.class));
+
+ Mockito.when(session.getAttribute(SshDaemonClient.KEY)).thenReturn(client);
+ Assert.assertTrue(gssAuthenticator.validateIdentity(session, "jhappy"));
+
+ }
+}
diff --git a/src/test/java/com/gitblit/tests/SshUnitTest.java b/src/test/java/com/gitblit/tests/SshUnitTest.java
index 43b51b7..27b4ec7 100644
--- a/src/test/java/com/gitblit/tests/SshUnitTest.java
+++ b/src/test/java/com/gitblit/tests/SshUnitTest.java
@@ -26,10 +26,10 @@
import java.security.PublicKey;
import java.util.concurrent.atomic.AtomicBoolean;
-import org.apache.sshd.ClientChannel;
-import org.apache.sshd.ClientSession;
-import org.apache.sshd.SshClient;
import org.apache.sshd.client.ServerKeyVerifier;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.util.SecurityUtils;
import org.junit.After;
import org.junit.AfterClass;
diff --git a/src/test/java/com/gitblit/tests/StringUtilsTest.java b/src/test/java/com/gitblit/tests/StringUtilsTest.java
index 0fd42aa..7176b88 100644
--- a/src/test/java/com/gitblit/tests/StringUtilsTest.java
+++ b/src/test/java/com/gitblit/tests/StringUtilsTest.java
@@ -50,7 +50,7 @@
public void testEscapeForHtml() throws Exception {
String input = "& < > \" \t";
String outputNoChange = "& < > " \t";
- String outputChange = "& < > " ";
+ String outputChange = "& < > " ";
assertEquals(outputNoChange, StringUtils.escapeForHtml(input, false));
assertEquals(outputChange, StringUtils.escapeForHtml(input, true));
}
diff --git a/src/test/java/com/gitblit/tests/TicketServiceTest.java b/src/test/java/com/gitblit/tests/TicketServiceTest.java
index 1676e34..c654383 100644
--- a/src/test/java/com/gitblit/tests/TicketServiceTest.java
+++ b/src/test/java/com/gitblit/tests/TicketServiceTest.java
@@ -293,9 +293,47 @@
assertTrue("failed to delete label " + label.name, service.deleteLabel(getRepository(), label.name, "lucifer"));
}
}
-
-
-
+
+ @Test
+ public void testPriorityAndSeverity() throws Exception {
+ // C1: create and insert a ticket
+ Change c1 = newChange("testPriorityAndSeverity() " + Long.toHexString(System.currentTimeMillis()));
+ TicketModel ticket = service.createTicket(getRepository(), c1);
+ assertTrue(ticket.number > 0);
+ assertEquals(TicketModel.Priority.Normal, ticket.priority);
+ assertEquals(TicketModel.Severity.Unrated, ticket.severity);
+
+ TicketModel constructed = service.getTicket(getRepository(), ticket.number);
+ compare(ticket, constructed);
+
+ // C2: Change Priority max
+ Change c2 = new Change("C2");
+ c2.setField(Field.priority, TicketModel.Priority.Urgent);
+ constructed = service.updateTicket(getRepository(), ticket.number, c2);
+ assertNotNull(constructed);
+ assertEquals(2, constructed.changes.size());
+ assertEquals(TicketModel.Priority.Urgent, constructed.priority);
+ assertEquals(TicketModel.Severity.Unrated, constructed.severity);
+
+ // C3: Change Severity max
+ Change c3 = new Change("C3");
+ c3.setField(Field.severity, TicketModel.Severity.Catastrophic);
+ constructed = service.updateTicket(getRepository(), ticket.number, c3);
+ assertNotNull(constructed);
+ assertEquals(3, constructed.changes.size());
+ assertEquals(TicketModel.Priority.Urgent, constructed.priority);
+ assertEquals(TicketModel.Severity.Catastrophic, constructed.severity);
+
+ // C4: Change Priority min
+ Change c4 = new Change("C3");
+ c4.setField(Field.priority, TicketModel.Priority.Low);
+ constructed = service.updateTicket(getRepository(), ticket.number, c4);
+ assertNotNull(constructed);
+ assertEquals(4, constructed.changes.size());
+ assertEquals(TicketModel.Priority.Low, constructed.priority);
+ assertEquals(TicketModel.Severity.Catastrophic, constructed.severity);
+ }
+
private Change newChange(String summary) {
Change change = new Change("C1");
change.setField(Field.title, summary);
diff --git a/src/test/java/com/gitblit/tests/UITicketTest.java b/src/test/java/com/gitblit/tests/UITicketTest.java
new file mode 100644
index 0000000..54aa1e1
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/UITicketTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * 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.
+ */
+package com.gitblit.tests;
+
+import java.io.File;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.bouncycastle.util.Arrays;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.manager.INotificationManager;
+import com.gitblit.manager.IPluginManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IUserManager;
+import com.gitblit.manager.NotificationManager;
+import com.gitblit.manager.PluginManager;
+import com.gitblit.manager.RepositoryManager;
+import com.gitblit.manager.RuntimeManager;
+import com.gitblit.manager.UserManager;
+import com.gitblit.models.Mailing;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.TicketModel;
+import com.gitblit.models.TicketModel.Attachment;
+import com.gitblit.models.TicketModel.Change;
+import com.gitblit.models.TicketModel.Field;
+import com.gitblit.models.TicketModel.Patchset;
+import com.gitblit.models.TicketModel.Priority;
+import com.gitblit.models.TicketModel.Severity;
+import com.gitblit.models.TicketModel.Status;
+import com.gitblit.models.TicketModel.Type;
+import com.gitblit.tests.mock.MemorySettings;
+import com.gitblit.tickets.ITicketService;
+import com.gitblit.tickets.ITicketService.TicketFilter;
+import com.gitblit.tickets.QueryResult;
+import com.gitblit.tickets.TicketIndexer.Lucene;
+import com.gitblit.tickets.BranchTicketService;
+import com.gitblit.tickets.TicketLabel;
+import com.gitblit.tickets.TicketMilestone;
+import com.gitblit.tickets.TicketNotifier;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.XssFilter;
+import com.gitblit.utils.XssFilter.AllowXssFilter;
+
+/**
+ * Generates the range of tickets to ease testing of the look and feel of tickets
+ */
+public class UITicketTest extends GitblitUnitTest {
+
+ private ITicketService service;
+ final String repoName = "UITicketTest.git";
+ final RepositoryModel repo = new RepositoryModel(repoName, null, null, null);
+
+ protected ITicketService getService(boolean deleteAll) throws Exception {
+
+ IStoredSettings settings = getSettings(deleteAll);
+ XssFilter xssFilter = new AllowXssFilter();
+ IRuntimeManager runtimeManager = new RuntimeManager(settings, xssFilter).start();
+ IPluginManager pluginManager = new PluginManager(runtimeManager).start();
+ INotificationManager notificationManager = new NotificationManager(settings).start();
+ IUserManager userManager = new UserManager(runtimeManager, pluginManager).start();
+ IRepositoryManager repositoryManager = new RepositoryManager(runtimeManager, pluginManager, userManager).start();
+
+ BranchTicketService service = new BranchTicketService(
+ runtimeManager,
+ pluginManager,
+ notificationManager,
+ userManager,
+ repositoryManager).start();
+
+ if (deleteAll) {
+ service.deleteAll(repo);
+ }
+ return service;
+ }
+
+ protected IStoredSettings getSettings(boolean deleteAll) throws Exception {
+ File dir = new File(GitBlitSuite.REPOSITORIES, repoName);
+ if (deleteAll) {
+ FileUtils.deleteDirectory(dir);
+ JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, repoName).close();
+ }
+
+ File luceneDir = new File(dir, "tickets/lucene");
+ luceneDir.mkdirs();
+
+ Map<String, Object> map = new HashMap<String, Object>();
+ map.put(Keys.git.repositoriesFolder, GitBlitSuite.REPOSITORIES.getAbsolutePath());
+ map.put(Keys.tickets.indexFolder, luceneDir.getAbsolutePath());
+
+ IStoredSettings settings = new MemorySettings(map);
+ return settings;
+ }
+
+ @Before
+ public void setup() throws Exception {
+ service = getService(true);
+ }
+
+ @After
+ public void cleanup() {
+ service.stop();
+ }
+
+ @Test
+ public void UITicketOptions() throws Exception {
+
+ for (TicketModel.Type t : TicketModel.Type.values())
+ {
+ for (TicketModel.Priority p : TicketModel.Priority.values())
+ {
+ for (TicketModel.Severity s : TicketModel.Severity.values())
+ {
+ assertNotNull(service.createTicket(repo, newChange(t, p, s)));
+ }
+ }
+ }
+ }
+
+ private Change newChange(Type type, Priority priority, Severity severity) {
+ Change change = new Change("JUnit");
+ change.setField(Field.title, String.format("Type: %s | Priority: %s | Severity: %s", type, priority, severity));
+ change.setField(Field.type, type);
+ change.setField(Field.severity, severity);
+ change.setField(Field.priority, priority);
+ return change;
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java b/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java
index 7b56362..8897ef7 100644
--- a/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java
+++ b/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java
@@ -30,6 +30,7 @@
import com.gitblit.models.SettingModel;
import com.gitblit.utils.XssFilter;
import com.gitblit.utils.XssFilter.AllowXssFilter;
+import com.google.inject.Injector;
public class MockRuntimeManager implements IRuntimeManager {
@@ -59,6 +60,11 @@
}
@Override
+ public Injector getInjector() {
+ return null;
+ }
+
+ @Override
public void setBaseFolder(File folder) {
this.baseFolder = folder;
}
@@ -79,26 +85,6 @@
}
@Override
- public boolean isServingRepositories() {
- return true;
- }
-
- @Override
- public boolean isServingHTTP() {
- return true;
- }
-
- @Override
- public boolean isServingGIT() {
- return true;
- }
-
- @Override
- public boolean isServingSSH() {
- return true;
- }
-
- @Override
public boolean isDebugMode() {
return true;
}