initial commit
This commit is contained in:
commit
906f8ec915
31
.classpath
Normal file
31
.classpath
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
|
||||||
|
<classpathentry kind="src" path="src"/>
|
||||||
|
<classpathentry kind="src" path="dev-resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" path="resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="lib" path="target/classes">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry exported="true" kind="con" path="ccw.LEININGEN_CONTAINER">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="blarg/target/native/macosx/x86_64"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry exported="true" kind="lib" path="classes">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
|
</classpath>
|
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/target
|
||||||
|
/lib
|
||||||
|
/classes
|
||||||
|
/checkouts
|
||||||
|
pom.xml
|
||||||
|
*.jar
|
||||||
|
*.class
|
||||||
|
/.lein-*
|
||||||
|
/.env
|
||||||
|
/*.log
|
||||||
|
/.settings
|
29
.project
Normal file
29
.project
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>blarg</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>ccw.builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>ccw.leiningen.builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>ccw.leiningen.nature</nature>
|
||||||
|
<nature>ccw.nature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
1
Procfile
Normal file
1
Procfile
Normal file
|
@ -0,0 +1 @@
|
||||||
|
web: lein with-profile production trampoline ring server
|
19
README.md
Normal file
19
README.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# blarg
|
||||||
|
|
||||||
|
FIXME
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
You will need [Leiningen][1] 2.0 or above installed.
|
||||||
|
|
||||||
|
[1]: https://github.com/technomancy/leiningen
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
To start a web server for the application, run:
|
||||||
|
|
||||||
|
lein ring server
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright © 2013 FIXME
|
27
project.clj
Normal file
27
project.clj
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
(defproject blarg "0.1"
|
||||||
|
:description "webapp for http://blarg.ca/"
|
||||||
|
:url "http://blarg.ca/"
|
||||||
|
:dependencies [[org.clojure/clojure "1.5.1"]
|
||||||
|
[lib-noir "0.5.4"]
|
||||||
|
[compojure "1.1.5"]
|
||||||
|
[ring-server "0.2.8"]
|
||||||
|
[clabango "0.5"]
|
||||||
|
[com.taoensso/timbre "1.6.0"]
|
||||||
|
[com.postspectacular/rotor "0.1.0"]
|
||||||
|
[com.taoensso/tower "1.5.1"]
|
||||||
|
[markdown-clj "0.9.19"]
|
||||||
|
[com.ashafa/clutch "0.4.0-RC1"]
|
||||||
|
[slugger "1.0.1"]
|
||||||
|
[clj-time "0.5.0"]
|
||||||
|
[org.clojure/math.numeric-tower "0.0.2"]]
|
||||||
|
:plugins [[lein-ring "0.8.5"]]
|
||||||
|
:ring {:handler blarg.handler/war-handler
|
||||||
|
:init blarg.handler/init
|
||||||
|
:destroy blarg.handler/destroy}
|
||||||
|
:profiles
|
||||||
|
{:production {:ring {:open-browser? false
|
||||||
|
:stacktraces? false
|
||||||
|
:auto-reload? false}}
|
||||||
|
:dev {:dependencies [[ring-mock "0.1.3"]
|
||||||
|
[ring/ring-devel "1.1.8"]]}}
|
||||||
|
:min-lein-version "2.0.0")
|
86
resources/public/css/bootstrap-markdown.css
vendored
Normal file
86
resources/public/css/bootstrap-markdown.css
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
.clearfix {
|
||||||
|
*zoom: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearfix:before,.clearfix:after {
|
||||||
|
display: table;
|
||||||
|
content: "";
|
||||||
|
line-height: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearfix:after {
|
||||||
|
clear: both
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-text {
|
||||||
|
font: 0/0 a;
|
||||||
|
color: transparent;
|
||||||
|
text-shadow: none;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-block-level {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 30px;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-editor {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid #ddd
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-editor>.md-header,.md-editor .md-footer {
|
||||||
|
display: block;
|
||||||
|
padding: 6px 4px;
|
||||||
|
background: #fff
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-editor>.md-preview {
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1px dashed #ddd;
|
||||||
|
border-bottom: 1px dashed #ddd;
|
||||||
|
min-height: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-editor>textarea {
|
||||||
|
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: 0;
|
||||||
|
outline: thin dotted \9;
|
||||||
|
margin: 0;
|
||||||
|
display: block;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px dashed #ddd;
|
||||||
|
border-bottom: 1px dashed #ddd;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
background: #eee
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-editor>textarea:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
background: #fff
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-editor.active {
|
||||||
|
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px
|
||||||
|
rgba(82, 168, 236, .6);
|
||||||
|
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px
|
||||||
|
rgba(82, 168, 236, .6);
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px
|
||||||
|
rgba(82, 168, 236, .6);
|
||||||
|
border-color: rgba(82, 168, 236, 0.8);
|
||||||
|
outline: 0;
|
||||||
|
outline: thin dotted \9;
|
||||||
|
-webkit-transition: border linear .2s, box-shadow linear .2s;
|
||||||
|
-moz-transition: border linear .2s, box-shadow linear .2s;
|
||||||
|
-o-transition: border linear .2s, box-shadow linear .2s;
|
||||||
|
transition: border linear .2s, box-shadow linear .2s
|
||||||
|
}
|
9
resources/public/css/bootstrap-responsive.min.css
vendored
Normal file
9
resources/public/css/bootstrap-responsive.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
9
resources/public/css/bootstrap.min.css
vendored
Normal file
9
resources/public/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
30
resources/public/css/prettify.css
Normal file
30
resources/public/css/prettify.css
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
.com { color: #93a1a1; }
|
||||||
|
.lit { color: #195f91; }
|
||||||
|
.pun, .opn, .clo { color: #93a1a1; }
|
||||||
|
.fun { color: #dc322f; }
|
||||||
|
.str, .atv { color: #D14; }
|
||||||
|
.kwd, .prettyprint .tag { color: #1e347b; }
|
||||||
|
.typ, .atn, .dec, .var { color: teal; }
|
||||||
|
.pln { color: #48484c; }
|
||||||
|
|
||||||
|
.prettyprint {
|
||||||
|
padding: 8px;
|
||||||
|
background-color: #f7f7f9;
|
||||||
|
border: 1px solid #e1e1e8;
|
||||||
|
}
|
||||||
|
.prettyprint.linenums {
|
||||||
|
-webkit-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||||
|
-moz-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||||
|
box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specify class=linenums on a pre to get line numbering */
|
||||||
|
ol.linenums {
|
||||||
|
margin: 0 0 0 33px; /* IE indents via margin-left */
|
||||||
|
}
|
||||||
|
ol.linenums li {
|
||||||
|
padding-left: 12px;
|
||||||
|
color: #bebec5;
|
||||||
|
line-height: 20px;
|
||||||
|
text-shadow: 0 1px 0 #fff;
|
||||||
|
}
|
61
resources/public/css/screen.css
Normal file
61
resources/public/css/screen.css
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content[role="main"] {
|
||||||
|
/*
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.content[role="main"] > div,
|
||||||
|
.content[role="main"] > ul,
|
||||||
|
.content[role="main"] > p,
|
||||||
|
.content[role="main"] > form {
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-post {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unpublished-post {
|
||||||
|
background-color: #e1edff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post .header {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post .header h2 {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
float: none;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
652
resources/public/css/select2.css
Normal file
652
resources/public/css/select2.css
Normal file
|
@ -0,0 +1,652 @@
|
||||||
|
/*
|
||||||
|
Version: 3.4.0 Timestamp: Tue May 14 08:27:33 PDT 2013
|
||||||
|
*/
|
||||||
|
.select2-container {
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
/* inline-block for ie7 */
|
||||||
|
zoom: 1;
|
||||||
|
*display: inline;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container,
|
||||||
|
.select2-drop,
|
||||||
|
.select2-search,
|
||||||
|
.select2-search input{
|
||||||
|
/*
|
||||||
|
Force border-box so that % widths fit the parent
|
||||||
|
container without overlap because of margin/padding.
|
||||||
|
|
||||||
|
More Info : http://www.quirksmode.org/css/box.html
|
||||||
|
*/
|
||||||
|
-webkit-box-sizing: border-box; /* webkit */
|
||||||
|
-khtml-box-sizing: border-box; /* konqueror */
|
||||||
|
-moz-box-sizing: border-box; /* firefox */
|
||||||
|
-ms-box-sizing: border-box; /* ie */
|
||||||
|
box-sizing: border-box; /* css3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-choice {
|
||||||
|
display: block;
|
||||||
|
height: 26px;
|
||||||
|
padding: 0 0 0 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 26px;
|
||||||
|
color: #444;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
-webkit-background-clip: padding-box;
|
||||||
|
-moz-background-clip: padding;
|
||||||
|
background-clip: padding-box;
|
||||||
|
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
background-color: #fff;
|
||||||
|
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.5, white));
|
||||||
|
background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 50%);
|
||||||
|
background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 50%);
|
||||||
|
background-image: -o-linear-gradient(bottom, #eeeeee 0%, #ffffff 50%);
|
||||||
|
background-image: -ms-linear-gradient(top, #ffffff 0%, #eeeeee 50%);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0);
|
||||||
|
background-image: linear-gradient(top, #ffffff 0%, #eeeeee 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container.select2-drop-above .select2-choice {
|
||||||
|
border-bottom-color: #aaa;
|
||||||
|
|
||||||
|
-webkit-border-radius:0 0 4px 4px;
|
||||||
|
-moz-border-radius:0 0 4px 4px;
|
||||||
|
border-radius:0 0 4px 4px;
|
||||||
|
|
||||||
|
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.9, white));
|
||||||
|
background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 90%);
|
||||||
|
background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 90%);
|
||||||
|
background-image: -o-linear-gradient(bottom, #eeeeee 0%, white 90%);
|
||||||
|
background-image: -ms-linear-gradient(top, #eeeeee 0%,#ffffff 90%);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 );
|
||||||
|
background-image: linear-gradient(top, #eeeeee 0%,#ffffff 90%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container.select2-allowclear .select2-choice span {
|
||||||
|
margin-right: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-choice span {
|
||||||
|
margin-right: 26px;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
-ms-text-overflow: ellipsis;
|
||||||
|
-o-text-overflow: ellipsis;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-choice abbr {
|
||||||
|
display: none;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
position: absolute;
|
||||||
|
right: 24px;
|
||||||
|
top: 8px;
|
||||||
|
|
||||||
|
font-size: 1px;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
border: 0;
|
||||||
|
background: url('../img/select2.png') right top no-repeat;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container.select2-allowclear .select2-choice abbr {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-choice abbr:hover {
|
||||||
|
background-position: right -11px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-drop-mask {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 9998;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-drop {
|
||||||
|
width: 100%;
|
||||||
|
margin-top:-1px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 9999;
|
||||||
|
top: 100%;
|
||||||
|
|
||||||
|
background: #fff;
|
||||||
|
color: #000;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
border-top: 0;
|
||||||
|
|
||||||
|
-webkit-border-radius: 0 0 4px 4px;
|
||||||
|
-moz-border-radius: 0 0 4px 4px;
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
|
||||||
|
-webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||||
|
-moz-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||||
|
box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-drop-auto-width {
|
||||||
|
border-top: 1px solid #aaa;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-drop-auto-width .select2-search {
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-drop.select2-drop-above {
|
||||||
|
margin-top: 1px;
|
||||||
|
border-top: 1px solid #aaa;
|
||||||
|
border-bottom: 0;
|
||||||
|
|
||||||
|
-webkit-border-radius: 4px 4px 0 0;
|
||||||
|
-moz-border-radius: 4px 4px 0 0;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
|
||||||
|
-webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||||
|
-moz-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||||
|
box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-choice div {
|
||||||
|
display: inline-block;
|
||||||
|
width: 18px;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
border-left: 1px solid #aaa;
|
||||||
|
-webkit-border-radius: 0 4px 4px 0;
|
||||||
|
-moz-border-radius: 0 4px 4px 0;
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
|
|
||||||
|
-webkit-background-clip: padding-box;
|
||||||
|
-moz-background-clip: padding;
|
||||||
|
background-clip: padding-box;
|
||||||
|
|
||||||
|
background: #ccc;
|
||||||
|
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
|
||||||
|
background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
|
||||||
|
background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
|
||||||
|
background-image: -o-linear-gradient(bottom, #ccc 0%, #eee 60%);
|
||||||
|
background-image: -ms-linear-gradient(top, #cccccc 0%, #eeeeee 60%);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0);
|
||||||
|
background-image: linear-gradient(top, #cccccc 0%, #eeeeee 60%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-choice div b {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: url('../img/select2.png') no-repeat 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-search {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 26px;
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 4px;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
z-index: 10000;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-search input {
|
||||||
|
width: 100%;
|
||||||
|
height: auto !important;
|
||||||
|
min-height: 26px;
|
||||||
|
padding: 4px 20px 4px 5px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
outline: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 1em;
|
||||||
|
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
-webkit-border-radius: 0;
|
||||||
|
-moz-border-radius: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
-moz-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
background: #fff url('../img/select2.png') no-repeat 100% -22px;
|
||||||
|
background: url('../img/select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
|
||||||
|
background: url('../img/select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
|
||||||
|
background: url('../img/select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
|
||||||
|
background: url('../img/select2.png') no-repeat 100% -22px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
|
||||||
|
background: url('../img/select2.png') no-repeat 100% -22px, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
|
||||||
|
background: url('../img/select2.png') no-repeat 100% -22px, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-drop.select2-drop-above .select2-search input {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-search input.select2-active {
|
||||||
|
background: #fff url('../img/select2-spinner.gif') no-repeat 100%;
|
||||||
|
background: url('../img/select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
|
||||||
|
background: url('../img/select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
|
||||||
|
background: url('../img/select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
|
||||||
|
background: url('../img/select2-spinner.gif') no-repeat 100%, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
|
||||||
|
background: url('../img/select2-spinner.gif') no-repeat 100%, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
|
||||||
|
background: url('../img/select2-spinner.gif') no-repeat 100%, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container-active .select2-choice,
|
||||||
|
.select2-container-active .select2-choices {
|
||||||
|
border: 1px solid #5897fb;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
-webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
|
||||||
|
-moz-box-shadow: 0 0 5px rgba(0,0,0,.3);
|
||||||
|
box-shadow: 0 0 5px rgba(0,0,0,.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-dropdown-open .select2-choice {
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
-webkit-box-shadow: 0 1px 0 #fff inset;
|
||||||
|
-moz-box-shadow: 0 1px 0 #fff inset;
|
||||||
|
box-shadow: 0 1px 0 #fff inset;
|
||||||
|
|
||||||
|
-webkit-border-bottom-left-radius: 0;
|
||||||
|
-moz-border-radius-bottomleft: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
|
||||||
|
-webkit-border-bottom-right-radius: 0;
|
||||||
|
-moz-border-radius-bottomright: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
|
||||||
|
background-color: #eee;
|
||||||
|
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, white), color-stop(0.5, #eeeeee));
|
||||||
|
background-image: -webkit-linear-gradient(center bottom, white 0%, #eeeeee 50%);
|
||||||
|
background-image: -moz-linear-gradient(center bottom, white 0%, #eeeeee 50%);
|
||||||
|
background-image: -o-linear-gradient(bottom, white 0%, #eeeeee 50%);
|
||||||
|
background-image: -ms-linear-gradient(top, #ffffff 0%,#eeeeee 50%);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 );
|
||||||
|
background-image: linear-gradient(top, #ffffff 0%,#eeeeee 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-dropdown-open.select2-drop-above .select2-choice,
|
||||||
|
.select2-dropdown-open.select2-drop-above .select2-choices {
|
||||||
|
border: 1px solid #5897fb;
|
||||||
|
border-top-color: transparent;
|
||||||
|
|
||||||
|
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, white), color-stop(0.5, #eeeeee));
|
||||||
|
background-image: -webkit-linear-gradient(center top, white 0%, #eeeeee 50%);
|
||||||
|
background-image: -moz-linear-gradient(center top, white 0%, #eeeeee 50%);
|
||||||
|
background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
|
||||||
|
background-image: -ms-linear-gradient(bottom, #ffffff 0%,#eeeeee 50%);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 );
|
||||||
|
background-image: linear-gradient(bottom, #ffffff 0%,#eeeeee 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-dropdown-open .select2-choice div {
|
||||||
|
background: transparent;
|
||||||
|
border-left: none;
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
.select2-dropdown-open .select2-choice div b {
|
||||||
|
background-position: -18px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* results */
|
||||||
|
.select2-results {
|
||||||
|
max-height: 200px;
|
||||||
|
padding: 0 0 0 4px;
|
||||||
|
margin: 4px 4px 4px 0;
|
||||||
|
position: relative;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results ul.select2-result-sub {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results ul.select2-result-sub > li .select2-result-label { padding-left: 20px }
|
||||||
|
.select2-results ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 40px }
|
||||||
|
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 60px }
|
||||||
|
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 80px }
|
||||||
|
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 100px }
|
||||||
|
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 110px }
|
||||||
|
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 120px }
|
||||||
|
|
||||||
|
.select2-results li {
|
||||||
|
list-style: none;
|
||||||
|
display: list-item;
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results li.select2-result-with-children > .select2-result-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results .select2-result-label {
|
||||||
|
padding: 3px 7px 4px;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
min-height: 1em;
|
||||||
|
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results .select2-highlighted {
|
||||||
|
background: #3875d7;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results li em {
|
||||||
|
background: #feffde;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results .select2-highlighted em {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results .select2-highlighted ul {
|
||||||
|
background: white;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.select2-results .select2-no-results,
|
||||||
|
.select2-results .select2-searching,
|
||||||
|
.select2-results .select2-selection-limit {
|
||||||
|
background: #f4f4f4;
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
disabled look for disabled choices in the results dropdown
|
||||||
|
*/
|
||||||
|
.select2-results .select2-disabled.select2-highlighted {
|
||||||
|
color: #666;
|
||||||
|
background: #f4f4f4;
|
||||||
|
display: list-item;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.select2-results .select2-disabled {
|
||||||
|
background: #f4f4f4;
|
||||||
|
display: list-item;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results .select2-selected {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-more-results.select2-active {
|
||||||
|
background: #f4f4f4 url('../img/select2-spinner.gif') no-repeat 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-more-results {
|
||||||
|
background: #f4f4f4;
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* disabled styles */
|
||||||
|
|
||||||
|
.select2-container.select2-container-disabled .select2-choice {
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
background-image: none;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container.select2-container-disabled .select2-choice div {
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
background-image: none;
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container.select2-container-disabled .select2-choice abbr {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* multiselect */
|
||||||
|
|
||||||
|
.select2-container-multi .select2-choices {
|
||||||
|
height: auto !important;
|
||||||
|
height: 1%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
cursor: text;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
background-color: #fff;
|
||||||
|
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
|
||||||
|
background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
|
||||||
|
background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
|
||||||
|
background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
|
||||||
|
background-image: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
|
||||||
|
background-image: linear-gradient(top, #eeeeee 1%, #ffffff 15%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-locked {
|
||||||
|
padding: 3px 5px 3px 5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container-multi .select2-choices {
|
||||||
|
min-height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container-multi.select2-container-active .select2-choices {
|
||||||
|
border: 1px solid #5897fb;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
-webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
|
||||||
|
-moz-box-shadow: 0 0 5px rgba(0,0,0,.3);
|
||||||
|
box-shadow: 0 0 5px rgba(0,0,0,.3);
|
||||||
|
}
|
||||||
|
.select2-container-multi .select2-choices li {
|
||||||
|
float: left;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.select2-container-multi .select2-choices .select2-search-field {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container-multi .select2-choices .select2-search-field input {
|
||||||
|
padding: 5px;
|
||||||
|
margin: 1px 0;
|
||||||
|
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 100%;
|
||||||
|
color: #666;
|
||||||
|
outline: 0;
|
||||||
|
border: 0;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
-moz-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container-multi .select2-choices .select2-search-field input.select2-active {
|
||||||
|
background: #fff url('../img/select2-spinner.gif') no-repeat 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-default {
|
||||||
|
color: #999 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container-multi .select2-choices .select2-search-choice {
|
||||||
|
padding: 3px 5px 3px 18px;
|
||||||
|
margin: 3px 0 3px 5px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
line-height: 13px;
|
||||||
|
color: #333;
|
||||||
|
cursor: default;
|
||||||
|
border: 1px solid #aaaaaa;
|
||||||
|
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
-webkit-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
|
||||||
|
-moz-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
|
||||||
|
box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
|
||||||
|
|
||||||
|
-webkit-background-clip: padding-box;
|
||||||
|
-moz-background-clip: padding;
|
||||||
|
background-clip: padding-box;
|
||||||
|
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
background-color: #e4e4e4;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0 );
|
||||||
|
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
|
||||||
|
background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
|
||||||
|
background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
|
||||||
|
background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
|
||||||
|
background-image: -ms-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
|
||||||
|
background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
|
||||||
|
}
|
||||||
|
.select2-container-multi .select2-choices .select2-search-choice span {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.select2-container-multi .select2-choices .select2-search-choice-focus {
|
||||||
|
background: #d4d4d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-search-choice-close {
|
||||||
|
display: block;
|
||||||
|
width: 12px;
|
||||||
|
height: 13px;
|
||||||
|
position: absolute;
|
||||||
|
right: 3px;
|
||||||
|
top: 4px;
|
||||||
|
|
||||||
|
font-size: 1px;
|
||||||
|
outline: none;
|
||||||
|
background: url('../img/select2.png') right top no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container-multi .select2-search-choice-close {
|
||||||
|
left: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
|
||||||
|
background-position: right -11px;
|
||||||
|
}
|
||||||
|
.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
|
||||||
|
background-position: right -11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* disabled styles */
|
||||||
|
.select2-container-multi.select2-container-disabled .select2-choices{
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
background-image: none;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
|
||||||
|
padding: 3px 5px 3px 5px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
background-image: none;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none;
|
||||||
|
background:none;
|
||||||
|
}
|
||||||
|
/* end multiselect */
|
||||||
|
|
||||||
|
|
||||||
|
.select2-result-selectable .select2-match,
|
||||||
|
.select2-result-unselectable .select2-match {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-offscreen, .select2-offscreen:focus {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
outline: 0;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-display-none {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-measure-scrollbar {
|
||||||
|
position: absolute;
|
||||||
|
top: -10000px;
|
||||||
|
left: -10000px;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
/* Retina-ize icons */
|
||||||
|
|
||||||
|
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi) {
|
||||||
|
.select2-search input, .select2-search-choice-close, .select2-container .select2-choice abbr, .select2-container .select2-choice div b {
|
||||||
|
background-image: url('../img/select2x2.png') !important;
|
||||||
|
background-repeat: no-repeat !important;
|
||||||
|
background-size: 60px 40px !important;
|
||||||
|
}
|
||||||
|
.select2-search input {
|
||||||
|
background-position: 100% -21px !important;
|
||||||
|
}
|
||||||
|
}
|
BIN
resources/public/img/glyphicons-halflings-white.png
Normal file
BIN
resources/public/img/glyphicons-halflings-white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
BIN
resources/public/img/glyphicons-halflings.png
Normal file
BIN
resources/public/img/glyphicons-halflings.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
resources/public/img/select2-spinner.gif
Normal file
BIN
resources/public/img/select2-spinner.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
resources/public/img/select2.png
Normal file
BIN
resources/public/img/select2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 613 B |
BIN
resources/public/img/select2x2.png
Normal file
BIN
resources/public/img/select2x2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 845 B |
986
resources/public/js/bootstrap-markdown.js
vendored
Normal file
986
resources/public/js/bootstrap-markdown.js
vendored
Normal file
|
@ -0,0 +1,986 @@
|
||||||
|
/* ===================================================
|
||||||
|
* bootstrap-markdown.js v1.0.0
|
||||||
|
* http://github.com/toopay/bootstrap-markdown
|
||||||
|
* ===================================================
|
||||||
|
* Copyright 2013 Taufan Aditya
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* ========================================================== */
|
||||||
|
|
||||||
|
!function ($) {
|
||||||
|
|
||||||
|
"use strict"; // jshint ;_;
|
||||||
|
|
||||||
|
|
||||||
|
/* MARKDOWN CLASS DEFINITION
|
||||||
|
* ========================== */
|
||||||
|
|
||||||
|
var Markdown = function (element, options) {
|
||||||
|
// Class Properties
|
||||||
|
this.$ns = 'bootstrap-markdown'
|
||||||
|
this.$element = $(element)
|
||||||
|
this.$editable = {el:null, type:null,attrKeys:[], attrValues:[], content:null}
|
||||||
|
this.$cloneEditor = {el:null, type:null,attrKeys:[], attrValues:[], content:null}
|
||||||
|
this.$options = $.extend(true, {}, $.fn.markdown.defaults, options)
|
||||||
|
this.$oldContent = null
|
||||||
|
this.$isPreview = false
|
||||||
|
this.$editor = null
|
||||||
|
this.$textarea = null
|
||||||
|
this.$handler = []
|
||||||
|
this.$callback = []
|
||||||
|
this.$nextTab = []
|
||||||
|
|
||||||
|
this.showEditor()
|
||||||
|
}
|
||||||
|
|
||||||
|
Markdown.prototype = {
|
||||||
|
|
||||||
|
constructor: Markdown
|
||||||
|
|
||||||
|
, __alterButtons: function(name,alter) {
|
||||||
|
var handler = this.$handler, isAll = (name == 'all'),that = this
|
||||||
|
|
||||||
|
$.each(handler,function(k,v) {
|
||||||
|
var halt = true
|
||||||
|
if (isAll) {
|
||||||
|
halt = false
|
||||||
|
} else {
|
||||||
|
halt = v.indexOf(name) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (halt == false) {
|
||||||
|
alter(that.$editor.find('button[data-handler="'+v+'"]'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
, __buildButtons: function(buttonsArray, container) {
|
||||||
|
var i,
|
||||||
|
ns = this.$ns,
|
||||||
|
handler = this.$handler,
|
||||||
|
callback = this.$callback
|
||||||
|
|
||||||
|
for (i=0;i<buttonsArray.length;i++) {
|
||||||
|
// Build each group container
|
||||||
|
var y, btnGroups = buttonsArray[i]
|
||||||
|
for (y=0;y<btnGroups.length;y++) {
|
||||||
|
// Build each button group
|
||||||
|
var z,
|
||||||
|
buttons = btnGroups[y].data,
|
||||||
|
btnGroupContainer = $('<div/>', {
|
||||||
|
'class': 'btn-group'
|
||||||
|
})
|
||||||
|
|
||||||
|
for (z=0;z<buttons.length;z++) {
|
||||||
|
var button = buttons[z],
|
||||||
|
buttonHandler = ns+'-'+button.name,
|
||||||
|
btnText = button.btnText ? button.btnText : '',
|
||||||
|
btnClass = button.btnClass ? button.btnClass : 'btn'
|
||||||
|
|
||||||
|
// Attach the button object
|
||||||
|
btnGroupContainer.append('<button class="'
|
||||||
|
+btnClass
|
||||||
|
+' btn-small" title="'
|
||||||
|
+button.title
|
||||||
|
+'" data-provider="'
|
||||||
|
+ns
|
||||||
|
+'" data-handler="'
|
||||||
|
+buttonHandler
|
||||||
|
+'"><i class="'
|
||||||
|
+button.icon
|
||||||
|
+'"></i> '
|
||||||
|
+btnText
|
||||||
|
+'</button>')
|
||||||
|
|
||||||
|
// Register handler and callback
|
||||||
|
handler.push(buttonHandler)
|
||||||
|
callback.push(button.callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach the button group into container dom
|
||||||
|
container.append(btnGroupContainer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return container
|
||||||
|
}
|
||||||
|
, __setListener: function() {
|
||||||
|
// Set size and resizable Properties
|
||||||
|
var hasRows = typeof this.$textarea.attr('rows') != 'undefined',
|
||||||
|
maxRows = this.$textarea.val().split("\n").length > 5 ? this.$textarea.val().split("\n").length : '5',
|
||||||
|
rowsVal = hasRows ? this.$textarea.attr('rows') : maxRows
|
||||||
|
|
||||||
|
this.$textarea.attr('rows',rowsVal)
|
||||||
|
this.$textarea.css('resize','none')
|
||||||
|
|
||||||
|
this.$textarea
|
||||||
|
.on('focus', $.proxy(this.focus, this))
|
||||||
|
.on('keypress', $.proxy(this.keypress, this))
|
||||||
|
.on('keyup', $.proxy(this.keyup, this))
|
||||||
|
|
||||||
|
if (this.eventSupported('keydown')) {
|
||||||
|
this.$textarea.on('keydown', $.proxy(this.keydown, this))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-attach markdown data
|
||||||
|
this.$textarea.data('markdown',this)
|
||||||
|
}
|
||||||
|
|
||||||
|
, __handle: function(e) {
|
||||||
|
var target = $(e.currentTarget),
|
||||||
|
handler = this.$handler,
|
||||||
|
callback = this.$callback,
|
||||||
|
handlerName = target.attr('data-handler'),
|
||||||
|
callbackIndex = handler.indexOf(handlerName),
|
||||||
|
callbackHandler = callback[callbackIndex]
|
||||||
|
|
||||||
|
// Trigger the focusin
|
||||||
|
$(e.currentTarget).focus()
|
||||||
|
|
||||||
|
callbackHandler(this)
|
||||||
|
|
||||||
|
// Unless it was the save handler,
|
||||||
|
// focusin the textarea
|
||||||
|
if (handlerName.indexOf('cmdSave') < 0) {
|
||||||
|
this.$textarea.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
, showEditor: function() {
|
||||||
|
var instance = this,
|
||||||
|
textarea,
|
||||||
|
ns = this.$ns,
|
||||||
|
container = this.$element,
|
||||||
|
originalHeigth = container.css('height'),
|
||||||
|
originalWidth = container.css('width'),
|
||||||
|
editable = this.$editable,
|
||||||
|
handler = this.$handler,
|
||||||
|
callback = this.$callback,
|
||||||
|
options = this.$options,
|
||||||
|
editor = $( '<div/>', {
|
||||||
|
'class': 'md-editor',
|
||||||
|
click: function() {
|
||||||
|
instance.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Prepare the editor
|
||||||
|
if (this.$editor == null) {
|
||||||
|
// Create the panel
|
||||||
|
var editorHeader = $('<div/>', {
|
||||||
|
'class': 'md-header'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Build the main buttons
|
||||||
|
if (options.buttons.length > 0) {
|
||||||
|
editorHeader = this.__buildButtons(options.buttons, editorHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the additional buttons
|
||||||
|
if (options.additionalButtons.length > 0) {
|
||||||
|
editorHeader = this.__buildButtons(options.additionalButtons, editorHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.append(editorHeader)
|
||||||
|
|
||||||
|
// Wrap the textarea
|
||||||
|
if (container.is('textarea')) {
|
||||||
|
container.before(editor)
|
||||||
|
textarea = container
|
||||||
|
textarea.addClass('md-input')
|
||||||
|
editor.append(textarea)
|
||||||
|
} else {
|
||||||
|
var rawContent = (typeof toMarkdown == 'function') ? toMarkdown(container.html()) : container.html(),
|
||||||
|
currentContent = $.trim(rawContent)
|
||||||
|
|
||||||
|
// This is some arbitrary content that could be edited
|
||||||
|
textarea = $('<textarea/>', {
|
||||||
|
'class': 'md-input',
|
||||||
|
'val' : currentContent
|
||||||
|
})
|
||||||
|
|
||||||
|
editor.append(textarea)
|
||||||
|
|
||||||
|
// Save the editable
|
||||||
|
editable.el = container
|
||||||
|
editable.type = container.prop('tagName').toLowerCase()
|
||||||
|
editable.content = container.html()
|
||||||
|
|
||||||
|
$(container[0].attributes).each(function(){
|
||||||
|
editable.attrKeys.push(this.nodeName)
|
||||||
|
editable.attrValues.push(this.nodeValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set editor to blocked the original container
|
||||||
|
container.replaceWith(editor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the footer if savable
|
||||||
|
if (options.savable) {
|
||||||
|
var editorFooter = $('<div/>', {
|
||||||
|
'class': 'md-footer'
|
||||||
|
}),
|
||||||
|
saveHandler = 'cmdSave'
|
||||||
|
|
||||||
|
// Register handler and callback
|
||||||
|
handler.push(saveHandler)
|
||||||
|
callback.push(options.onSave)
|
||||||
|
|
||||||
|
editorFooter.append('<button class="btn btn-success" data-provider="'
|
||||||
|
+ns
|
||||||
|
+'" data-handler="'
|
||||||
|
+saveHandler
|
||||||
|
+'"><i class="icon icon-white icon-ok"></i> Save</button>')
|
||||||
|
|
||||||
|
editor.append(editorFooter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set width/height
|
||||||
|
$.each(['height','width'],function(k,attr){
|
||||||
|
if (options[attr] != 'inherit') {
|
||||||
|
if (jQuery.isNumeric(options[attr])) {
|
||||||
|
editor.css(attr,options[attr]+'px')
|
||||||
|
} else {
|
||||||
|
editor.addClass(options[attr])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Reference
|
||||||
|
this.$editor = editor
|
||||||
|
this.$textarea = textarea
|
||||||
|
this.$editable = editable
|
||||||
|
this.$oldContent = this.getContent()
|
||||||
|
|
||||||
|
this.__setListener()
|
||||||
|
|
||||||
|
// Set editor attributes, data short-hand API and listener
|
||||||
|
this.$editor.attr('id',(new Date).getTime())
|
||||||
|
this.$editor.on('click', '[data-provider="bootstrap-markdown"]', $.proxy(this.__handle, this))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.$editor.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.autofocus) {
|
||||||
|
this.$textarea.focus()
|
||||||
|
this.$editor.addClass('active')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger the onShow hook
|
||||||
|
options.onShow(this)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
, showPreview: function() {
|
||||||
|
var options = this.$options,
|
||||||
|
callbackContent = options.onPreview(this), // Try to get the content from callback
|
||||||
|
container = this.$textarea,
|
||||||
|
replacementContainer = $('<div/>',{'class':'md-preview','data-provider':'markdown-preview'}),
|
||||||
|
cloneEditor = this.$cloneEditor,
|
||||||
|
content
|
||||||
|
|
||||||
|
// Give flag that tell the editor enter preview mode
|
||||||
|
this.$isPreview = true
|
||||||
|
// Disable all buttons
|
||||||
|
this.disableButtons('all').enableButtons('cmdPreview')
|
||||||
|
|
||||||
|
// Save the editor
|
||||||
|
cloneEditor.el = container
|
||||||
|
cloneEditor.type = container.prop('tagName').toLowerCase()
|
||||||
|
cloneEditor.content = container.val()
|
||||||
|
|
||||||
|
$(container[0].attributes).each(function(){
|
||||||
|
cloneEditor.attrKeys.push(this.nodeName)
|
||||||
|
cloneEditor.attrValues.push(this.nodeValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$cloneEditor = cloneEditor
|
||||||
|
|
||||||
|
if (typeof callbackContent == 'string') {
|
||||||
|
// Set the content based by callback content
|
||||||
|
content = callbackContent
|
||||||
|
} else {
|
||||||
|
// Set the content
|
||||||
|
content = (typeof markdown == 'object') ? markdown.toHTML(container.val()) : container.val()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build preview element and replace the editor temporarily
|
||||||
|
replacementContainer.html(content)
|
||||||
|
container.replaceWith(replacementContainer)
|
||||||
|
|
||||||
|
// Attach the editor instances
|
||||||
|
replacementContainer.data('markdown',this)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
, hidePreview: function() {
|
||||||
|
// Give flag that tell the editor quit preview mode
|
||||||
|
this.$isPreview = false
|
||||||
|
|
||||||
|
// Build the original element
|
||||||
|
var container = this.$editor.find('div[data-provider="markdown-preview"]'),
|
||||||
|
cloneEditor = this.$cloneEditor,
|
||||||
|
oldElement = $('<'+cloneEditor.type+'/>')
|
||||||
|
|
||||||
|
$(cloneEditor.attrKeys).each(function(k,v) {
|
||||||
|
oldElement.attr(cloneEditor.attrKeys[k],cloneEditor.attrValues[k])
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set the editor content
|
||||||
|
oldElement.val(cloneEditor.content)
|
||||||
|
|
||||||
|
// Set the editor data
|
||||||
|
container.replaceWith(oldElement)
|
||||||
|
|
||||||
|
// Enable all buttons
|
||||||
|
this.enableButtons('all')
|
||||||
|
|
||||||
|
// Back to the editor
|
||||||
|
this.$textarea = oldElement
|
||||||
|
this.__setListener()
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
, isDirty: function() {
|
||||||
|
return this.$oldContent != this.getContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
, getContent: function() {
|
||||||
|
return (this.$isPreview) ? this.$cloneEditor.content : this.$textarea.val()
|
||||||
|
}
|
||||||
|
|
||||||
|
, setContent: function(content) {
|
||||||
|
this.$textarea.val(content)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
, findSelection: function(chunk) {
|
||||||
|
var content = this.getContent(), startChunkPosition
|
||||||
|
|
||||||
|
if (startChunkPosition = content.indexOf(chunk), startChunkPosition >= 0 && chunk.length > 0) {
|
||||||
|
var oldSelection = this.getSelection(), selection
|
||||||
|
|
||||||
|
this.setSelection(startChunkPosition,startChunkPosition+chunk.length)
|
||||||
|
selection = this.getSelection()
|
||||||
|
|
||||||
|
this.setSelection(oldSelection.start,oldSelection.end)
|
||||||
|
|
||||||
|
return selection
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, getSelection: function() {
|
||||||
|
|
||||||
|
var e = this.$textarea[0]
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
('selectionStart' in e && function() {
|
||||||
|
var l = e.selectionEnd - e.selectionStart
|
||||||
|
return { start: e.selectionStart, end: e.selectionEnd, length: l, text: e.value.substr(e.selectionStart, l) }
|
||||||
|
}) ||
|
||||||
|
|
||||||
|
/* browser not supported */
|
||||||
|
function() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
)()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
, setSelection: function(start,end) {
|
||||||
|
|
||||||
|
var e = this.$textarea[0]
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
('selectionStart' in e && function() {
|
||||||
|
e.selectionStart = start
|
||||||
|
e.selectionEnd = end
|
||||||
|
return
|
||||||
|
}) ||
|
||||||
|
|
||||||
|
/* browser not supported */
|
||||||
|
function() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
)()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
, replaceSelection: function(text) {
|
||||||
|
|
||||||
|
var e = this.$textarea[0]
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
('selectionStart' in e && function() {
|
||||||
|
e.value = e.value.substr(0, e.selectionStart) + text + e.value.substr(e.selectionEnd, e.value.length)
|
||||||
|
// Set cursor to the last replacement end
|
||||||
|
e.selectionStart = e.value.length
|
||||||
|
return this
|
||||||
|
}) ||
|
||||||
|
|
||||||
|
/* browser not supported */
|
||||||
|
function() {
|
||||||
|
e.value += text
|
||||||
|
return jQuery(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
)()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
, getNextTab: function() {
|
||||||
|
// Shift the nextTab
|
||||||
|
if (this.$nextTab.length == 0) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
var nextTab, tab = this.$nextTab.shift()
|
||||||
|
|
||||||
|
if (typeof tab == 'function') {
|
||||||
|
nextTab = tab()
|
||||||
|
} else if (typeof tab == 'object' && tab.length > 0) {
|
||||||
|
nextTab = tab
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextTab
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, setNextTab: function(start,end) {
|
||||||
|
// Push new selection into nextTab collections
|
||||||
|
if (typeof start == 'string') {
|
||||||
|
var that = this
|
||||||
|
this.$nextTab.push(function(){
|
||||||
|
return that.findSelection(start)
|
||||||
|
})
|
||||||
|
} else if (typeof start == 'numeric' && typeof end == 'numeric') {
|
||||||
|
var oldSelection = this.getSelection()
|
||||||
|
|
||||||
|
this.setSelection(start,end)
|
||||||
|
this.$nextTab.push(this.getSelection())
|
||||||
|
|
||||||
|
this.setSelection(oldSelection.start,oldSelection.end)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
, enableButtons: function(name) {
|
||||||
|
var alter = function (el) {
|
||||||
|
el.removeAttr('disabled')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__alterButtons(name,alter)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
, disableButtons: function(name) {
|
||||||
|
var alter = function (el) {
|
||||||
|
el.attr('disabled','disabled')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__alterButtons(name,alter)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
, eventSupported: function(eventName) {
|
||||||
|
var isSupported = eventName in this.$element
|
||||||
|
if (!isSupported) {
|
||||||
|
this.$element.setAttribute(eventName, 'return;')
|
||||||
|
isSupported = typeof this.$element[eventName] === 'function'
|
||||||
|
}
|
||||||
|
return isSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
, keydown: function (e) {
|
||||||
|
this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
|
||||||
|
this.keyup(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
, keypress: function (e) {
|
||||||
|
if (this.suppressKeyPressRepeat) return
|
||||||
|
this.keyup(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
, keyup: function (e) {
|
||||||
|
var blocked = false
|
||||||
|
switch(e.keyCode) {
|
||||||
|
case 40: // down arrow
|
||||||
|
case 38: // up arrow
|
||||||
|
case 16: // shift
|
||||||
|
case 17: // ctrl
|
||||||
|
case 18: // alt
|
||||||
|
break
|
||||||
|
|
||||||
|
case 9: // tab
|
||||||
|
var nextTab
|
||||||
|
if (nextTab = this.getNextTab(),nextTab != null) {
|
||||||
|
// Get the nextTab if exists
|
||||||
|
var that = this
|
||||||
|
setTimeout(function(){
|
||||||
|
that.setSelection(nextTab.start,nextTab.end)
|
||||||
|
},500)
|
||||||
|
} else {
|
||||||
|
// Put the cursor to the end
|
||||||
|
this.setSelection(this.getContent().length,this.getContent().length)
|
||||||
|
}
|
||||||
|
|
||||||
|
blocked = true
|
||||||
|
break
|
||||||
|
|
||||||
|
case 13: // enter
|
||||||
|
case 27: // escape
|
||||||
|
blocked = false
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
blocked = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocked) {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, focus: function (e) {
|
||||||
|
var options = this.$options,
|
||||||
|
isHideable = options.hideable,
|
||||||
|
editor = this.$editor
|
||||||
|
|
||||||
|
editor.addClass('active')
|
||||||
|
|
||||||
|
// Blur other markdown(s)
|
||||||
|
$(document).find('.md-editor').each(function(){
|
||||||
|
if ($(this).attr('id') != editor.attr('id')) {
|
||||||
|
var attachedMarkdown
|
||||||
|
|
||||||
|
if (attachedMarkdown = $(this).find('textarea').data('markdown'),
|
||||||
|
attachedMarkdown == null) {
|
||||||
|
attachedMarkdown = $(this).find('div[data-provider="markdown-preview"]').data('markdown')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachedMarkdown) {
|
||||||
|
attachedMarkdown.blur()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
, blur: function (e) {
|
||||||
|
var options = this.$options,
|
||||||
|
isHideable = options.hideable,
|
||||||
|
editor = this.$editor,
|
||||||
|
editable = this.$editable
|
||||||
|
|
||||||
|
// Force to quit preview mode
|
||||||
|
if (this.$isPreview) {
|
||||||
|
this.hidePreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editor.hasClass('active') || this.$element.parent().length == 0) {
|
||||||
|
editor.removeClass('active')
|
||||||
|
|
||||||
|
if (isHideable) {
|
||||||
|
|
||||||
|
// Check for editable elements
|
||||||
|
if (editable.el != null) {
|
||||||
|
// Build the original element
|
||||||
|
var oldElement = $('<'+editable.type+'/>'),
|
||||||
|
content = this.getContent(),
|
||||||
|
currentContent = (typeof markdown == 'object') ? markdown.toHTML(content) : content
|
||||||
|
|
||||||
|
$(editable.attrKeys).each(function(k,v) {
|
||||||
|
oldElement.attr(editable.attrKeys[k],editable.attrValues[k])
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get the editor content
|
||||||
|
oldElement.html(currentContent)
|
||||||
|
|
||||||
|
editor.replaceWith(oldElement)
|
||||||
|
} else {
|
||||||
|
editor.hide()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger the onBlur hook
|
||||||
|
options.onBlur(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MARKDOWN PLUGIN DEFINITION
|
||||||
|
* ========================== */
|
||||||
|
|
||||||
|
var old = $.fn.markdown
|
||||||
|
|
||||||
|
$.fn.markdown = function (option) {
|
||||||
|
return this.each(function () {
|
||||||
|
var $this = $(this)
|
||||||
|
, data = $this.data('markdown')
|
||||||
|
, options = typeof option == 'object' && option
|
||||||
|
if (!data) $this.data('markdown', (data = new Markdown(this, options)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn.markdown.defaults = {
|
||||||
|
/* Editor Properties */
|
||||||
|
autofocus: false,
|
||||||
|
hideable: false,
|
||||||
|
savable:false,
|
||||||
|
width: 'inherit',
|
||||||
|
height: 'inherit',
|
||||||
|
|
||||||
|
/* Buttons Properties */
|
||||||
|
buttons: [
|
||||||
|
[{
|
||||||
|
name: 'groupFont',
|
||||||
|
data: [{
|
||||||
|
name: 'cmdBold',
|
||||||
|
title: 'Bold',
|
||||||
|
icon: 'icon icon-bold',
|
||||||
|
callback: function(e){
|
||||||
|
// Give/remove ** surround the selection
|
||||||
|
var chunk, cursor, selected = e.getSelection(), content = e.getContent()
|
||||||
|
|
||||||
|
if (selected.length == 0) {
|
||||||
|
// Give extra word
|
||||||
|
chunk = 'strong text'
|
||||||
|
} else {
|
||||||
|
chunk = selected.text
|
||||||
|
}
|
||||||
|
|
||||||
|
// transform selection and set the cursor into chunked text
|
||||||
|
if (content.substr(selected.start-2,2) == '**'
|
||||||
|
&& content.substr(selected.end,2) == '**' ) {
|
||||||
|
e.setSelection(selected.start-2,selected.end+2)
|
||||||
|
e.replaceSelection(chunk)
|
||||||
|
cursor = selected.start-2
|
||||||
|
} else {
|
||||||
|
e.replaceSelection('**'+chunk+'**')
|
||||||
|
cursor = selected.start+2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the cursor
|
||||||
|
e.setSelection(cursor,cursor+chunk.length)
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
name: 'cmdItalic',
|
||||||
|
title: 'Italic',
|
||||||
|
icon: 'icon icon-italic',
|
||||||
|
callback: function(e){
|
||||||
|
// Give/remove * surround the selection
|
||||||
|
var chunk, cursor, selected = e.getSelection(), content = e.getContent()
|
||||||
|
|
||||||
|
if (selected.length == 0) {
|
||||||
|
// Give extra word
|
||||||
|
chunk = 'emphasized text'
|
||||||
|
} else {
|
||||||
|
chunk = selected.text
|
||||||
|
}
|
||||||
|
|
||||||
|
// transform selection and set the cursor into chunked text
|
||||||
|
if (content.substr(selected.start-1,1) == '*'
|
||||||
|
&& content.substr(selected.end,1) == '*' ) {
|
||||||
|
e.setSelection(selected.start-1,selected.end+1)
|
||||||
|
e.replaceSelection(chunk)
|
||||||
|
cursor = selected.start-1
|
||||||
|
} else {
|
||||||
|
e.replaceSelection('*'+chunk+'*')
|
||||||
|
cursor = selected.start+1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the cursor
|
||||||
|
e.setSelection(cursor,cursor+chunk.length)
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
name: 'cmdHeading',
|
||||||
|
title: 'Heading',
|
||||||
|
icon: 'icon icon-font',
|
||||||
|
callback: function(e){
|
||||||
|
// Append/remove ### surround the selection
|
||||||
|
var chunk, cursor, selected = e.getSelection(), content = e.getContent(), pointer, prevChar
|
||||||
|
|
||||||
|
if (selected.length == 0) {
|
||||||
|
// Give extra word
|
||||||
|
chunk = 'heading text'
|
||||||
|
} else {
|
||||||
|
chunk = selected.text
|
||||||
|
}
|
||||||
|
|
||||||
|
// transform selection and set the cursor into chunked text
|
||||||
|
if ((pointer = 4, content.substr(selected.start-pointer,pointer) == '### ')
|
||||||
|
|| (pointer = 3, content.substr(selected.start-pointer,pointer) == '###')) {
|
||||||
|
e.setSelection(selected.start-pointer,selected.end)
|
||||||
|
e.replaceSelection(chunk)
|
||||||
|
cursor = selected.start-pointer
|
||||||
|
} else if (prevChar = content.substr(selected.start-1,1), !!prevChar && prevChar != '\n') {
|
||||||
|
e.replaceSelection('\n\n### '+chunk+'\n')
|
||||||
|
cursor = selected.start+6
|
||||||
|
} else {
|
||||||
|
// Empty string before element
|
||||||
|
e.replaceSelection('### '+chunk+'\n')
|
||||||
|
cursor = selected.start+4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the cursor
|
||||||
|
e.setSelection(cursor,cursor+chunk.length)
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},{
|
||||||
|
name: 'groupLink',
|
||||||
|
data: [{
|
||||||
|
name: 'cmdUrl',
|
||||||
|
title: 'URL/Link',
|
||||||
|
icon: 'icon icon-globe',
|
||||||
|
callback: function(e){
|
||||||
|
// Give [] surround the selection and prepend the link
|
||||||
|
var chunk, cursor, selected = e.getSelection(), content = e.getContent(), link
|
||||||
|
|
||||||
|
if (selected.length == 0) {
|
||||||
|
// Give extra word
|
||||||
|
chunk = 'enter link description here'
|
||||||
|
} else {
|
||||||
|
chunk = selected.text
|
||||||
|
}
|
||||||
|
|
||||||
|
link = prompt('Insert Hyperlink','http://')
|
||||||
|
|
||||||
|
if (link != null) {
|
||||||
|
// transform selection and set the cursor into chunked text
|
||||||
|
e.replaceSelection('['+chunk+']('+link+')')
|
||||||
|
cursor = selected.start+1
|
||||||
|
|
||||||
|
// Set the cursor
|
||||||
|
e.setSelection(cursor,cursor+chunk.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
name: 'cmdImage',
|
||||||
|
title: 'Image',
|
||||||
|
icon: 'icon icon-picture',
|
||||||
|
callback: function(e){
|
||||||
|
// Give ![] surround the selection and prepend the image link
|
||||||
|
var chunk, cursor, selected = e.getSelection(), content = e.getContent(), link
|
||||||
|
|
||||||
|
if (selected.length == 0) {
|
||||||
|
// Give extra word
|
||||||
|
chunk = 'enter image description here'
|
||||||
|
} else {
|
||||||
|
chunk = selected.text
|
||||||
|
}
|
||||||
|
|
||||||
|
link = prompt('Insert Image Hyperlink','http://')
|
||||||
|
|
||||||
|
if (link != null) {
|
||||||
|
// transform selection and set the cursor into chunked text
|
||||||
|
e.replaceSelection('!['+chunk+']('+link+' "enter image title here")')
|
||||||
|
cursor = selected.start+2
|
||||||
|
|
||||||
|
// Set the next tab
|
||||||
|
e.setNextTab('enter image title here')
|
||||||
|
|
||||||
|
// Set the cursor
|
||||||
|
e.setSelection(cursor,cursor+chunk.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},{
|
||||||
|
name: 'groupMisc',
|
||||||
|
data: [{
|
||||||
|
name: 'cmdList',
|
||||||
|
title: 'List',
|
||||||
|
icon: 'icon icon-list',
|
||||||
|
callback: function(e){
|
||||||
|
// Prepend/Give - surround the selection
|
||||||
|
var chunk, cursor, selected = e.getSelection(), content = e.getContent()
|
||||||
|
|
||||||
|
// transform selection and set the cursor into chunked text
|
||||||
|
if (selected.length == 0) {
|
||||||
|
// Give extra word
|
||||||
|
chunk = 'list text here'
|
||||||
|
|
||||||
|
e.replaceSelection('- '+chunk)
|
||||||
|
|
||||||
|
// Set the cursor
|
||||||
|
cursor = selected.start+2
|
||||||
|
} else {
|
||||||
|
if (selected.text.indexOf('\n') < 0) {
|
||||||
|
chunk = selected.text
|
||||||
|
|
||||||
|
e.replaceSelection('- '+chunk)
|
||||||
|
|
||||||
|
// Set the cursor
|
||||||
|
cursor = selected.start+2
|
||||||
|
} else {
|
||||||
|
var list = []
|
||||||
|
|
||||||
|
list = selected.text.split('\n')
|
||||||
|
chunk = list[0]
|
||||||
|
|
||||||
|
$.each(list,function(k,v) {
|
||||||
|
list[k] = '- '+v
|
||||||
|
})
|
||||||
|
|
||||||
|
e.replaceSelection('\n\n'+list.join('\n'))
|
||||||
|
|
||||||
|
// Set the cursor
|
||||||
|
cursor = selected.start+4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Set the cursor
|
||||||
|
e.setSelection(cursor,cursor+chunk.length)
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},{
|
||||||
|
name: 'groupUtil',
|
||||||
|
data: [{
|
||||||
|
name: 'cmdPreview',
|
||||||
|
title: 'Preview',
|
||||||
|
btnText: 'Preview',
|
||||||
|
btnClass: 'btn btn-inverse',
|
||||||
|
icon: 'icon icon-white icon-search',
|
||||||
|
callback: function(e){
|
||||||
|
// Check the preview mode and toggle based on this flag
|
||||||
|
var isPreview = e.$isPreview,content
|
||||||
|
|
||||||
|
if (isPreview == false) {
|
||||||
|
// Give flag that tell the editor enter preview mode
|
||||||
|
e.showPreview()
|
||||||
|
} else {
|
||||||
|
e.hidePreview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
additionalButtons:[], // Place to hook more buttons by code
|
||||||
|
|
||||||
|
/* Events hook */
|
||||||
|
onShow: function (e) {},
|
||||||
|
onPreview: function (e) {},
|
||||||
|
onSave: function (e) {},
|
||||||
|
onBlur: function (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn.markdown.Constructor = Markdown
|
||||||
|
|
||||||
|
|
||||||
|
/* MARKDOWN NO CONFLICT
|
||||||
|
* ==================== */
|
||||||
|
|
||||||
|
$.fn.markdown.noConflict = function () {
|
||||||
|
$.fn.markdown = old
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MARKDOWN GLOBAL FUNCTION & DATA-API
|
||||||
|
* ==================================== */
|
||||||
|
var initMarkdown = function(el) {
|
||||||
|
var $this = el
|
||||||
|
|
||||||
|
if ($this.data('markdown')) {
|
||||||
|
$this.data('markdown').showEditor()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$this.markdown($this.data())
|
||||||
|
}
|
||||||
|
|
||||||
|
var analyzeMarkdown = function(e) {
|
||||||
|
var blurred = false,
|
||||||
|
el,
|
||||||
|
$docEditor = $(e.currentTarget)
|
||||||
|
|
||||||
|
// Check whether it was editor childs or not
|
||||||
|
if ((e.type == 'focusin' || e.type == 'click') && $docEditor.length == 1 && typeof $docEditor[0] == 'object'){
|
||||||
|
el = $docEditor[0].activeElement
|
||||||
|
if ( ! $(el).data('markdown')) {
|
||||||
|
if (typeof $(el).parent().parent().parent().attr('class') == "undefined"
|
||||||
|
|| $(el).parent().parent().parent().attr('class').indexOf('md-editor') < 0) {
|
||||||
|
if ( typeof $(el).parent().parent().attr('class') == "undefined"
|
||||||
|
|| $(el).parent().parent().attr('class').indexOf('md-editor') < 0) {
|
||||||
|
|
||||||
|
blurred = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
blurred = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (blurred) {
|
||||||
|
// Blur event
|
||||||
|
$(document).find('.md-editor').each(function(){
|
||||||
|
var parentMd = $(el).parent()
|
||||||
|
|
||||||
|
if ($(this).attr('id') != parentMd.attr('id')) {
|
||||||
|
var attachedMarkdown
|
||||||
|
|
||||||
|
if (attachedMarkdown = $(this).find('textarea').data('markdown'),
|
||||||
|
attachedMarkdown == null) {
|
||||||
|
attachedMarkdown = $(this).find('div[data-provider="markdown-preview"]').data('markdown')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachedMarkdown) {
|
||||||
|
attachedMarkdown.blur()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document)
|
||||||
|
.on('click.markdown.data-api', '[data-provide="markdown-editable"]', function (e) {
|
||||||
|
initMarkdown($(this))
|
||||||
|
e.preventDefault()
|
||||||
|
})
|
||||||
|
.on('click', function (e) {
|
||||||
|
analyzeMarkdown(e)
|
||||||
|
})
|
||||||
|
.on('focusin', function (e) {
|
||||||
|
analyzeMarkdown(e)
|
||||||
|
})
|
||||||
|
.ready(function(){
|
||||||
|
$('textarea[data-provide="markdown"]').each(function(){
|
||||||
|
initMarkdown($(this))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}(window.jQuery);
|
6
resources/public/js/bootstrap.min.js
vendored
Normal file
6
resources/public/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1616
resources/public/js/markdown.js
Normal file
1616
resources/public/js/markdown.js
Normal file
File diff suppressed because it is too large
Load diff
43
resources/public/js/modals.js
Normal file
43
resources/public/js/modals.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Publish Post Modal
|
||||||
|
|
||||||
|
$('a[data-publishpostid]').click(function() {
|
||||||
|
var postId = $(this).data('publishpostid');
|
||||||
|
|
||||||
|
$('#publishModalOkNoReset').data('forpostid', postId);
|
||||||
|
$('#publishModalOk').data('forpostid', postId);
|
||||||
|
$('#publishModal').modal({backdrop: 'static', keyboard: false});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#publishModalOkNoReset').click(function() {
|
||||||
|
var postId = $(this).data('forpostid');
|
||||||
|
$('#publishModal').find('button').attr('disabled', 'disabled');
|
||||||
|
window.location = context + "/publish/" + postId;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#publishModalOk').click(function() {
|
||||||
|
var postId = $(this).data('forpostid');
|
||||||
|
$('#publishModal').find('button').attr('disabled', 'disabled');
|
||||||
|
window.location = context + "/publish/" + postId + "?reset-date=true";
|
||||||
|
});
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Delete Post Modal
|
||||||
|
|
||||||
|
$('a[data-deletepostid]').click(function() {
|
||||||
|
var postId = $(this).data('deletepostid');
|
||||||
|
|
||||||
|
$('#deleteModalOk').data('forpostid', postId);
|
||||||
|
$('#deleteModal').modal({backdrop: 'static', keyboard: false});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#deleteModalOk').click(function() {
|
||||||
|
var postId = $(this).data('forpostid');
|
||||||
|
$('#deleteModal').find('button').attr('disabled', 'disabled');
|
||||||
|
window.location = context + "/deletepost/" + postId;
|
||||||
|
});
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
})
|
30
resources/public/js/prettify.js
Normal file
30
resources/public/js/prettify.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
!function(){var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
|
||||||
|
(function(){function S(a){function d(e){var b=e.charCodeAt(0);if(b!==92)return b;var a=e.charAt(1);return(b=r[a])?b:"0"<=a&&a<="7"?parseInt(e.substring(1),8):a==="u"||a==="x"?parseInt(e.substring(2),16):e.charCodeAt(1)}function g(e){if(e<32)return(e<16?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e);return e==="\\"||e==="-"||e==="]"||e==="^"?"\\"+e:e}function b(e){var b=e.substring(1,e.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),e=[],a=
|
||||||
|
b[0]==="^",c=["["];a&&c.push("^");for(var a=a?1:0,f=b.length;a<f;++a){var h=b[a];if(/\\[bdsw]/i.test(h))c.push(h);else{var h=d(h),l;a+2<f&&"-"===b[a+1]?(l=d(b[a+2]),a+=2):l=h;e.push([h,l]);l<65||h>122||(l<65||h>90||e.push([Math.max(65,h)|32,Math.min(l,90)|32]),l<97||h>122||e.push([Math.max(97,h)&-33,Math.min(l,122)&-33]))}}e.sort(function(e,a){return e[0]-a[0]||a[1]-e[1]});b=[];f=[];for(a=0;a<e.length;++a)h=e[a],h[0]<=f[1]+1?f[1]=Math.max(f[1],h[1]):b.push(f=h);for(a=0;a<b.length;++a)h=b[a],c.push(g(h[0])),
|
||||||
|
h[1]>h[0]&&(h[1]+1>h[0]&&c.push("-"),c.push(g(h[1])));c.push("]");return c.join("")}function s(e){for(var a=e.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),c=a.length,d=[],f=0,h=0;f<c;++f){var l=a[f];l==="("?++h:"\\"===l.charAt(0)&&(l=+l.substring(1))&&(l<=h?d[l]=-1:a[f]=g(l))}for(f=1;f<d.length;++f)-1===d[f]&&(d[f]=++x);for(h=f=0;f<c;++f)l=a[f],l==="("?(++h,d[h]||(a[f]="(?:")):"\\"===l.charAt(0)&&(l=+l.substring(1))&&l<=h&&
|
||||||
|
(a[f]="\\"+d[l]);for(f=0;f<c;++f)"^"===a[f]&&"^"!==a[f+1]&&(a[f]="");if(e.ignoreCase&&m)for(f=0;f<c;++f)l=a[f],e=l.charAt(0),l.length>=2&&e==="["?a[f]=b(l):e!=="\\"&&(a[f]=l.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return a.join("")}for(var x=0,m=!1,j=!1,k=0,c=a.length;k<c;++k){var i=a[k];if(i.ignoreCase)j=!0;else if(/[a-z]/i.test(i.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){m=!0;j=!1;break}}for(var r={b:8,t:9,n:10,v:11,
|
||||||
|
f:12,r:13},n=[],k=0,c=a.length;k<c;++k){i=a[k];if(i.global||i.multiline)throw Error(""+i);n.push("(?:"+s(i)+")")}return RegExp(n.join("|"),j?"gi":"g")}function T(a,d){function g(a){var c=a.nodeType;if(c==1){if(!b.test(a.className)){for(c=a.firstChild;c;c=c.nextSibling)g(c);c=a.nodeName.toLowerCase();if("br"===c||"li"===c)s[j]="\n",m[j<<1]=x++,m[j++<<1|1]=a}}else if(c==3||c==4)c=a.nodeValue,c.length&&(c=d?c.replace(/\r\n?/g,"\n"):c.replace(/[\t\n\r ]+/g," "),s[j]=c,m[j<<1]=x,x+=c.length,m[j++<<1|1]=
|
||||||
|
a)}var b=/(?:^|\s)nocode(?:\s|$)/,s=[],x=0,m=[],j=0;g(a);return{a:s.join("").replace(/\n$/,""),d:m}}function H(a,d,g,b){d&&(a={a:d,e:a},g(a),b.push.apply(b,a.g))}function U(a){for(var d=void 0,g=a.firstChild;g;g=g.nextSibling)var b=g.nodeType,d=b===1?d?a:g:b===3?V.test(g.nodeValue)?a:d:d;return d===a?void 0:d}function C(a,d){function g(a){for(var j=a.e,k=[j,"pln"],c=0,i=a.a.match(s)||[],r={},n=0,e=i.length;n<e;++n){var z=i[n],w=r[z],t=void 0,f;if(typeof w==="string")f=!1;else{var h=b[z.charAt(0)];
|
||||||
|
if(h)t=z.match(h[1]),w=h[0];else{for(f=0;f<x;++f)if(h=d[f],t=z.match(h[1])){w=h[0];break}t||(w="pln")}if((f=w.length>=5&&"lang-"===w.substring(0,5))&&!(t&&typeof t[1]==="string"))f=!1,w="src";f||(r[z]=w)}h=c;c+=z.length;if(f){f=t[1];var l=z.indexOf(f),B=l+f.length;t[2]&&(B=z.length-t[2].length,l=B-f.length);w=w.substring(5);H(j+h,z.substring(0,l),g,k);H(j+h+l,f,I(w,f),k);H(j+h+B,z.substring(B),g,k)}else k.push(j+h,w)}a.g=k}var b={},s;(function(){for(var g=a.concat(d),j=[],k={},c=0,i=g.length;c<i;++c){var r=
|
||||||
|
g[c],n=r[3];if(n)for(var e=n.length;--e>=0;)b[n.charAt(e)]=r;r=r[1];n=""+r;k.hasOwnProperty(n)||(j.push(r),k[n]=q)}j.push(/[\S\s]/);s=S(j)})();var x=d.length;return g}function v(a){var d=[],g=[];a.tripleQuotedStrings?d.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?d.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
|
||||||
|
q,"'\"`"]):d.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&g.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var b=a.hashComments;b&&(a.cStyleComments?(b>1?d.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):d.push(["com",/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),g.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,q])):d.push(["com",
|
||||||
|
/^#[^\n\r]*/,q,"#"]));a.cStyleComments&&(g.push(["com",/^\/\/[^\n\r]*/,q]),g.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));if(b=a.regexLiterals){var s=(b=b>1?"":"\n\r")?".":"[\\S\\s]";g.push(["lang-regex",RegExp("^(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+("/(?=[^/*"+b+"])(?:[^/\\x5B\\x5C"+b+"]|\\x5C"+s+"|\\x5B(?:[^\\x5C\\x5D"+b+"]|\\x5C"+
|
||||||
|
s+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&g.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&g.push(["kwd",RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),q]);d.push(["pln",/^\s+/,q," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");g.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,
|
||||||
|
q],["pun",RegExp(b),q]);return C(d,g)}function J(a,d,g){function b(a){var c=a.nodeType;if(c==1&&!x.test(a.className))if("br"===a.nodeName)s(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((c==3||c==4)&&g){var d=a.nodeValue,i=d.match(m);if(i)c=d.substring(0,i.index),a.nodeValue=c,(d=d.substring(i.index+i[0].length))&&a.parentNode.insertBefore(j.createTextNode(d),a.nextSibling),s(a),c||a.parentNode.removeChild(a)}}function s(a){function b(a,c){var d=
|
||||||
|
c?a.cloneNode(!1):a,e=a.parentNode;if(e){var e=b(e,1),g=a.nextSibling;e.appendChild(d);for(var i=g;i;i=g)g=i.nextSibling,e.appendChild(i)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),d;(d=a.parentNode)&&d.nodeType===1;)a=d;c.push(a)}for(var x=/(?:^|\s)nocode(?:\s|$)/,m=/\r\n?|\n/,j=a.ownerDocument,k=j.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var c=[k],i=0;i<c.length;++i)b(c[i]);d===(d|0)&&c[0].setAttribute("value",d);var r=j.createElement("ol");
|
||||||
|
r.className="linenums";for(var d=Math.max(0,d-1|0)||0,i=0,n=c.length;i<n;++i)k=c[i],k.className="L"+(i+d)%10,k.firstChild||k.appendChild(j.createTextNode("\u00a0")),r.appendChild(k);a.appendChild(r)}function p(a,d){for(var g=d.length;--g>=0;){var b=d[g];F.hasOwnProperty(b)?D.console&&console.warn("cannot override language handler %s",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\s*</.test(d)?"default-markup":"default-code";return F[a]}function K(a){var d=a.h;try{var g=T(a.c,a.i),b=g.a;
|
||||||
|
a.a=b;a.d=g.d;a.e=0;I(d,b)(a);var s=/\bMSIE\s(\d+)/.exec(navigator.userAgent),s=s&&+s[1]<=8,d=/\n/g,x=a.a,m=x.length,g=0,j=a.d,k=j.length,b=0,c=a.g,i=c.length,r=0;c[i]=m;var n,e;for(e=n=0;e<i;)c[e]!==c[e+2]?(c[n++]=c[e++],c[n++]=c[e++]):e+=2;i=n;for(e=n=0;e<i;){for(var p=c[e],w=c[e+1],t=e+2;t+2<=i&&c[t+1]===w;)t+=2;c[n++]=p;c[n++]=w;e=t}c.length=n;var f=a.c,h;if(f)h=f.style.display,f.style.display="none";try{for(;b<k;){var l=j[b+2]||m,B=c[r+2]||m,t=Math.min(l,B),A=j[b+1],G;if(A.nodeType!==1&&(G=x.substring(g,
|
||||||
|
t))){s&&(G=G.replace(d,"\r"));A.nodeValue=G;var L=A.ownerDocument,o=L.createElement("span");o.className=c[r+1];var v=A.parentNode;v.replaceChild(o,A);o.appendChild(A);g<l&&(j[b+1]=A=L.createTextNode(x.substring(t,l)),v.insertBefore(A,o.nextSibling))}g=t;g>=l&&(b+=2);g>=B&&(r+=2)}}finally{if(f)f.style.display=h}}catch(u){D.console&&console.log(u&&u.stack||u)}}var D=window,y=["break,continue,do,else,for,if,return,while"],E=[[y,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
|
||||||
|
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],M=[E,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],N=[E,"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],
|
||||||
|
O=[N,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"],E=[E,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],P=[y,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
|
||||||
|
Q=[y,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],W=[y,"as,assert,const,copy,drop,enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv,pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"],y=[y,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],R=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/,
|
||||||
|
V=/\S/,X=v({keywords:[M,O,E,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",P,Q,y],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),F={};p(X,["default-code"]);p(C([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",
|
||||||
|
/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);p(C([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],
|
||||||
|
["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);p(C([],[["atv",/^[\S\s]+/]]),["uq.val"]);p(v({keywords:M,hashComments:!0,cStyleComments:!0,types:R}),["c","cc","cpp","cxx","cyc","m"]);p(v({keywords:"null,true,false"}),["json"]);p(v({keywords:O,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:R}),
|
||||||
|
["cs"]);p(v({keywords:N,cStyleComments:!0}),["java"]);p(v({keywords:y,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);p(v({keywords:P,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);p(v({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]);p(v({keywords:Q,
|
||||||
|
hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);p(v({keywords:E,cStyleComments:!0,regexLiterals:!0}),["javascript","js"]);p(v({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);p(v({keywords:W,cStyleComments:!0,multilineStrings:!0}),["rc","rs","rust"]);
|
||||||
|
p(C([],[["str",/^[\S\s]+/]]),["regex"]);var Y=D.PR={createSimpleLexer:C,registerLangHandler:p,sourceDecorator:v,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:D.prettyPrintOne=function(a,d,g){var b=document.createElement("div");b.innerHTML="<pre>"+a+"</pre>";b=b.firstChild;g&&J(b,g,!0);K({h:d,j:g,c:b,i:1});
|
||||||
|
return b.innerHTML},prettyPrint:D.prettyPrint=function(a,d){function g(){for(var b=D.PR_SHOULD_USE_CONTINUATION?c.now()+250:Infinity;i<p.length&&c.now()<b;i++){for(var d=p[i],j=h,k=d;k=k.previousSibling;){var m=k.nodeType,o=(m===7||m===8)&&k.nodeValue;if(o?!/^\??prettify\b/.test(o):m!==3||/\S/.test(k.nodeValue))break;if(o){j={};o.replace(/\b(\w+)=([\w%+\-.:]+)/g,function(a,b,c){j[b]=c});break}}k=d.className;if((j!==h||e.test(k))&&!v.test(k)){m=!1;for(o=d.parentNode;o;o=o.parentNode)if(f.test(o.tagName)&&
|
||||||
|
o.className&&e.test(o.className)){m=!0;break}if(!m){d.className+=" prettyprinted";m=j.lang;if(!m){var m=k.match(n),y;if(!m&&(y=U(d))&&t.test(y.tagName))m=y.className.match(n);m&&(m=m[1])}if(w.test(d.tagName))o=1;else var o=d.currentStyle,u=s.defaultView,o=(o=o?o.whiteSpace:u&&u.getComputedStyle?u.getComputedStyle(d,q).getPropertyValue("white-space"):0)&&"pre"===o.substring(0,3);u=j.linenums;if(!(u=u==="true"||+u))u=(u=k.match(/\blinenums\b(?::(\d+))?/))?u[1]&&u[1].length?+u[1]:!0:!1;u&&J(d,u,o);r=
|
||||||
|
{h:m,c:d,j:u,i:o};K(r)}}}i<p.length?setTimeout(g,250):"function"===typeof a&&a()}for(var b=d||document.body,s=b.ownerDocument||document,b=[b.getElementsByTagName("pre"),b.getElementsByTagName("code"),b.getElementsByTagName("xmp")],p=[],m=0;m<b.length;++m)for(var j=0,k=b[m].length;j<k;++j)p.push(b[m][j]);var b=q,c=Date;c.now||(c={now:function(){return+new Date}});var i=0,r,n=/\blang(?:uage)?-([\w.]+)(?!\S)/,e=/\bprettyprint\b/,v=/\bprettyprinted\b/,w=/pre|xmp/i,t=/^code$/i,f=/^(?:pre|code|xmp)$/i,
|
||||||
|
h={};g()}};typeof define==="function"&&define.amd&&define("google-code-prettify",[],function(){return Y})})();}()
|
22
resources/public/js/select2.min.js
vendored
Normal file
22
resources/public/js/select2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
17
resources/public/js/site.js
Normal file
17
resources/public/js/site.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
$(document).ready(function() {
|
||||||
|
var code = $('div.content[role="main"]').find('pre');
|
||||||
|
code.addClass('prettyprint');
|
||||||
|
|
||||||
|
// TODO: properly fix this in markdown-clj instead of doing this here...
|
||||||
|
// the problem is markdown-clj inserts newlines immediately after
|
||||||
|
// the opening <pre><code> bit which looks a bit silly
|
||||||
|
code.each(function() {
|
||||||
|
var text = $(this).text();
|
||||||
|
if (text.length >= 2 && text.substring(0, 2) === '\r\n')
|
||||||
|
$(this).text(text.substring(2));
|
||||||
|
else if (text.length >= 1 && text.substring(0, 1) === '\n')
|
||||||
|
$(this).text(text.substring(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
prettyPrint();
|
||||||
|
});
|
184
resources/public/js/to-markdown.js
Normal file
184
resources/public/js/to-markdown.js
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
/*
|
||||||
|
* to-markdown - an HTML to Markdown converter
|
||||||
|
*
|
||||||
|
* Copyright 2011, Dom Christie
|
||||||
|
* Licenced under the MIT licence
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
var toMarkdown = function(string) {
|
||||||
|
|
||||||
|
var ELEMENTS = [
|
||||||
|
{
|
||||||
|
patterns: 'p',
|
||||||
|
replacement: function(str, attrs, innerHTML) {
|
||||||
|
return innerHTML ? '\n\n' + innerHTML + '\n' : '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patterns: 'br',
|
||||||
|
type: 'void',
|
||||||
|
replacement: '\n'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patterns: 'h([1-6])',
|
||||||
|
replacement: function(str, hLevel, attrs, innerHTML) {
|
||||||
|
var hPrefix = '';
|
||||||
|
for(var i = 0; i < hLevel; i++) {
|
||||||
|
hPrefix += '#';
|
||||||
|
}
|
||||||
|
return '\n\n' + hPrefix + ' ' + innerHTML + '\n';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patterns: 'hr',
|
||||||
|
type: 'void',
|
||||||
|
replacement: '\n\n* * *\n'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patterns: 'a',
|
||||||
|
replacement: function(str, attrs, innerHTML) {
|
||||||
|
var href = attrs.match(attrRegExp('href')),
|
||||||
|
title = attrs.match(attrRegExp('title'));
|
||||||
|
return href ? '[' + innerHTML + ']' + '(' + href[1] + (title && title[1] ? ' "' + title[1] + '"' : '') + ')' : str;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patterns: ['b', 'strong'],
|
||||||
|
replacement: function(str, attrs, innerHTML) {
|
||||||
|
return innerHTML ? '**' + innerHTML + '**' : '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patterns: ['i', 'em'],
|
||||||
|
replacement: function(str, attrs, innerHTML) {
|
||||||
|
return innerHTML ? '_' + innerHTML + '_' : '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patterns: 'code',
|
||||||
|
replacement: function(str, attrs, innerHTML) {
|
||||||
|
return innerHTML ? '`' + innerHTML + '`' : '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patterns: 'img',
|
||||||
|
type: 'void',
|
||||||
|
replacement: function(str, attrs, innerHTML) {
|
||||||
|
var src = attrs.match(attrRegExp('src')),
|
||||||
|
alt = attrs.match(attrRegExp('alt')),
|
||||||
|
title = attrs.match(attrRegExp('title'));
|
||||||
|
return '![' + (alt && alt[1] ? alt[1] : '') + ']' + '(' + src[1] + (title && title[1] ? ' "' + title[1] + '"' : '') + ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
for(var i = 0, len = ELEMENTS.length; i < len; i++) {
|
||||||
|
if(typeof ELEMENTS[i].patterns === 'string') {
|
||||||
|
string = replaceEls(string, { tag: ELEMENTS[i].patterns, replacement: ELEMENTS[i].replacement, type: ELEMENTS[i].type });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for(var j = 0, pLen = ELEMENTS[i].patterns.length; j < pLen; j++) {
|
||||||
|
string = replaceEls(string, { tag: ELEMENTS[i].patterns[j], replacement: ELEMENTS[i].replacement, type: ELEMENTS[i].type });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceEls(html, elProperties) {
|
||||||
|
var pattern = elProperties.type === 'void' ? '<' + elProperties.tag + '\\b([^>]*)\\/?>' : '<' + elProperties.tag + '\\b([^>]*)>([\\s\\S]*?)<\\/' + elProperties.tag + '>',
|
||||||
|
regex = new RegExp(pattern, 'gi'),
|
||||||
|
markdown = '';
|
||||||
|
if(typeof elProperties.replacement === 'string') {
|
||||||
|
markdown = html.replace(regex, elProperties.replacement);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
markdown = html.replace(regex, function(str, p1, p2, p3) {
|
||||||
|
return elProperties.replacement.call(this, str, p1, p2, p3);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return markdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
function attrRegExp(attr) {
|
||||||
|
return new RegExp(attr + '\\s*=\\s*["\']?([^"\']*)["\']?', 'i');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre code blocks
|
||||||
|
|
||||||
|
string = string.replace(/<pre\b[^>]*>`([\s\S]*)`<\/pre>/gi, function(str, innerHTML) {
|
||||||
|
innerHTML = innerHTML.replace(/^\t+/g, ' '); // convert tabs to spaces (you know it makes sense)
|
||||||
|
innerHTML = innerHTML.replace(/\n/g, '\n ');
|
||||||
|
return '\n\n ' + innerHTML + '\n';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lists
|
||||||
|
|
||||||
|
// Escape numbers that could trigger an ol
|
||||||
|
// If there are more than three spaces before the code, it would be in a pre tag
|
||||||
|
// Make sure we are escaping the period not matching any character
|
||||||
|
string = string.replace(/^(\s{0,3}\d+)\. /g, '$1\\. ');
|
||||||
|
|
||||||
|
// Converts lists that have no child lists (of same type) first, then works it's way up
|
||||||
|
var noChildrenRegex = /<(ul|ol)\b[^>]*>(?:(?!<ul|<ol)[\s\S])*?<\/\1>/gi;
|
||||||
|
while(string.match(noChildrenRegex)) {
|
||||||
|
string = string.replace(noChildrenRegex, function(str) {
|
||||||
|
return replaceLists(str);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceLists(html) {
|
||||||
|
|
||||||
|
html = html.replace(/<(ul|ol)\b[^>]*>([\s\S]*?)<\/\1>/gi, function(str, listType, innerHTML) {
|
||||||
|
var lis = innerHTML.split('</li>');
|
||||||
|
lis.splice(lis.length - 1, 1);
|
||||||
|
|
||||||
|
for(i = 0, len = lis.length; i < len; i++) {
|
||||||
|
if(lis[i]) {
|
||||||
|
var prefix = (listType === 'ol') ? (i + 1) + ". " : "* ";
|
||||||
|
lis[i] = lis[i].replace(/\s*<li[^>]*>([\s\S]*)/i, function(str, innerHTML) {
|
||||||
|
|
||||||
|
innerHTML = innerHTML.replace(/^\s+/, '');
|
||||||
|
innerHTML = innerHTML.replace(/\n\n/g, '\n\n ');
|
||||||
|
// indent nested lists
|
||||||
|
innerHTML = innerHTML.replace(/\n([ ]*)+(\*|\d+\.) /g, '\n$1 $2 ');
|
||||||
|
return prefix + innerHTML;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lis.join('\n');
|
||||||
|
});
|
||||||
|
return '\n\n' + html.replace(/[ \t]+\n|\s+$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blockquotes
|
||||||
|
var deepest = /<blockquote\b[^>]*>((?:(?!<blockquote)[\s\S])*?)<\/blockquote>/gi;
|
||||||
|
while(string.match(deepest)) {
|
||||||
|
string = string.replace(deepest, function(str) {
|
||||||
|
return replaceBlockquotes(str);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceBlockquotes(html) {
|
||||||
|
html = html.replace(/<blockquote\b[^>]*>([\s\S]*?)<\/blockquote>/gi, function(str, inner) {
|
||||||
|
inner = inner.replace(/^\s+|\s+$/g, '');
|
||||||
|
inner = cleanUp(inner);
|
||||||
|
inner = inner.replace(/^/gm, '> ');
|
||||||
|
inner = inner.replace(/^(>([ \t]{2,}>)+)/gm, '> >');
|
||||||
|
return inner;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanUp(string) {
|
||||||
|
string = string.replace(/^[\t\r\n]+|[\t\r\n]+$/g, ''); // trim leading/trailing whitespace
|
||||||
|
string = string.replace(/\n\s+\n/g, '\n\n');
|
||||||
|
string = string.replace(/\n{3,}/g, '\n\n'); // limit consecutive linebreaks to 2
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanUp(string);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof exports === 'object') {
|
||||||
|
exports.toMarkdown = toMarkdown;
|
||||||
|
}
|
2
resources/site.config
Normal file
2
resources/site.config
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
{:database
|
||||||
|
{:url "http://10.0.0.20:5984/"}}
|
21
src/blarg/config.clj
Normal file
21
src/blarg/config.clj
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
(ns blarg.config
|
||||||
|
(:require [clojure.java.io :as io])
|
||||||
|
(:import [java.io PushbackReader]))
|
||||||
|
|
||||||
|
(def site-config (atom nil))
|
||||||
|
|
||||||
|
(defn load-config []
|
||||||
|
(with-open [r (PushbackReader. (io/reader (io/resource "site.config")))]
|
||||||
|
(read r)))
|
||||||
|
|
||||||
|
(defn get-config
|
||||||
|
"returns the entire site configuration"
|
||||||
|
[]
|
||||||
|
(if (nil? @site-config)
|
||||||
|
(reset! site-config (load-config))
|
||||||
|
@site-config))
|
||||||
|
|
||||||
|
(defn get-db-config
|
||||||
|
"returns just the database portion of the site configuration"
|
||||||
|
[]
|
||||||
|
(:database (get-config)))
|
84
src/blarg/datetime.clj
Normal file
84
src/blarg/datetime.clj
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
(ns blarg.datetime
|
||||||
|
(:require [clj-time.core]
|
||||||
|
[clj-time.coerce]
|
||||||
|
[clj-time.format]
|
||||||
|
[clj-time.local]))
|
||||||
|
|
||||||
|
(defn get-timestamp
|
||||||
|
"Converts a date object to a local timestamp string. The current
|
||||||
|
date is used if none is provided."
|
||||||
|
([] (get-timestamp (clj-time.local/local-now)))
|
||||||
|
([date]
|
||||||
|
(clj-time.local/format-local-time date :date-time)))
|
||||||
|
|
||||||
|
(defn parse-timestamp
|
||||||
|
"Converts a local timestamp string to a date object."
|
||||||
|
[s]
|
||||||
|
(clj-time.local/to-local-date-time s))
|
||||||
|
|
||||||
|
(defn same-date?
|
||||||
|
"Compares to dates to see if they are the same. The dates passed in are
|
||||||
|
coerced to LocalDate objects before being compared."
|
||||||
|
([a b-day b-month b-year]
|
||||||
|
(same-date? a (clj-time.core/local-date b-year b-month b-day)))
|
||||||
|
([a b]
|
||||||
|
(let [local-a (clj-time.coerce/to-local-date a)
|
||||||
|
local-b (clj-time.coerce/to-local-date b)]
|
||||||
|
(= 0 (.compareTo local-a local-b)))))
|
||||||
|
|
||||||
|
(defn string->date
|
||||||
|
"Given a map (or a sequence of maps), converts string timestamps contained in
|
||||||
|
each of the provided fields to LocalDate objects."
|
||||||
|
[coll fields]
|
||||||
|
(cond
|
||||||
|
(map? coll)
|
||||||
|
(merge
|
||||||
|
coll
|
||||||
|
(apply
|
||||||
|
(fn [field]
|
||||||
|
(if-let [timestamp (get coll field)]
|
||||||
|
{field (parse-timestamp timestamp)}))
|
||||||
|
fields))
|
||||||
|
|
||||||
|
(seq? coll)
|
||||||
|
(map (fn [m] (string->date m fields)) coll)))
|
||||||
|
|
||||||
|
(defn date->string
|
||||||
|
"Does the reverse of string->date (converts LocalDate's back to strings)"
|
||||||
|
[coll fields]
|
||||||
|
(cond
|
||||||
|
(map? coll)
|
||||||
|
(merge
|
||||||
|
coll
|
||||||
|
(apply
|
||||||
|
(fn [field]
|
||||||
|
(if-let [date (get coll field)]
|
||||||
|
{field (get-timestamp date)}))
|
||||||
|
fields))
|
||||||
|
|
||||||
|
(seq? coll)
|
||||||
|
(map (fn [m] (date->string m fields)) coll)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn ->relative-timestamp
|
||||||
|
"Returns a readable string representing the time between the current date
|
||||||
|
and the provided one"
|
||||||
|
[date]
|
||||||
|
(if (string? date)
|
||||||
|
(->relative-timestamp (clj-time.local/to-local-date-time date))
|
||||||
|
(let [span (clj-time.core/interval date (clj-time.core/now))
|
||||||
|
years (clj-time.core/in-years span)
|
||||||
|
weeks (clj-time.core/in-weeks span)
|
||||||
|
days (clj-time.core/in-days span)
|
||||||
|
hours (clj-time.core/in-hours span)
|
||||||
|
minutes (clj-time.core/in-minutes span)]
|
||||||
|
(cond
|
||||||
|
(> years 0) (clj-time.format/unparse (clj-time.format/formatter "MMM d, yyyy") date)
|
||||||
|
(> weeks 0) (clj-time.format/unparse (clj-time.format/formatter "MMM d") date)
|
||||||
|
(> days 1) (str days " days ago")
|
||||||
|
(= days 1) "1 day ago"
|
||||||
|
(> hours 1) (str hours " hours ago")
|
||||||
|
(= hours 1) "1 hour ago"
|
||||||
|
(> minutes 1) (str minutes " minutes ago")
|
||||||
|
(= minutes 1) "1 minute ago"
|
||||||
|
:else "just now"))))
|
55
src/blarg/handler.clj
Normal file
55
src/blarg/handler.clj
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
(ns blarg.handler
|
||||||
|
(:use blarg.routes.home
|
||||||
|
blarg.routes.posts
|
||||||
|
blarg.routes.auth
|
||||||
|
blarg.routes.accessrules
|
||||||
|
compojure.core)
|
||||||
|
(:require [noir.util.middleware :as middleware]
|
||||||
|
[compojure.route :as route]
|
||||||
|
[taoensso.timbre :as timbre]
|
||||||
|
[com.postspectacular.rotor :as rotor]
|
||||||
|
[blarg.views.layout :as layout]))
|
||||||
|
|
||||||
|
(defroutes app-routes
|
||||||
|
(route/resources "/")
|
||||||
|
(route/not-found "Not Found"))
|
||||||
|
|
||||||
|
(defn destroy []
|
||||||
|
(timbre/info "picture-gallery is shutting down"))
|
||||||
|
|
||||||
|
(defn init
|
||||||
|
"init will be called once when
|
||||||
|
app is deployed as a servlet on
|
||||||
|
an app server such as Tomcat
|
||||||
|
put any initialization code here"
|
||||||
|
[]
|
||||||
|
(timbre/set-config!
|
||||||
|
[:appenders :rotor]
|
||||||
|
{:min-level :info
|
||||||
|
:enabled? true
|
||||||
|
:async? false ; should be always false for rotor
|
||||||
|
:max-message-per-msecs nil
|
||||||
|
:fn rotor/append})
|
||||||
|
|
||||||
|
(timbre/set-config!
|
||||||
|
[:shared-appender-config :rotor]
|
||||||
|
{:path "blarg.log" :max-size 10000 :backlog 10})
|
||||||
|
|
||||||
|
(timbre/info "blarg started successfully"))
|
||||||
|
|
||||||
|
(defn destroy
|
||||||
|
"destroy will be called when your application
|
||||||
|
shuts down, put any clean up code here"
|
||||||
|
[]
|
||||||
|
(timbre/info "blarg is shutting down..."))
|
||||||
|
|
||||||
|
;;append your application routes to the all-routes vector
|
||||||
|
(def all-routes [auth-routes home-routes posts-routes app-routes])
|
||||||
|
|
||||||
|
(def app (-> all-routes
|
||||||
|
(middleware/app-handler)
|
||||||
|
(middleware/wrap-access-rules {:redirect "/unauthorized"} auth-required)
|
||||||
|
;;add your middlewares here
|
||||||
|
))
|
||||||
|
|
||||||
|
(def war-handler (middleware/war-handler app))
|
29
src/blarg/models/db.clj
Normal file
29
src/blarg/models/db.clj
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
(ns blarg.models.db
|
||||||
|
(:require [blarg.config :as config]
|
||||||
|
[com.ashafa.clutch :as couch]))
|
||||||
|
|
||||||
|
(defn db-url [db]
|
||||||
|
(let [c (config/get-db-config)
|
||||||
|
url (:url c)
|
||||||
|
user (:user c)
|
||||||
|
pass (:pass c)]
|
||||||
|
(if (and user pass)
|
||||||
|
(assoc (cemerick.url/url url db)
|
||||||
|
:username user
|
||||||
|
:password pass)
|
||||||
|
(cemerick.url/url url db))))
|
||||||
|
|
||||||
|
(def users (db-url "blarg_users"))
|
||||||
|
(def posts (db-url "blarg_posts"))
|
||||||
|
(def comments (db-url "blarg_comments"))
|
||||||
|
|
||||||
|
(defmacro ->view-values
|
||||||
|
"returns a sequence of only the values returned by running a view"
|
||||||
|
[& body]
|
||||||
|
`(if-let [result# ~@body]
|
||||||
|
(map (fn [x#] (:value x#)) result#)))
|
||||||
|
|
||||||
|
(defmacro ->first-view-value
|
||||||
|
"returns only the first value from the sequence returned by running a view"
|
||||||
|
[& body]
|
||||||
|
`(first (->view-values ~@body)))
|
95
src/blarg/models/posts.clj
Normal file
95
src/blarg/models/posts.clj
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
(ns blarg.models.posts
|
||||||
|
(:use [blarg.models.db]
|
||||||
|
[blarg.datetime]
|
||||||
|
[slugger.core])
|
||||||
|
(:require [com.ashafa.clutch :as couch]))
|
||||||
|
|
||||||
|
(def per-page 5)
|
||||||
|
(def timestamp-fields [:created_at])
|
||||||
|
|
||||||
|
(defmacro ->post-list [& body]
|
||||||
|
`(string->date
|
||||||
|
(->view-values
|
||||||
|
~@body)
|
||||||
|
~timestamp-fields))
|
||||||
|
|
||||||
|
(defmacro ->single-post [& body]
|
||||||
|
`(string->date
|
||||||
|
(let [result# ~@body]
|
||||||
|
(if (seq? result#)
|
||||||
|
(->first-view-value result#)
|
||||||
|
result#))
|
||||||
|
~timestamp-fields))
|
||||||
|
|
||||||
|
(defn get-post [id]
|
||||||
|
(->single-post
|
||||||
|
(couch/with-db posts
|
||||||
|
(couch/get-document id))))
|
||||||
|
|
||||||
|
(defn get-post-by-date-slug [date slug]
|
||||||
|
(->single-post
|
||||||
|
(couch/with-db posts
|
||||||
|
(couch/get-view "posts" "listPostsBySlug" {:key [date, slug]}))))
|
||||||
|
|
||||||
|
(defn add-post [title body user tags]
|
||||||
|
(->single-post
|
||||||
|
(couch/with-db posts
|
||||||
|
(couch/put-document {:title title
|
||||||
|
:slug (->slug title)
|
||||||
|
:body body
|
||||||
|
:user user
|
||||||
|
:tags tags
|
||||||
|
:created_at (get-timestamp)
|
||||||
|
:published false
|
||||||
|
:type "post"}))))
|
||||||
|
|
||||||
|
(defn update-post [id title body user tags published? reset-date?]
|
||||||
|
(if-let [post (get-post id)]
|
||||||
|
(->single-post
|
||||||
|
(couch/with-db posts
|
||||||
|
(couch/update-document
|
||||||
|
(-> (date->string post timestamp-fields)
|
||||||
|
(merge (if title {:title title :slug (->slug title)}))
|
||||||
|
(merge (if body {:body body}))
|
||||||
|
(merge (if user {:user user}))
|
||||||
|
(merge (if tags {:tags tags}))
|
||||||
|
(merge (if-not (nil? published?) {:published published?}))
|
||||||
|
(merge (if reset-date? {:created_at (get-timestamp)}))))))))
|
||||||
|
|
||||||
|
(defn delete-post [id]
|
||||||
|
(if-let [post (get-post id)]
|
||||||
|
(couch/with-db posts
|
||||||
|
(couch/delete-document post))))
|
||||||
|
|
||||||
|
(defn publish-post [id publish? reset-date?]
|
||||||
|
(update-post id nil nil nil nil publish? reset-date?))
|
||||||
|
|
||||||
|
(defn list-tags []
|
||||||
|
(map
|
||||||
|
(fn [x]
|
||||||
|
(:key x))
|
||||||
|
(couch/with-db posts
|
||||||
|
(couch/get-view "posts" "listTags" {:group true}))))
|
||||||
|
|
||||||
|
(defn list-posts
|
||||||
|
([unpublished?] (list-posts unpublished? per-page 0))
|
||||||
|
([unpublished? n] (list-posts unpublished? n 0))
|
||||||
|
([unpublished? n offset]
|
||||||
|
(let [params (merge
|
||||||
|
{:descending true}
|
||||||
|
(when n {:limit n})
|
||||||
|
(when offset {:skip offset}))
|
||||||
|
viewName (if unpublished? "listPosts" "listPublishedPosts")]
|
||||||
|
(->post-list
|
||||||
|
(couch/with-db posts
|
||||||
|
(couch/get-view "posts" viewName params))))))
|
||||||
|
|
||||||
|
(defn count-posts
|
||||||
|
[unpublished?]
|
||||||
|
(->first-view-value
|
||||||
|
(couch/with-db posts
|
||||||
|
(couch/get-view
|
||||||
|
"posts"
|
||||||
|
(if unpublished? "countPosts" "countPublishedPosts")
|
||||||
|
{:group true}))))
|
||||||
|
|
27
src/blarg/models/users.clj
Normal file
27
src/blarg/models/users.clj
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
(ns blarg.models.users
|
||||||
|
(:use [blarg.models.db]
|
||||||
|
[blarg.datetime])
|
||||||
|
(:require [com.ashafa.clutch :as couch]
|
||||||
|
[noir.util.crypt :as crypt]))
|
||||||
|
|
||||||
|
(defn get-user
|
||||||
|
([id]
|
||||||
|
(couch/with-db users
|
||||||
|
(couch/get-document id)))
|
||||||
|
([id pass]
|
||||||
|
(if-let [user (get-user id)]
|
||||||
|
(when (crypt/compare pass (:password user))
|
||||||
|
user))))
|
||||||
|
|
||||||
|
(defn add-user [id pass email]
|
||||||
|
(let [user {:_id id
|
||||||
|
:password (crypt/encrypt pass)
|
||||||
|
:email email
|
||||||
|
:created_at (get-timestamp)}]
|
||||||
|
(couch/with-db users
|
||||||
|
(couch/put-document user))))
|
||||||
|
|
||||||
|
(defn delete-user [id]
|
||||||
|
(couch/with-db users
|
||||||
|
(if-let [user (get-user id)]
|
||||||
|
(couch/delete-document user))))
|
38
src/blarg/repl.clj
Normal file
38
src/blarg/repl.clj
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
(ns blarg.repl
|
||||||
|
(:use blarg.handler
|
||||||
|
ring.server.standalone
|
||||||
|
[ring.middleware file-info file]))
|
||||||
|
|
||||||
|
(defonce server (atom nil))
|
||||||
|
|
||||||
|
(defn get-handler []
|
||||||
|
;; #'app expands to (var app) so that when we reload our code,
|
||||||
|
;; the server is forced to re-resolve the symbol in the var
|
||||||
|
;; rather than having its own copy. When the root binding
|
||||||
|
;; changes, the server picks it up without having to restart.
|
||||||
|
(-> #'app
|
||||||
|
; Makes static assets in $PROJECT_DIR/resources/public/ available.
|
||||||
|
(wrap-file "resources")
|
||||||
|
; Content-Type, Content-Length, and Last Modified headers for files in body
|
||||||
|
(wrap-file-info)))
|
||||||
|
|
||||||
|
(defn start-server
|
||||||
|
"used for starting the server in development mode from REPL"
|
||||||
|
[& [port]]
|
||||||
|
(let [port (if port (Integer/parseInt port) 8080)]
|
||||||
|
(reset! server
|
||||||
|
(serve (get-handler)
|
||||||
|
{:port port
|
||||||
|
:init init
|
||||||
|
:auto-reload? true
|
||||||
|
:destroy destroy
|
||||||
|
:join true}))
|
||||||
|
(println (str "You can view the site at http://localhost:" port))))
|
||||||
|
|
||||||
|
(defn stop-server []
|
||||||
|
(.stop @server)
|
||||||
|
(reset! server nil))
|
||||||
|
|
||||||
|
#_(start-server)
|
||||||
|
|
||||||
|
#_(stop-server)
|
6
src/blarg/routes/accessrules.clj
Normal file
6
src/blarg/routes/accessrules.clj
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
(ns blarg.routes.accessrules
|
||||||
|
(:require [blarg.routes.auth :as auth]
|
||||||
|
[noir.session :as session]))
|
||||||
|
|
||||||
|
(defn auth-required [method url params]
|
||||||
|
(auth/logged-in?))
|
38
src/blarg/routes/auth.clj
Normal file
38
src/blarg/routes/auth.clj
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
(ns blarg.routes.auth
|
||||||
|
(:use [blarg.routes.helpers]
|
||||||
|
[compojure.core]
|
||||||
|
[noir.util.route])
|
||||||
|
(:require [blarg.views.layout :as layout]
|
||||||
|
[blarg.models.users :as users]
|
||||||
|
[noir.session :as session]
|
||||||
|
[noir.response :as resp]
|
||||||
|
[noir.validation :as vali]))
|
||||||
|
|
||||||
|
(defn logged-in? []
|
||||||
|
(not (nil? (session/get :user))))
|
||||||
|
|
||||||
|
(defn login-page []
|
||||||
|
(if (logged-in?)
|
||||||
|
(resp/redirect "/")
|
||||||
|
(layout/render "auth/login.html" {:login-error (session/flash-get :login-error)
|
||||||
|
:html-title (->html-title ["Login"])})))
|
||||||
|
|
||||||
|
(defn handle-login [id pass]
|
||||||
|
(if-let [user (users/get-user id pass)]
|
||||||
|
(do
|
||||||
|
(session/put! :user id)
|
||||||
|
(resp/redirect "/"))
|
||||||
|
(do
|
||||||
|
(session/flash-put! :login-error "Invalid username/password.")
|
||||||
|
(resp/redirect "/login"))))
|
||||||
|
|
||||||
|
(defn logout []
|
||||||
|
(session/clear!)
|
||||||
|
(resp/redirect "/"))
|
||||||
|
|
||||||
|
(defroutes auth-routes
|
||||||
|
(GET "/unauthorized" [] "Unauthorized.")
|
||||||
|
(GET "/login" [] (login-page))
|
||||||
|
(POST "/login" [id pass] (handle-login id pass))
|
||||||
|
(GET "/logout" [] (logout))
|
||||||
|
(restricted GET "/private" [] "private!"))
|
13
src/blarg/routes/helpers.clj
Normal file
13
src/blarg/routes/helpers.clj
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
(ns blarg.routes.helpers
|
||||||
|
(:require [clj-time.core]))
|
||||||
|
|
||||||
|
(defn get-post-url [post]
|
||||||
|
(str
|
||||||
|
"/"
|
||||||
|
(clj-time.core/year (:created_at post)) "/"
|
||||||
|
(clj-time.core/month (:created_at post)) "/"
|
||||||
|
(clj-time.core/day (:created_at post)) "/"
|
||||||
|
(:slug post)))
|
||||||
|
|
||||||
|
(defn ->html-title [coll]
|
||||||
|
(str " » " (clojure.string/join " » " coll)))
|
14
src/blarg/routes/home.clj
Normal file
14
src/blarg/routes/home.clj
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
(ns blarg.routes.home
|
||||||
|
(:use [slugger.core]
|
||||||
|
[compojure.core]
|
||||||
|
[blarg.routes.helpers])
|
||||||
|
(:require [blarg.views.layout :as layout]
|
||||||
|
[noir.response :as resp]))
|
||||||
|
|
||||||
|
(defn about-page []
|
||||||
|
(layout/render
|
||||||
|
"about.html" {:html-title (->html-title ["About"])}))
|
||||||
|
|
||||||
|
(defroutes home-routes
|
||||||
|
(GET "/about" [] (about-page))
|
||||||
|
(GET "/toslug" [text] (resp/json {:slug (if text (->slug text))})))
|
117
src/blarg/routes/posts.clj
Normal file
117
src/blarg/routes/posts.clj
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
(ns blarg.routes.posts
|
||||||
|
(:use [slugger.core]
|
||||||
|
[compojure.core]
|
||||||
|
[noir.util.route]
|
||||||
|
[blarg.util]
|
||||||
|
[blarg.routes.helpers])
|
||||||
|
(:require [clj-time.core]
|
||||||
|
[clojure.math.numeric-tower :as math]
|
||||||
|
[noir.response :as resp]
|
||||||
|
[noir.validation :as vali]
|
||||||
|
[noir.session :as session]
|
||||||
|
[blarg.views.layout :as layout]
|
||||||
|
[blarg.models.posts :as posts]
|
||||||
|
[blarg.routes.auth :as auth]))
|
||||||
|
|
||||||
|
(defn string->tags [s]
|
||||||
|
(set
|
||||||
|
(map
|
||||||
|
(fn [tag]
|
||||||
|
(->slug tag))
|
||||||
|
(clojure.string/split s #","))))
|
||||||
|
|
||||||
|
(defn tags->string [tags]
|
||||||
|
(clojure.string/join "," tags))
|
||||||
|
|
||||||
|
(defn postcount->pagecount
|
||||||
|
([] (postcount->pagecount (posts/count-posts (auth/logged-in?))))
|
||||||
|
([postcount]
|
||||||
|
(int (math/ceil (/ postcount posts/per-page)))))
|
||||||
|
|
||||||
|
(defn valid-post? [title tags body]
|
||||||
|
(vali/rule (vali/has-value? title)
|
||||||
|
[:title "Title must be provided."])
|
||||||
|
(vali/rule (vali/has-value? tags)
|
||||||
|
[:tags "One or more tags must be provided."])
|
||||||
|
(vali/rule (vali/has-value? body)
|
||||||
|
[:body "Post body must be provided."])
|
||||||
|
(not (vali/errors? :title :tags :body)))
|
||||||
|
|
||||||
|
(defn list-page [page]
|
||||||
|
(let [totalpages (postcount->pagecount )
|
||||||
|
lastpage (- totalpages 1)
|
||||||
|
currentpage (make-in-range page 0 lastpage)
|
||||||
|
offset (* currentpage posts/per-page)]
|
||||||
|
(layout/render
|
||||||
|
"posts/list.html" {:posts (posts/list-posts (auth/logged-in?) posts/per-page offset)
|
||||||
|
:prevpage (- currentpage 1)
|
||||||
|
:nextpage (+ currentpage 1)
|
||||||
|
:atlastpage (= currentpage lastpage)
|
||||||
|
:atfirstpage (= currentpage 0)
|
||||||
|
:inlist true})))
|
||||||
|
|
||||||
|
(defn show-post-page [year month day slug]
|
||||||
|
(let [date (str year "-" month "-" day)
|
||||||
|
post (posts/get-post-by-date-slug date slug)]
|
||||||
|
(if (not-empty post)
|
||||||
|
(layout/render
|
||||||
|
"posts/showpost.html" {:post post
|
||||||
|
:html-title (->html-title [(:title post)])})
|
||||||
|
(resp/redirect "/notfound"))))
|
||||||
|
|
||||||
|
(defn new-post-page [& post]
|
||||||
|
(layout/render
|
||||||
|
"posts/newpost.html" (merge (first post)
|
||||||
|
{:all-tags (posts/list-tags)
|
||||||
|
:html-title (->html-title ["New Post"])
|
||||||
|
:validation-errors @vali/*errors*})))
|
||||||
|
|
||||||
|
(defn handle-new-post [title tags body]
|
||||||
|
(if (valid-post? title tags body)
|
||||||
|
(if-let [post (posts/add-post title body (session/get :user) (string->tags tags))]
|
||||||
|
(resp/redirect (get-post-url post))
|
||||||
|
(throw (Exception. "Error creating new post.")))
|
||||||
|
(new-post-page {:title title
|
||||||
|
:tags tags
|
||||||
|
:body body})))
|
||||||
|
|
||||||
|
(defn edit-post-page [id & posted-post]
|
||||||
|
(let [post (if posted-post
|
||||||
|
(first posted-post)
|
||||||
|
(posts/get-post id))]
|
||||||
|
(layout/render
|
||||||
|
"posts/editpost.html" (merge post
|
||||||
|
{:tags (tags->string (:tags post))
|
||||||
|
:all-tags (posts/list-tags)
|
||||||
|
:html-title (->html-title ["Edit Post"])
|
||||||
|
:validation-errors @vali/*errors*}))))
|
||||||
|
|
||||||
|
(defn handle-edit-post [id title tags body]
|
||||||
|
(if (valid-post? title tags body)
|
||||||
|
(if-let [post (posts/update-post id title body (session/get :user) (string->tags tags) nil nil)]
|
||||||
|
(resp/redirect (get-post-url post))
|
||||||
|
(throw (Exception. "Error updating post.")))
|
||||||
|
(edit-post-page id {:title title
|
||||||
|
:tags tags
|
||||||
|
:body body})))
|
||||||
|
|
||||||
|
(defn handle-publish-post [id publish? reset-date?]
|
||||||
|
(if-let [post (posts/publish-post id publish? reset-date?)]
|
||||||
|
(resp/redirect (get-post-url post))
|
||||||
|
(throw (Exception. "Error toggling publish on post."))))
|
||||||
|
|
||||||
|
(defn handle-delete-post [id]
|
||||||
|
(if-let [post (posts/delete-post id)]
|
||||||
|
(resp/redirect "/")
|
||||||
|
(throw (Exception. "Error deleting post."))))
|
||||||
|
|
||||||
|
(defroutes posts-routes
|
||||||
|
(GET "/" [page] (list-page (parse-int page 0)))
|
||||||
|
(GET "/:year/:month/:day/:slug" [year month day slug] (show-post-page year month day slug))
|
||||||
|
(restricted GET "/newpost" [] (new-post-page))
|
||||||
|
(restricted POST "/newpost" [title tags body] (handle-new-post title tags body))
|
||||||
|
(restricted GET "/editpost/:id" [id] (edit-post-page id))
|
||||||
|
(restricted POST "/editpost/:id" [id title tags body] (handle-edit-post id title tags body))
|
||||||
|
(restricted GET "/publish/:id" [id reset-date] (handle-publish-post id true reset-date))
|
||||||
|
(restricted GET "/unpublish/:id" [id] (handle-publish-post id false false))
|
||||||
|
(restricted GET "/deletepost/:id" [id] (handle-delete-post id)))
|
33
src/blarg/util.clj
Normal file
33
src/blarg/util.clj
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
(ns blarg.util
|
||||||
|
(:require [noir.io :as io]
|
||||||
|
[markdown.core :as md]))
|
||||||
|
|
||||||
|
(defn md->html
|
||||||
|
"reads a markdown file from public/md and returns an HTML string"
|
||||||
|
[filename]
|
||||||
|
(->>
|
||||||
|
(io/slurp-resource filename)
|
||||||
|
(md/md-to-html-string)))
|
||||||
|
|
||||||
|
(defn string->int
|
||||||
|
([s] (string->int s nil))
|
||||||
|
([s default]
|
||||||
|
(let [match (re-find #"-?\d+" s)]
|
||||||
|
(if match
|
||||||
|
(Integer. match)
|
||||||
|
default))))
|
||||||
|
|
||||||
|
(defn parse-int
|
||||||
|
([x] (parse-int x nil))
|
||||||
|
([x default]
|
||||||
|
(cond
|
||||||
|
(nil? x) default
|
||||||
|
(number? x) (int x)
|
||||||
|
(string? x) (string->int x default)
|
||||||
|
:else default)))
|
||||||
|
|
||||||
|
(defn make-in-range [n low high]
|
||||||
|
(cond
|
||||||
|
(< n low) low
|
||||||
|
(> n high) high
|
||||||
|
:else n))
|
13
src/blarg/views/layout.clj
Normal file
13
src/blarg/views/layout.clj
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
(ns blarg.views.layout
|
||||||
|
(:use noir.request)
|
||||||
|
(:require [clabango.parser :as parser]
|
||||||
|
[noir.session :as session]
|
||||||
|
[blarg.views.viewfilters]))
|
||||||
|
|
||||||
|
(def template-path "blarg/views/templates/")
|
||||||
|
|
||||||
|
(defn render [template & [params]]
|
||||||
|
(parser/render-file (str template-path template)
|
||||||
|
(assoc params
|
||||||
|
:context (:context *request*)
|
||||||
|
:user-id (session/get :user))))
|
12
src/blarg/views/templates/about.html
Normal file
12
src/blarg/views/templates/about.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends "blarg/views/templates/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
|
<h2>About This Site</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nam cursus. Morbi ut mi. Nullam enim leo, egestas id, condimentum at, laoreet mattis, massa. Sed eleifend nonummy diam. Praesent mauris ante, elementum et, bibendum at, posuere sit amet, nibh. Duis tincidunt lectus quis dui viverra vestibulum. Suspendisse vulputate aliquam dui. Nulla elementum dui ut augue. Aliquam vehicula mi at mauris. Maecenas placerat, nisl at consequat rhoncus, sem nunc gravida justo, quis eleifend arcu velit quis lacus. Morbi magna magna, tincidunt a, mattis non, imperdiet vitae, tellus. Sed odio est, auctor ac, sollicitudin in, consequat vitae, orci. Fusce id felis. Vivamus sollicitudin metus eget eros.</p>
|
||||||
|
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In posuere felis nec tortor. Pellentesque faucibus. Ut accumsan ultricies elit. Maecenas at justo id velit placerat molestie. Donec dictum lectus non odio. Cras a ante vitae enim iaculis aliquam. Mauris nunc quam, venenatis nec, euismod sit amet, egestas placerat, est. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras id elit. Integer quis urna. Ut ante enim, dapibus malesuada, fringilla eu, condimentum quis, tellus. Aenean porttitor eros vel dolor. Donec convallis pede venenatis nibh. Duis quam. Nam eget lacus. Aliquam erat volutpat. Quisque dignissim congue leo.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
18
src/blarg/views/templates/auth/login.html
Normal file
18
src/blarg/views/templates/auth/login.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{% extends "blarg/views/templates/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="login-container">
|
||||||
|
<h2>Login</h2>
|
||||||
|
|
||||||
|
{% if login-error %}
|
||||||
|
<p class="error">{{login-error}}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form action="{{context}}/login" method="POST">
|
||||||
|
<p><input id="id" name="id" type="text" class="input-block-level" placeholder="Username"></input></p>
|
||||||
|
<p><input id="pass" name="pass" type="password" class="input-block-level" placeholder="Password"></input></p>
|
||||||
|
<button class="btn btn-primary" type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
73
src/blarg/views/templates/base.html
Normal file
73
src/blarg/views/templates/base.html
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<title>blarg.ca{{html-title|default:}}</title>
|
||||||
|
|
||||||
|
<link href="{{context}}/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="{{context}}/css/bootstrap-markdown.css" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="{{context}}/css/prettify.css" rel="stylesheet" type="text/css" />
|
||||||
|
{% if user-id %}
|
||||||
|
<link href="{{context}}/css/select2.css" rel="stylesheet" type="text/css" />
|
||||||
|
{% endif %}
|
||||||
|
<link href="{{context}}/css/screen.css" rel="stylesheet" type="text/css" />
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var context = "{{context}}";
|
||||||
|
</script>
|
||||||
|
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js" type="text/javascript"></script>
|
||||||
|
<script src="{{context}}/js/bootstrap.min.js" type="text/javascript"></script>
|
||||||
|
<script src="{{context}}/js/markdown.js" type="text/javascript"></script>
|
||||||
|
<script src="{{context}}/js/to-markdown.js" type="text/javascript"></script>
|
||||||
|
<script src="{{context}}/js/bootstrap-markdown.js" type="text/javascript"></script>
|
||||||
|
<script src="{{context}}/js/prettify.js" type="text/javascript"></script>
|
||||||
|
{% if user-id %}
|
||||||
|
<script src="{{context}}/js/select2.min.js" type="text/javascript"></script>
|
||||||
|
<script src="{{context}}/js/modals.js" type="text/javascript"></script>
|
||||||
|
{% endif %}
|
||||||
|
<script src="{{context}}/js/site.js" type="text/javascript"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="navbar">
|
||||||
|
<div class="navbar-inner">
|
||||||
|
<a class="brand" href="{{context}}/">blarg.ca</a>
|
||||||
|
<ul class="nav">
|
||||||
|
<li><a href="{{context}}/about">About</a></li>
|
||||||
|
<li><a href="{{context}}/archive">Archive</a></li>
|
||||||
|
</ul>
|
||||||
|
<ul class="nav pull-right">
|
||||||
|
{% if user-id %}
|
||||||
|
<li><a href="{{context}}/newpost"><i class="icon-pencil"></i> New Post</a></li>
|
||||||
|
<li class="dropdown">
|
||||||
|
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||||
|
<i class="icon-user"></i> {{user-id}}<span class="caret"></span>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="{{context}}/profile">Profile</a></li>
|
||||||
|
<li class="divider"></li>
|
||||||
|
<li><a href="{{context}}/logout">Sign Out</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="{{context}}/login">Login</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if breadcrumbs %}
|
||||||
|
{% include "blarg/views/templates/breadcrumbs.html" %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="content" role="main">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
9
src/blarg/views/templates/breadcrumbs.html
Normal file
9
src/blarg/views/templates/breadcrumbs.html
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<ul class="breadcrumb">
|
||||||
|
{% for crumb in breadcrumbs %}
|
||||||
|
{% if forloop.last %}
|
||||||
|
<li class="active">{{crumb.name}}</li>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="{{context}}{{crumb.url}}">{{crumb.name}}</a> <span class="divider">/</span></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
13
src/blarg/views/templates/error.html
Normal file
13
src/blarg/views/templates/error.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends "blarg/views/templates/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
|
<h2>Error!</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Sorry, an error has occurred.</p>
|
||||||
|
|
||||||
|
<pre>{{error-info}}</pre>
|
||||||
|
|
||||||
|
{% endblock %}
|
17
src/blarg/views/templates/posts/deletemodal.html
Normal file
17
src/blarg/views/templates/posts/deletemodal.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{% if user-id %}
|
||||||
|
|
||||||
|
<div id="deleteModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h3 id="deleteModalLabel">Delete Post</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Are you sure you want to delete this post?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||||
|
<button class="btn btn-danger" data-forpostid="" id="deleteModalOk">Delete Post</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
11
src/blarg/views/templates/posts/editpost.html
Normal file
11
src/blarg/views/templates/posts/editpost.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "blarg/views/templates/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
|
<h2>Edit Post</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include "blarg/views/templates/posts/postform.html" %}
|
||||||
|
|
||||||
|
{% endblock %}
|
24
src/blarg/views/templates/posts/list.html
Normal file
24
src/blarg/views/templates/posts/list.html
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{% extends "blarg/views/templates/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% for post in posts %}
|
||||||
|
{% include "blarg/views/templates/posts/post.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<ul class="pager">
|
||||||
|
{% if atlastpage %}
|
||||||
|
<li class="previous disabled"><a>← Older</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="previous"><a href="{{context}}/?page={{nextpage}}">← Older</a></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if atfirstpage %}
|
||||||
|
<li class="next disabled"><a>Newer →</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="next"><a href="{{context}}/?page={{prevpage}}">Newer →</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% include "blarg/views/templates/posts/publishmodal.html" %}
|
||||||
|
{% include "blarg/views/templates/posts/deletemodal.html" %}
|
||||||
|
{% endblock %}
|
11
src/blarg/views/templates/posts/newpost.html
Normal file
11
src/blarg/views/templates/posts/newpost.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "blarg/views/templates/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
|
<h2>New Post</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include "blarg/views/templates/posts/postform.html" %}
|
||||||
|
|
||||||
|
{% endblock %}
|
43
src/blarg/views/templates/posts/post.html
Normal file
43
src/blarg/views/templates/posts/post.html
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<div class="post{%if not inlist %} single-post{% endif %}{% if not post.published %} unpublished-post{% endif %}">
|
||||||
|
<div class="header">
|
||||||
|
{% if inlist %}
|
||||||
|
<a href="{{context}}{{post|post-url}}"><h2>{{post.title}}</h2></a>
|
||||||
|
{% else %}
|
||||||
|
<h2>{{post.title}}</h2>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
<div class="pull-left">
|
||||||
|
<span class="muted">Posted by {{post.user}}, <time title="{{post.created_at|to_fulltime}}">{{post.created_at|to_relative}}</time></span>
|
||||||
|
</div>
|
||||||
|
{% if user-id %}
|
||||||
|
<div class="pull-right">
|
||||||
|
<a class="btn btn-mini" href="{{context}}/editpost/{{post._id}}">Edit</a>
|
||||||
|
<a class="btn btn-mini btn-danger" data-deletepostid="{{post._id}}" href="#">Delete</a>
|
||||||
|
{% if post.published %}
|
||||||
|
<a class="btn btn-mini btn-warning" href="{{context}}/unpublish/{{post._id}}">Unpublish</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if not post.published %}
|
||||||
|
<a class="btn btn-mini btn-warning" data-publishpostid="{{post._id}}" href="#">Publish</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
{{post.body|md-to-html}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<div class="pull-left">
|
||||||
|
<small>{% for tag in post.tags %}<span class="label"><a href="{{context}}/tag/{{tag}}">{{tag}}</a></span> {% endfor %}</small>
|
||||||
|
</div>
|
||||||
|
{% if inlist %}
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="{{context}}{{post|post-url}}#comments">Comments</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
44
src/blarg/views/templates/posts/postform.html
Normal file
44
src/blarg/views/templates/posts/postform.html
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<form class="form-vertical" method="post">
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="title">Title</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-xxlarge" id="title" name="title" placeholder="Post Title" value="{{title}}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="tags">Tags</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-xxlarge" id="tags" name="tags" value="{{tags}}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="body">Post Body</label>
|
||||||
|
<div class="controls">
|
||||||
|
<textarea id="body" data-provide="markdown" name="body" rows="10">{{body}}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
<button type="button" class="btn">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
var allTags = {{all-tags|to-json}};
|
||||||
|
|
||||||
|
$("#body").markdown();
|
||||||
|
$("#tags").select2({tags:allTags});
|
||||||
|
$("#tags").on("change", function() { $("#tags_val").html($("#tags").val());});
|
||||||
|
|
||||||
|
var errors = {{validation-errors|to-json}};
|
||||||
|
for (var field in errors) {
|
||||||
|
var inputElement = $('#' + field);
|
||||||
|
var controlGroup = inputElement.closest('div.control-group');
|
||||||
|
|
||||||
|
controlGroup.addClass('error');
|
||||||
|
inputElement.after('<span class="help-block">' + errors[field] + '</span>');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
18
src/blarg/views/templates/posts/publishmodal.html
Normal file
18
src/blarg/views/templates/posts/publishmodal.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{% if user-id %}
|
||||||
|
|
||||||
|
<div id="publishModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="publishModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h3 id="publishModalLabel">Publish Post</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Would you also like to reset the post's date to the current date/time?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||||
|
<button class="btn btn-primary" data-forpostid="" id="publishModalOkNoReset">Publish Only</button>
|
||||||
|
<button class="btn btn-warning" data-forpostid="" id="publishModalOk">Publish and Reset Date</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
7
src/blarg/views/templates/posts/showpost.html
Normal file
7
src/blarg/views/templates/posts/showpost.html
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "blarg/views/templates/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% include "blarg/views/templates/posts/post.html" %}
|
||||||
|
{% include "blarg/views/templates/posts/publishmodal.html" %}
|
||||||
|
{% include "blarg/views/templates/posts/deletemodal.html" %}
|
||||||
|
{% endblock %}
|
29
src/blarg/views/viewfilters.clj
Normal file
29
src/blarg/views/viewfilters.clj
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
(ns blarg.views.viewfilters
|
||||||
|
(:use [blarg.datetime]
|
||||||
|
[blarg.routes.helpers])
|
||||||
|
(:require [clabango.filters :refer [deftemplatefilter]]
|
||||||
|
[markdown.core :as md]
|
||||||
|
[clj-time.core]
|
||||||
|
[clj-time.format]))
|
||||||
|
|
||||||
|
(deftemplatefilter "is_false" [node body arg]
|
||||||
|
(if body
|
||||||
|
false
|
||||||
|
true))
|
||||||
|
|
||||||
|
(deftemplatefilter "default" [node body arg]
|
||||||
|
(if body
|
||||||
|
body
|
||||||
|
arg))
|
||||||
|
|
||||||
|
(deftemplatefilter "md-to-html" [node body arg]
|
||||||
|
(md/md-to-html-string body))
|
||||||
|
|
||||||
|
(deftemplatefilter "post-url" [node body arg]
|
||||||
|
(get-post-url body))
|
||||||
|
|
||||||
|
(deftemplatefilter "to_relative" [node body arg]
|
||||||
|
(->relative-timestamp body))
|
||||||
|
|
||||||
|
(deftemplatefilter "to_fulltime" [node body arg]
|
||||||
|
(clj-time.local/format-local-time body :rfc822))
|
Reference in a new issue