initial commit

This commit is contained in:
Gered 2013-05-20 17:43:38 -04:00
commit 906f8ec915
54 changed files with 4783 additions and 0 deletions

31
.classpath Normal file
View 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
View file

@ -0,0 +1,11 @@
/target
/lib
/classes
/checkouts
pom.xml
*.jar
*.class
/.lein-*
/.env
/*.log
/.settings

29
.project Normal file
View 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
View file

@ -0,0 +1 @@
web: lein with-profile production trampoline ring server

19
README.md Normal file
View 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
View 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")

View 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
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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;
}

View 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;
}

View 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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

View 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

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View 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;
});
////////////////////////////////////////////////////////////////////////////
})

View 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

File diff suppressed because one or more lines are too long

View 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();
});

View 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
View file

@ -0,0 +1,2 @@
{:database
{:url "http://10.0.0.20:5984/"}}

21
src/blarg/config.clj Normal file
View 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
View 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
View 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
View 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)))

View 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}))))

View 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
View 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)

View 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
View 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!"))

View 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 " &raquo; " (clojure.string/join " &raquo; " coll)))

14
src/blarg/routes/home.clj Normal file
View 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
View 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
View 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))

View 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))))

View 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 %}

View 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 %}

View 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>

View 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>

View 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 %}

View 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">&times;</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 %}

View 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 %}

View 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>&larr; Older</a></li>
{% else %}
<li class="previous"><a href="{{context}}/?page={{nextpage}}">&larr; Older</a></li>
{% endif %}
{% if atfirstpage %}
<li class="next disabled"><a>Newer &rarr;</a></li>
{% else %}
<li class="next"><a href="{{context}}/?page={{prevpage}}">Newer &rarr;</a></li>
{% endif %}
</ul>
{% include "blarg/views/templates/posts/publishmodal.html" %}
{% include "blarg/views/templates/posts/deletemodal.html" %}
{% endblock %}

View 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 %}

View 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>

View 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>

View 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">&times;</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 %}

View 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 %}

View 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))