Editor half way through
This commit is contained in:
@@ -1,145 +1,41 @@
|
||||
package cethttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"embed"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/senaduka/cetvorke/database"
|
||||
)
|
||||
|
||||
//go:embed static/*
|
||||
var staticFiles embed.FS
|
||||
|
||||
func StartServer(waitgroup *sync.WaitGroup) {
|
||||
defer waitgroup.Done()
|
||||
|
||||
r := mux.NewRouter()
|
||||
|
||||
// Serve embedded static files
|
||||
staticFS, err := fs.Sub(staticFiles, "static")
|
||||
if err != nil {
|
||||
log.Fatal("error:", err)
|
||||
}
|
||||
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
|
||||
|
||||
r.HandleFunc("/p/{postID}/{slug}", postHandler)
|
||||
r.HandleFunc("/", indexHandler)
|
||||
r.HandleFunc("/a/", archiveHandler)
|
||||
r.HandleFunc("/y/{year}/", yearHandler)
|
||||
r.HandleFunc("/y/{year}/m/{month}/", monthHandler)
|
||||
r.HandleFunc("/editor/e/{postID}", editHandler)
|
||||
r.HandleFunc("/editor/n/", newHandler)
|
||||
r.HandeeFunc("/editor", editorHandler)
|
||||
|
||||
err := http.ListenAndServe(":8018", r)
|
||||
err = http.ListenAndServe(":8018", r)
|
||||
if err != nil {
|
||||
log.Fatal("error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
links, err := database.GetRecentLinks()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := "<h1>IslamBosna</h1>\n<h2>Najnoviji članci</h2>\n"
|
||||
|
||||
for _, link := range links {
|
||||
response += "<p>" + link.HTMLLink() + "</p>\n"
|
||||
}
|
||||
|
||||
response = html5Page(response)
|
||||
|
||||
w.Write([]byte(response))
|
||||
}
|
||||
|
||||
func postHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
fmt.Println(vars)
|
||||
postID, err := strconv.Atoi(vars["postID"])
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
post, err := database.GetPost(postID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := post.HTMLPage()
|
||||
|
||||
response = html5Page(response)
|
||||
|
||||
w.Write([]byte(response))
|
||||
}
|
||||
|
||||
func archiveHandler(w http.ResponseWriter, r *http.Request) {
|
||||
links, title, err := database.GetAllYearLinks()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := "<h1>" + title + "</h1>\n"
|
||||
|
||||
for _, link := range links {
|
||||
response += "<p>" + link.HTMLArchiveLink() + "</p>\n"
|
||||
}
|
||||
|
||||
response = html5Page(response)
|
||||
|
||||
w.Write([]byte(response))
|
||||
}
|
||||
|
||||
func yearHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
year, err := strconv.Atoi(vars["year"])
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
links, title, err := database.GetMonthLinksByYear(year)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := "<h1>" + title + "</h1>\n"
|
||||
|
||||
for _, link := range links {
|
||||
response += "<p>" + link.HTMLArchiveMonthLink() + "</p>\n"
|
||||
}
|
||||
|
||||
response = html5Page(response)
|
||||
|
||||
w.Write([]byte(response))
|
||||
}
|
||||
|
||||
func monthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
year, err := strconv.Atoi(vars["year"])
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
month, err := strconv.Atoi(vars["month"])
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
links, title, err := database.GetLinksByMonth(year, month)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := "<h1>" + title + "</h1>\n"
|
||||
|
||||
for _, link := range links {
|
||||
response += "<p>" + link.HTMLLink() + "</p>\n"
|
||||
}
|
||||
|
||||
response = html5Page(response)
|
||||
w.Write([]byte(response))
|
||||
}
|
||||
|
||||
16
cethttp/editor.go
Normal file
16
cethttp/editor.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package cethttp
|
||||
|
||||
const editorPage = `
|
||||
<form action="/save" method="POST">
|
||||
<input type="text" name="title" placeholder="Naslov" />
|
||||
<input type="hidden" value="{{.PostID}}" />
|
||||
<div id="editor"></div>
|
||||
<script>
|
||||
const editor = new Editor({
|
||||
el: document.querySelector('#editor'),
|
||||
height: '500px',
|
||||
initialEditType: 'wysiwyg',
|
||||
previewStyle: 'tab'
|
||||
});
|
||||
</script>
|
||||
`
|
||||
1
cethttp/editor_handlers.go
Normal file
1
cethttp/editor_handlers.go
Normal file
@@ -0,0 +1 @@
|
||||
package cethttp
|
||||
126
cethttp/reader_handlers.go
Normal file
126
cethttp/reader_handlers.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package cethttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/senaduka/cetvorke/database"
|
||||
)
|
||||
|
||||
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
links, err := database.GetRecentLinks()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := "<h1>IslamBosna</h1>\n<h2>Najnoviji članci</h2>\n"
|
||||
|
||||
for _, link := range links {
|
||||
response += "<p>" + link.HTMLLink() + "</p>\n"
|
||||
}
|
||||
|
||||
response = html5Page(response)
|
||||
|
||||
w.Write([]byte(response))
|
||||
}
|
||||
|
||||
func postHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
fmt.Println(vars)
|
||||
postID, err := strconv.Atoi(vars["postID"])
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
post, err := database.GetPost(postID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := post.HTMLPage()
|
||||
|
||||
response = html5Page(response)
|
||||
|
||||
w.Write([]byte(response))
|
||||
}
|
||||
|
||||
func archiveHandler(w http.ResponseWriter, r *http.Request) {
|
||||
links, title, err := database.GetAllYearLinks()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := "<h1>" + title + "</h1>\n"
|
||||
|
||||
for _, link := range links {
|
||||
response += "<p>" + link.HTMLArchiveLink() + "</p>\n"
|
||||
}
|
||||
|
||||
response = html5Page(response)
|
||||
|
||||
w.Write([]byte(response))
|
||||
}
|
||||
|
||||
func yearHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
year, err := strconv.Atoi(vars["year"])
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
links, title, err := database.GetMonthLinksByYear(year)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := "<h1>" + title + "</h1>\n"
|
||||
|
||||
for _, link := range links {
|
||||
response += "<p>" + link.HTMLArchiveMonthLink() + "</p>\n"
|
||||
}
|
||||
|
||||
response = html5Page(response)
|
||||
|
||||
w.Write([]byte(response))
|
||||
}
|
||||
|
||||
func monthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
year, err := strconv.Atoi(vars["year"])
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
month, err := strconv.Atoi(vars["month"])
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
links, title, err := database.GetLinksByMonth(year, month)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := "<h1>" + title + "</h1>\n"
|
||||
|
||||
for _, link := range links {
|
||||
response += "<p>" + link.HTMLLink() + "</p>\n"
|
||||
}
|
||||
|
||||
response = html5Page(response)
|
||||
w.Write([]byte(response))
|
||||
}
|
||||
78
cethttp/static/markdown.js
Normal file
78
cethttp/static/markdown.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/*! Licensed under MIT, https://github.com/sofish/pen */
|
||||
(function(root) {
|
||||
|
||||
// only works with Pen
|
||||
if(!root.Pen) return;
|
||||
|
||||
// markdown covertor obj
|
||||
var covertor = {
|
||||
keymap: { '96': '`', '62': '>', '49': '1', '46': '.', '45': '-', '42': '*', '35': '#'},
|
||||
stack : []
|
||||
};
|
||||
|
||||
// return valid markdown syntax
|
||||
covertor.valid = function(str) {
|
||||
var len = str.length;
|
||||
|
||||
if(str.match(/[#]{1,6}/)) {
|
||||
return ['h' + len, len];
|
||||
} else if(str === '```') {
|
||||
return ['pre', len];
|
||||
} else if(str === '>') {
|
||||
return ['blockquote', len];
|
||||
} else if(str === '1.') {
|
||||
return ['insertorderedlist', len];
|
||||
} else if(str === '-' || str === '*') {
|
||||
return ['insertunorderedlist', len];
|
||||
} else if(str.match(/(?:\.|\*|\-){3,}/)) {
|
||||
return ['inserthorizontalrule', len];
|
||||
}
|
||||
};
|
||||
|
||||
// parse command
|
||||
covertor.parse = function(e) {
|
||||
var code = e.keyCode || e.which;
|
||||
|
||||
// when `space` is pressed
|
||||
if (code === 32) {
|
||||
var markdownSyntax = this.stack.join('');
|
||||
// reset stack
|
||||
this.stack = [];
|
||||
|
||||
var cmd = this.valid(markdownSyntax);
|
||||
if (cmd) {
|
||||
// prevents leading space after executing command
|
||||
e.preventDefault();
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
|
||||
// make cmd
|
||||
if(this.keymap[code]) this.stack.push(this.keymap[code]);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// exec command
|
||||
covertor.action = function(pen, cmd) {
|
||||
|
||||
// only apply effect at line start
|
||||
if(pen.selection.focusOffset > cmd[1]) return;
|
||||
|
||||
var node = pen.selection.focusNode;
|
||||
node.textContent = node.textContent.slice(cmd[1]);
|
||||
pen.execCommand(cmd[0]);
|
||||
};
|
||||
|
||||
// init covertor
|
||||
covertor.init = function(pen) {
|
||||
pen.on('keypress', function(e) {
|
||||
var cmd = covertor.parse(e);
|
||||
if(cmd) return covertor.action(pen, cmd);
|
||||
});
|
||||
};
|
||||
|
||||
// append to Pen
|
||||
root.Pen.prototype.markdown = covertor;
|
||||
|
||||
}(window));
|
||||
175
cethttp/static/pen.css
Normal file
175
cethttp/static/pen.css
Normal file
@@ -0,0 +1,175 @@
|
||||
/*! Licensed under MIT, https://github.com/sofish/pen */
|
||||
|
||||
/* basic reset */
|
||||
.pen, .pen-menu, .pen-input, .pen textarea{font:400 1.16em/1.45 Palatino, Optima, Georgia, serif;color:#331;}
|
||||
.pen:focus{outline:none;}
|
||||
.pen fieldset, img {border: 0;}
|
||||
.pen blockquote{padding-left:10px;margin-left:-14px;border-left:4px solid #1abf89;}
|
||||
.pen a{color:#1abf89;}
|
||||
.pen del{text-decoration:line-through;}
|
||||
.pen sub, .pen sup {font-size:75%;position:relative;vertical-align:text-top;}
|
||||
:root .pen sub, :root .pen sup{vertical-align:baseline; /* for ie9 and other mordern browsers */}
|
||||
.pen sup {top:-0.5em;}
|
||||
.pen sub {bottom:-0.25em;}
|
||||
.pen hr{border:none;border-bottom:1px solid #cfcfcf;margin-bottom:25px;*color:pink;*filter:chroma(color=pink);height:10px;*margin:-7px 0 15px;}
|
||||
.pen small{font-size:0.8em;color:#888;}
|
||||
.pen em, .pen b, .pen strong{font-weight:700;}
|
||||
.pen pre{white-space:pre-wrap;padding:0.85em;background:#f8f8f8;}
|
||||
|
||||
/* block-level element margin */
|
||||
.pen p, .pen pre, .pen ul, .pen ol, .pen dl, .pen form, .pen table, .pen blockquote{margin-bottom:16px;}
|
||||
|
||||
/* headers */
|
||||
.pen h1, .pen h2, .pen h3, .pen h4, .pen h5, .pen h6{margin-bottom:16px;font-weight:700;line-height:1.2;}
|
||||
.pen h1{font-size:2em;}
|
||||
.pen h2{font-size:1.8em;}
|
||||
.pen h3{font-size:1.6em;}
|
||||
.pen h4{font-size:1.4em;}
|
||||
.pen h5, .pen h6{font-size:1.2em;}
|
||||
|
||||
/* list */
|
||||
.pen ul, .pen ol{margin-left:1.2em;}
|
||||
.pen ul, .pen-ul{list-style:disc;}
|
||||
.pen ol, .pen-ol{list-style:decimal;}
|
||||
.pen li ul, .pen li ol, .pen-ul ul, .pen-ul ol, .pen-ol ul, .pen-ol ol{margin:0 2em 0 1.2em;}
|
||||
.pen li ul, .pen-ul ul, .pen-ol ul{list-style: circle;}
|
||||
|
||||
/* pen menu */
|
||||
.pen-menu [class^="icon-"], .pen-menu [class*=" icon-"] { /* reset to avoid conflicts with Bootstrap */
|
||||
background: transparent;
|
||||
background-image: none;
|
||||
}
|
||||
.pen-menu { min-width: 320px; }
|
||||
.pen-menu, .pen-input{font-size:14px;line-height:1;}
|
||||
.pen-menu{white-space:nowrap;box-shadow:1px 2px 3px -2px #222;background:#333;background-image:linear-gradient(to bottom, #222, #333);opacity:0.9;position:fixed;height:36px;border:1px solid #333;border-radius:3px;display:none;z-index:1000;}
|
||||
.pen-menu:after {top:100%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none;}
|
||||
.pen-menu:after {border-color:rgba(51, 51, 51, 0);border-top-color:#333;border-width:6px;left:50%;margin-left:-6px;}
|
||||
.pen-menu-below:after {top: -11px; display:block; -moz-transform: rotate(180deg); -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); -o-transform: rotate(180deg); transform: rotate(180deg);}
|
||||
.pen-icon{font:normal 900 16px/40px Georgia serif;min-width:20px;display:inline-block;padding:0 10px;height:36px;overflow:hidden;color:#fff;text-align:center;cursor:pointer;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;}
|
||||
.pen-icon:first-of-type{border-top-left-radius:3px;border-bottom-left-radius:3px;}
|
||||
.pen-icon:last-of-type{border-top-right-radius:3px;border-bottom-right-radius:3px;}
|
||||
.pen-icon:hover{background:#000;}
|
||||
.pen-icon.active{color:#1abf89;background:#000;box-shadow:inset 2px 2px 4px #000;}
|
||||
.pen-input{position:absolute;width:100%;left:0;top:0;height:36px;line-height:20px;background:#333;color:#fff;border:none;text-align:center;display:none;font-family:arial, sans-serif;}
|
||||
.pen-input:focus{outline:none;}
|
||||
|
||||
.pen-textarea{display:block;background:#f8f8f8;padding:20px;}
|
||||
.pen textarea{font-size:14px;border:none;background:none;width:100%;_height:200px;min-height:200px;resize:none;}
|
||||
|
||||
@font-face {
|
||||
font-family: 'pen';
|
||||
src: url('font/fontello.eot?370dad08');
|
||||
src: url('font/fontello.eot?370dad08#iefix') format('embedded-opentype'),
|
||||
url('font/fontello.woff?370dad08') format('woff'),
|
||||
url('font/fontello.ttf?370dad08') format('truetype'),
|
||||
url('font/fontello.svg?370dad08#fontello') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.pen-menu [class^="icon-"]:before, .pen-menu [class*=" icon-"]:before {
|
||||
font-family: "pen";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
speak: none;
|
||||
display: inline-block;
|
||||
text-decoration: inherit;
|
||||
width: 1em;
|
||||
margin-right: .2em;
|
||||
text-align: center;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1em;
|
||||
margin-left: .2em;
|
||||
}
|
||||
.pen-menu .icon-location:before { content: '\e815'; } /* '' */
|
||||
.pen-menu .icon-fit:before { content: '\e80f'; } /* '' */
|
||||
.pen-menu .icon-bold:before { content: '\e805'; } /* '' */
|
||||
.pen-menu .icon-italic:before { content: '\e806'; } /* '' */
|
||||
.pen-menu .icon-justifyleft:before { content: '\e80a'; } /* '' */
|
||||
.pen-menu .icon-justifycenter:before { content: '\e80b'; } /* '' */
|
||||
.pen-menu .icon-justifyright:before { content: '\e80c'; } /* '' */
|
||||
.pen-menu .icon-justifyfull:before { content: '\e80d'; } /* '' */
|
||||
.pen-menu .icon-outdent:before { content: '\e800'; } /* '' */
|
||||
.pen-menu .icon-indent:before { content: '\e801'; } /* '' */
|
||||
.pen-menu .icon-mode:before { content: '\e813'; } /* '' */
|
||||
.pen-menu .icon-fullscreen:before { content: '\e80e'; } /* '' */
|
||||
.pen-menu .icon-insertunorderedlist:before { content: '\e802'; } /* '' */
|
||||
.pen-menu .icon-insertorderedlist:before { content: '\e803'; } /* '' */
|
||||
.pen-menu .icon-strikethrough:before { content: '\e807'; } /* '' */
|
||||
.pen-menu .icon-underline:before { content: '\e804'; } /* '' */
|
||||
.pen-menu .icon-blockquote:before { content: '\e814'; } /* '' */
|
||||
.pen-menu .icon-undo:before { content: '\e817'; } /* '' */
|
||||
.pen-menu .icon-code:before { content: '\e816'; } /* '' */
|
||||
.pen-menu .icon-pre:before { content: '\e816'; } /* '' */
|
||||
.pen-menu .icon-unlink:before { content: '\e811'; } /* '' */
|
||||
.pen-menu .icon-superscript:before { content: '\e808'; } /* '' */
|
||||
.pen-menu .icon-subscript:before { content: '\e809'; } /* '' */
|
||||
.pen-menu .icon-inserthorizontalrule:before { content: '\e818'; } /* '' */
|
||||
.pen-menu .icon-pin:before { content: '\e812'; } /* '' */
|
||||
.pen-menu .icon-createlink:before { content: '\e810'; } /* '' */
|
||||
.pen-menu .icon-h1:before { content: 'H1'; }
|
||||
.pen-menu .icon-h2:before { content: 'H2'; }
|
||||
.pen-menu .icon-h3:before { content: 'H3'; }
|
||||
.pen-menu .icon-h4:before { content: 'H4'; }
|
||||
.pen-menu .icon-h5:before { content: 'H5'; }
|
||||
.pen-menu .icon-h6:before { content: 'H6'; }
|
||||
.pen-menu .icon-p:before { content: 'P'; }
|
||||
.pen-menu .icon-insertimage:before { width:1.8em;margin:0;position:relative;top:-2px;content:'IMG';font-size:12px;border:1px solid #fff;padding:2px;border-radius:2px; }
|
||||
.pen {
|
||||
position: relative;
|
||||
}
|
||||
.pen.hinted h1:before,
|
||||
.pen.hinted h2:before,
|
||||
.pen.hinted h3:before,
|
||||
.pen.hinted h4:before,
|
||||
.pen.hinted h5:before,
|
||||
.pen.hinted h6:before,
|
||||
.pen.hinted blockquote:before,
|
||||
.pen.hinted hr:before {
|
||||
color: #eee;
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
white-space: nowrap;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.pen.hinted blockquote { border-left: 0; margin-left: 0; padding-left: 0; }
|
||||
.pen.hinted blockquote:before {
|
||||
color: #1abf89;
|
||||
content: ">";
|
||||
font-weight: bold;
|
||||
vertical-align: center;
|
||||
}
|
||||
.pen.hinted h1:before { content: "#";}
|
||||
.pen.hinted h2:before { content: "##";}
|
||||
.pen.hinted h3:before { content: "###";}
|
||||
.pen.hinted h4:before { content: "####";}
|
||||
.pen.hinted h5:before { content: "#####";}
|
||||
.pen.hinted h6:before { content: "######";}
|
||||
.pen.hinted hr:before { content: "﹘﹘﹘"; line-height: 1.2; vertical-align: bottom; }
|
||||
|
||||
.pen.hinted pre:before, .pen.hinted pre:after {
|
||||
content: "```";
|
||||
display: block;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.pen.hinted ul { list-style: none; }
|
||||
.pen.hinted ul li:before {
|
||||
content: "*";
|
||||
color: #999;
|
||||
line-height: 1;
|
||||
vertical-align: bottom;
|
||||
margin-left: -1.2em;
|
||||
display: inline-block;
|
||||
width: 1.2em;
|
||||
}
|
||||
|
||||
.pen.hinted b:before, .pen.hinted b:after { content: "**"; color: #eee; font-weight: normal; }
|
||||
.pen.hinted i:before, .pen.hinted i:after { content: "*"; color: #eee; }
|
||||
|
||||
.pen.hinted a { text-decoration: none; }
|
||||
.pen.hinted a:before {content: "["; color: #ddd; }
|
||||
.pen.hinted a:after { content: "](" attr(href) ")"; color: #ddd; }
|
||||
|
||||
.pen-placeholder:after { position: absolute; top: 0; left: 0; content: attr(data-placeholder); color: #999; cursor: text; }
|
||||
856
cethttp/static/pen.js
Normal file
856
cethttp/static/pen.js
Normal file
@@ -0,0 +1,856 @@
|
||||
/*! Licensed under MIT, https://github.com/sofish/pen */
|
||||
(function(root, doc) {
|
||||
|
||||
var Pen, debugMode, selection, utils = {};
|
||||
var toString = Object.prototype.toString;
|
||||
var slice = Array.prototype.slice;
|
||||
|
||||
// allow command list
|
||||
var commandsReg = {
|
||||
block: /^(?:p|h[1-6]|blockquote|pre)$/,
|
||||
inline: /^(?:bold|italic|underline|insertorderedlist|insertunorderedlist|indent|outdent)$/,
|
||||
source: /^(?:createlink|unlink)$/,
|
||||
insert: /^(?:inserthorizontalrule|insertimage|insert)$/,
|
||||
wrap: /^(?:code)$/
|
||||
};
|
||||
|
||||
var lineBreakReg = /^(?:blockquote|pre|div)$/i;
|
||||
|
||||
var effectNodeReg = /(?:[pubia]|h[1-6]|blockquote|[uo]l|li)/i;
|
||||
|
||||
var strReg = {
|
||||
whiteSpace: /(^\s+)|(\s+$)/g,
|
||||
mailTo: /^(?!mailto:|.+\/|.+#|.+\?)(.*@.*\..+)$/,
|
||||
http: /^(?!\w+?:\/\/|mailto:|\/|\.\/|\?|#)(.*)$/
|
||||
};
|
||||
|
||||
var autoLinkReg = {
|
||||
url: /((https?|ftp):\/\/|www\.)[^\s<]{3,}/gi,
|
||||
prefix: /^(?:https?|ftp):\/\//i,
|
||||
notLink: /^(?:img|a|input|audio|video|source|code|pre|script|head|title|style)$/i,
|
||||
maxLength: 100
|
||||
};
|
||||
|
||||
// type detect
|
||||
utils.is = function(obj, type) {
|
||||
return toString.call(obj).slice(8, -1) === type;
|
||||
};
|
||||
|
||||
utils.forEach = function(obj, iterator, arrayLike) {
|
||||
if (!obj) return;
|
||||
if (arrayLike == null) arrayLike = utils.is(obj, 'Array');
|
||||
if (arrayLike) {
|
||||
for (var i = 0, l = obj.length; i < l; i++) iterator(obj[i], i, obj);
|
||||
} else {
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) iterator(obj[key], key, obj);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// copy props from a obj
|
||||
utils.copy = function(defaults, source) {
|
||||
utils.forEach(source, function (value, key) {
|
||||
defaults[key] = utils.is(value, 'Object') ? utils.copy({}, value) :
|
||||
utils.is(value, 'Array') ? utils.copy([], value) : value;
|
||||
});
|
||||
return defaults;
|
||||
};
|
||||
|
||||
// log
|
||||
utils.log = function(message, force) {
|
||||
if (debugMode || force)
|
||||
console.log('%cPEN DEBUGGER: %c' + message, 'font-family:arial,sans-serif;color:#1abf89;line-height:2em;', 'font-family:cursor,monospace;color:#333;');
|
||||
};
|
||||
|
||||
utils.delayExec = function (fn) {
|
||||
var timer = null;
|
||||
return function (delay) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function() {
|
||||
fn();
|
||||
}, delay || 1);
|
||||
};
|
||||
};
|
||||
|
||||
// merge: make it easy to have a fallback
|
||||
utils.merge = function(config) {
|
||||
|
||||
// default settings
|
||||
var defaults = {
|
||||
class: 'pen',
|
||||
debug: false,
|
||||
toolbar: null, // custom toolbar
|
||||
stay: config.stay || !config.debug,
|
||||
stayMsg: 'Are you going to leave here?',
|
||||
textarea: '<textarea name="content"></textarea>',
|
||||
list: [
|
||||
'blockquote', 'h2', 'h3', 'p', 'code', 'insertorderedlist', 'insertunorderedlist', 'inserthorizontalrule',
|
||||
'indent', 'outdent', 'bold', 'italic', 'underline', 'createlink', 'insertimage'
|
||||
],
|
||||
titles: {},
|
||||
cleanAttrs: ['id', 'class', 'style', 'name'],
|
||||
cleanTags: ['script'],
|
||||
linksInNewWindow: false
|
||||
};
|
||||
|
||||
// user-friendly config
|
||||
if (config.nodeType === 1) {
|
||||
defaults.editor = config;
|
||||
} else if (config.match && config.match(/^#[\S]+$/)) {
|
||||
defaults.editor = doc.getElementById(config.slice(1));
|
||||
} else {
|
||||
defaults = utils.copy(defaults, config);
|
||||
}
|
||||
|
||||
return defaults;
|
||||
};
|
||||
|
||||
function commandOverall(ctx, cmd, val) {
|
||||
var message = ' to exec 「' + cmd + '」 command' + (val ? (' with value: ' + val) : '');
|
||||
|
||||
try {
|
||||
doc.execCommand(cmd, false, val);
|
||||
} catch(err) {
|
||||
// TODO: there's an error when insert a image to document, but not a bug
|
||||
return utils.log('fail' + message, true);
|
||||
}
|
||||
|
||||
utils.log('success' + message);
|
||||
}
|
||||
|
||||
function commandInsert(ctx, name, val) {
|
||||
var node = getNode(ctx);
|
||||
if (!node) return;
|
||||
ctx._range.selectNode(node);
|
||||
ctx._range.collapse(false);
|
||||
|
||||
// hide menu when a image was inserted
|
||||
if(name === 'insertimage' && ctx._menu) toggleNode(ctx._menu, true);
|
||||
|
||||
return commandOverall(ctx, name, val);
|
||||
}
|
||||
|
||||
function commandBlock(ctx, name) {
|
||||
var list = effectNode(ctx, getNode(ctx), true);
|
||||
if (list.indexOf(name) !== -1) name = 'p';
|
||||
return commandOverall(ctx, 'formatblock', name);
|
||||
}
|
||||
|
||||
function commandWrap(ctx, tag, value) {
|
||||
value = '<' + tag + '>' + (value||selection.toString()) + '</' + tag + '>';
|
||||
return commandOverall(ctx, 'insertHTML', value);
|
||||
}
|
||||
|
||||
function commandLink(ctx, tag, value) {
|
||||
if (ctx.config.linksInNewWindow) {
|
||||
value = '< a href="' + value + '" target="_blank">' + (selection.toString()) + '</a>';
|
||||
return commandOverall(ctx, 'insertHTML', value);
|
||||
} else {
|
||||
return commandOverall(ctx, tag, value);
|
||||
}
|
||||
}
|
||||
|
||||
function initToolbar(ctx) {
|
||||
var icons = '', inputStr = '<input class="pen-input" placeholder="http://" />';
|
||||
|
||||
ctx._toolbar = ctx.config.toolbar;
|
||||
if (!ctx._toolbar) {
|
||||
var toolList = ctx.config.list;
|
||||
utils.forEach(toolList, function (name) {
|
||||
var klass = 'pen-icon icon-' + name;
|
||||
var title = ctx.config.titles[name] || '';
|
||||
icons += '<i class="' + klass + '" data-action="' + name + '" title="' + title + '"></i>';
|
||||
}, true);
|
||||
if (toolList.indexOf('createlink') >= 0 || toolList.indexOf('insertimage') >= 0)
|
||||
icons += inputStr;
|
||||
} else if (ctx._toolbar.querySelectorAll('[data-action=createlink]').length ||
|
||||
ctx._toolbar.querySelectorAll('[data-action=insertimage]').length) {
|
||||
icons += inputStr;
|
||||
}
|
||||
|
||||
if (icons) {
|
||||
ctx._menu = doc.createElement('div');
|
||||
ctx._menu.setAttribute('class', ctx.config.class + '-menu pen-menu');
|
||||
ctx._menu.innerHTML = icons;
|
||||
ctx._inputBar = ctx._menu.querySelector('input');
|
||||
toggleNode(ctx._menu, true);
|
||||
doc.body.appendChild(ctx._menu);
|
||||
}
|
||||
if (ctx._toolbar && ctx._inputBar) toggleNode(ctx._inputBar);
|
||||
}
|
||||
|
||||
function initEvents(ctx) {
|
||||
var toolbar = ctx._toolbar || ctx._menu, editor = ctx.config.editor;
|
||||
|
||||
var toggleMenu = utils.delayExec(function() {
|
||||
ctx.highlight().menu();
|
||||
});
|
||||
var outsideClick = function() {};
|
||||
|
||||
function updateStatus(delay) {
|
||||
ctx._range = ctx.getRange();
|
||||
toggleMenu(delay);
|
||||
}
|
||||
|
||||
if (ctx._menu) {
|
||||
var setpos = function() {
|
||||
if (ctx._menu.style.display === 'block') ctx.menu();
|
||||
};
|
||||
|
||||
// change menu offset when window resize / scroll
|
||||
addListener(ctx, root, 'resize', setpos);
|
||||
addListener(ctx, root, 'scroll', setpos);
|
||||
|
||||
// toggle toolbar on mouse select
|
||||
var selecting = false;
|
||||
addListener(ctx, editor, 'mousedown', function() {
|
||||
selecting = true;
|
||||
});
|
||||
addListener(ctx, editor, 'mouseleave', function() {
|
||||
if (selecting) updateStatus(800);
|
||||
selecting = false;
|
||||
});
|
||||
addListener(ctx, editor, 'mouseup', function() {
|
||||
if (selecting) updateStatus(100);
|
||||
selecting = false;
|
||||
});
|
||||
// Hide menu when focusing outside of editor
|
||||
outsideClick = function(e) {
|
||||
if (ctx._menu && !containsNode(editor, e.target) && !containsNode(ctx._menu, e.target)) {
|
||||
removeListener(ctx, doc, 'click', outsideClick);
|
||||
toggleMenu(100);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
addListener(ctx, editor, 'click', function() {
|
||||
updateStatus(0);
|
||||
});
|
||||
}
|
||||
|
||||
addListener(ctx, editor, 'keyup', function(e) {
|
||||
if (e.which === 8 && ctx.isEmpty()) return lineBreak(ctx, true);
|
||||
// toggle toolbar on key select
|
||||
if (e.which !== 13 || e.shiftKey) return updateStatus(400);
|
||||
var node = getNode(ctx, true);
|
||||
if (!node || !node.nextSibling || !lineBreakReg.test(node.nodeName)) return;
|
||||
if (node.nodeName !== node.nextSibling.nodeName) return;
|
||||
// hack for webkit, make 'enter' behavior like as firefox.
|
||||
if (node.lastChild.nodeName !== 'BR') node.appendChild(doc.createElement('br'));
|
||||
utils.forEach(node.nextSibling.childNodes, function(child) {
|
||||
if (child) node.appendChild(child);
|
||||
}, true);
|
||||
node.parentNode.removeChild(node.nextSibling);
|
||||
focusNode(ctx, node.lastChild, ctx.getRange());
|
||||
});
|
||||
|
||||
// check line break
|
||||
addListener(ctx, editor, 'keydown', function(e) {
|
||||
editor.classList.remove('pen-placeholder');
|
||||
if (e.which !== 13 || e.shiftKey) return;
|
||||
var node = getNode(ctx, true);
|
||||
if (!node || !lineBreakReg.test(node.nodeName)) return;
|
||||
var lastChild = node.lastChild;
|
||||
if (!lastChild || !lastChild.previousSibling) return;
|
||||
if (lastChild.previousSibling.textContent || lastChild.textContent) return;
|
||||
// quit block mode for 2 'enter'
|
||||
e.preventDefault();
|
||||
var p = doc.createElement('p');
|
||||
p.innerHTML = '<br>';
|
||||
node.removeChild(lastChild);
|
||||
if (!node.nextSibling) node.parentNode.appendChild(p);
|
||||
else node.parentNode.insertBefore(p, node.nextSibling);
|
||||
focusNode(ctx, p, ctx.getRange());
|
||||
});
|
||||
|
||||
var menuApply = function(action, value) {
|
||||
ctx.execCommand(action, value);
|
||||
ctx._range = ctx.getRange();
|
||||
ctx.highlight().menu();
|
||||
};
|
||||
|
||||
// toggle toolbar on key select
|
||||
addListener(ctx, toolbar, 'click', function(e) {
|
||||
var node = e.target, action;
|
||||
|
||||
while (node !== toolbar && !(action = node.getAttribute('data-action'))) {
|
||||
node = node.parentNode;
|
||||
}
|
||||
|
||||
if (!action) return;
|
||||
if (!/(?:createlink)|(?:insertimage)/.test(action)) return menuApply(action);
|
||||
if (!ctx._inputBar) return;
|
||||
|
||||
// create link
|
||||
var input = ctx._inputBar;
|
||||
if (toolbar === ctx._menu) toggleNode(input);
|
||||
else {
|
||||
ctx._inputActive = true;
|
||||
ctx.menu();
|
||||
}
|
||||
if (ctx._menu.style.display === 'none') return;
|
||||
|
||||
setTimeout(function() { input.focus(); }, 400);
|
||||
var createlink = function() {
|
||||
var inputValue = input.value;
|
||||
|
||||
if (!inputValue) action = 'unlink';
|
||||
else {
|
||||
inputValue = input.value
|
||||
.replace(strReg.whiteSpace, '')
|
||||
.replace(strReg.mailTo, 'mailto:$1')
|
||||
.replace(strReg.http, 'http://$1');
|
||||
}
|
||||
menuApply(action, inputValue);
|
||||
if (toolbar === ctx._menu) toggleNode(input, false);
|
||||
else toggleNode(ctx._menu, true);
|
||||
};
|
||||
|
||||
input.onkeypress = function(e) {
|
||||
if (e.which === 13) return createlink();
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
// listen for placeholder
|
||||
addListener(ctx, editor, 'focus', function() {
|
||||
if (ctx.isEmpty()) lineBreak(ctx, true);
|
||||
addListener(ctx, doc, 'click', outsideClick);
|
||||
});
|
||||
|
||||
addListener(ctx, editor, 'blur', function() {
|
||||
checkPlaceholder(ctx);
|
||||
ctx.checkContentChange();
|
||||
});
|
||||
|
||||
// listen for paste and clear style
|
||||
addListener(ctx, editor, 'paste', function() {
|
||||
setTimeout(function() {
|
||||
ctx.cleanContent();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addListener(ctx, target, type, listener) {
|
||||
if (ctx._events.hasOwnProperty(type)) {
|
||||
ctx._events[type].push(listener);
|
||||
} else {
|
||||
ctx._eventTargets = ctx._eventTargets || [];
|
||||
ctx._eventsCache = ctx._eventsCache || [];
|
||||
var index = ctx._eventTargets.indexOf(target);
|
||||
if (index < 0) index = ctx._eventTargets.push(target) - 1;
|
||||
ctx._eventsCache[index] = ctx._eventsCache[index] || {};
|
||||
ctx._eventsCache[index][type] = ctx._eventsCache[index][type] || [];
|
||||
ctx._eventsCache[index][type].push(listener);
|
||||
|
||||
target.addEventListener(type, listener, false);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// trigger local events
|
||||
function triggerListener(ctx, type) {
|
||||
if (!ctx._events.hasOwnProperty(type)) return;
|
||||
var args = slice.call(arguments, 2);
|
||||
utils.forEach(ctx._events[type], function (listener) {
|
||||
listener.apply(ctx, args);
|
||||
});
|
||||
}
|
||||
|
||||
function removeListener(ctx, target, type, listener) {
|
||||
var events = ctx._events[type];
|
||||
if (!events) {
|
||||
var _index = ctx._eventTargets.indexOf(target);
|
||||
if (_index >= 0) events = ctx._eventsCache[_index][type];
|
||||
}
|
||||
if (!events) return ctx;
|
||||
var index = events.indexOf(listener);
|
||||
if (index >= 0) events.splice(index, 1);
|
||||
target.removeEventListener(type, listener, false);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
function removeAllListeners(ctx) {
|
||||
utils.forEach(this._events, function (events) {
|
||||
events.length = 0;
|
||||
}, false);
|
||||
if (!ctx._eventsCache) return ctx;
|
||||
utils.forEach(ctx._eventsCache, function (events, index) {
|
||||
var target = ctx._eventTargets[index];
|
||||
utils.forEach(events, function (listeners, type) {
|
||||
utils.forEach(listeners, function (listener) {
|
||||
target.removeEventListener(type, listener, false);
|
||||
}, true);
|
||||
}, false);
|
||||
}, true);
|
||||
ctx._eventTargets = [];
|
||||
ctx._eventsCache = [];
|
||||
return ctx;
|
||||
}
|
||||
|
||||
function checkPlaceholder(ctx) {
|
||||
ctx.config.editor.classList[ctx.isEmpty() ? 'add' : 'remove']('pen-placeholder');
|
||||
}
|
||||
|
||||
function trim(str) {
|
||||
return (str || '').replace(/^\s+|\s+$/g, '');
|
||||
}
|
||||
|
||||
// node.contains is not implemented in IE10/IE11
|
||||
function containsNode(parent, child) {
|
||||
if (parent === child) return true;
|
||||
child = child.parentNode;
|
||||
while (child) {
|
||||
if (child === parent) return true;
|
||||
child = child.parentNode;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getNode(ctx, byRoot) {
|
||||
var node, root = ctx.config.editor;
|
||||
ctx._range = ctx._range || ctx.getRange();
|
||||
node = ctx._range.commonAncestorContainer;
|
||||
if (!node || node === root) return null;
|
||||
while (node && (node.nodeType !== 1) && (node.parentNode !== root)) node = node.parentNode;
|
||||
while (node && byRoot && (node.parentNode !== root)) node = node.parentNode;
|
||||
return containsNode(root, node) ? node : null;
|
||||
}
|
||||
|
||||
// node effects
|
||||
function effectNode(ctx, el, returnAsNodeName) {
|
||||
var nodes = [];
|
||||
el = el || ctx.config.editor;
|
||||
while (el && el !== ctx.config.editor) {
|
||||
if (el.nodeName.match(effectNodeReg)) {
|
||||
nodes.push(returnAsNodeName ? el.nodeName.toLowerCase() : el);
|
||||
}
|
||||
el = el.parentNode;
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
// breakout from node
|
||||
function lineBreak(ctx, empty) {
|
||||
var range = ctx._range = ctx.getRange(), node = doc.createElement('p');
|
||||
if (empty) ctx.config.editor.innerHTML = '';
|
||||
node.innerHTML = '<br>';
|
||||
range.insertNode(node);
|
||||
focusNode(ctx, node.childNodes[0], range);
|
||||
}
|
||||
|
||||
function focusNode(ctx, node, range) {
|
||||
range.setStartAfter(node);
|
||||
range.setEndBefore(node);
|
||||
range.collapse(false);
|
||||
ctx.setRange(range);
|
||||
}
|
||||
|
||||
function autoLink(node) {
|
||||
if (node.nodeType === 1) {
|
||||
if (autoLinkReg.notLink.test(node.tagName)) return;
|
||||
utils.forEach(node.childNodes, function (child) {
|
||||
autoLink(child);
|
||||
}, true);
|
||||
} else if (node.nodeType === 3) {
|
||||
var result = urlToLink(node.nodeValue || '');
|
||||
if (!result.links) return;
|
||||
var frag = doc.createDocumentFragment(),
|
||||
div = doc.createElement('div');
|
||||
div.innerHTML = result.text;
|
||||
while (div.childNodes.length) frag.appendChild(div.childNodes[0]);
|
||||
node.parentNode.replaceChild(frag, node);
|
||||
}
|
||||
}
|
||||
|
||||
function urlToLink(str) {
|
||||
var count = 0;
|
||||
str = str.replace(autoLinkReg.url, function(url) {
|
||||
var realUrl = url, displayUrl = url;
|
||||
count++;
|
||||
if (url.length > autoLinkReg.maxLength) displayUrl = url.slice(0, autoLinkReg.maxLength) + '...';
|
||||
// Add http prefix if necessary
|
||||
if (!autoLinkReg.prefix.test(realUrl)) realUrl = 'http://' + realUrl;
|
||||
return '<a href="' + realUrl + '">' + displayUrl + '</a>';
|
||||
});
|
||||
return {links: count, text: str};
|
||||
}
|
||||
|
||||
function toggleNode(node, hide) {
|
||||
node.style.display = hide ? 'none' : 'block';
|
||||
}
|
||||
|
||||
Pen = function(config) {
|
||||
|
||||
if (!config) throw new Error('Can\'t find config');
|
||||
|
||||
debugMode = config.debug;
|
||||
|
||||
// merge user config
|
||||
var defaults = utils.merge(config);
|
||||
|
||||
var editor = defaults.editor;
|
||||
|
||||
if (!editor || editor.nodeType !== 1) throw new Error('Can\'t find editor');
|
||||
|
||||
// set default class
|
||||
editor.classList.add(defaults.class);
|
||||
|
||||
// set contenteditable
|
||||
editor.setAttribute('contenteditable', 'true');
|
||||
|
||||
// assign config
|
||||
this.config = defaults;
|
||||
|
||||
// set placeholder
|
||||
if (defaults.placeholder) editor.setAttribute('data-placeholder', defaults.placeholder);
|
||||
checkPlaceholder(this);
|
||||
|
||||
// save the selection obj
|
||||
this.selection = selection;
|
||||
|
||||
// define local events
|
||||
this._events = {change: []};
|
||||
|
||||
// enable toolbar
|
||||
initToolbar(this);
|
||||
|
||||
// init events
|
||||
initEvents(this);
|
||||
|
||||
// to check content change
|
||||
this._prevContent = this.getContent();
|
||||
|
||||
// enable markdown covert
|
||||
if (this.markdown) this.markdown.init(this);
|
||||
|
||||
// stay on the page
|
||||
if (this.config.stay) this.stay(this.config);
|
||||
|
||||
if(this.config.input) {
|
||||
this.addOnSubmitListener(this.config.input);
|
||||
}
|
||||
};
|
||||
|
||||
Pen.prototype.on = function(type, listener) {
|
||||
addListener(this, this.config.editor, type, listener);
|
||||
return this;
|
||||
};
|
||||
|
||||
Pen.prototype.addOnSubmitListener = function(inputElement) {
|
||||
var form = inputElement.form;
|
||||
var me = this;
|
||||
form.addEventListener("submit", function() {
|
||||
inputElement.value = me.config.saveAsMarkdown ? me.toMd(me.config.editor.innerHTML) : me.config.editor.innerHTML;
|
||||
});
|
||||
};
|
||||
|
||||
Pen.prototype.isEmpty = function(node) {
|
||||
node = node || this.config.editor;
|
||||
return !(node.querySelector('img')) && !(node.querySelector('blockquote')) &&
|
||||
!(node.querySelector('li')) && !trim(node.textContent);
|
||||
};
|
||||
|
||||
Pen.prototype.getContent = function() {
|
||||
return this.isEmpty() ? '' : trim(this.config.editor.innerHTML);
|
||||
};
|
||||
|
||||
Pen.prototype.setContent = function(html) {
|
||||
this.config.editor.innerHTML = html;
|
||||
this.cleanContent();
|
||||
return this;
|
||||
};
|
||||
|
||||
Pen.prototype.checkContentChange = function () {
|
||||
var prevContent = this._prevContent, currentContent = this.getContent();
|
||||
if (prevContent === currentContent) return;
|
||||
this._prevContent = currentContent;
|
||||
triggerListener(this, 'change', currentContent, prevContent);
|
||||
};
|
||||
|
||||
Pen.prototype.getRange = function() {
|
||||
var editor = this.config.editor, range = selection.rangeCount && selection.getRangeAt(0);
|
||||
if (!range) range = doc.createRange();
|
||||
if (!containsNode(editor, range.commonAncestorContainer)) {
|
||||
range.selectNodeContents(editor);
|
||||
range.collapse(false);
|
||||
}
|
||||
return range;
|
||||
};
|
||||
|
||||
Pen.prototype.setRange = function(range) {
|
||||
range = range || this._range;
|
||||
if (!range) {
|
||||
range = this.getRange();
|
||||
range.collapse(false); // set to end
|
||||
}
|
||||
try {
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
} catch (e) {/* IE throws error sometimes*/}
|
||||
return this;
|
||||
};
|
||||
|
||||
Pen.prototype.focus = function(focusStart) {
|
||||
if (!focusStart) this.setRange();
|
||||
this.config.editor.focus();
|
||||
return this;
|
||||
};
|
||||
|
||||
Pen.prototype.execCommand = function(name, value) {
|
||||
name = name.toLowerCase();
|
||||
this.setRange();
|
||||
|
||||
if (commandsReg.block.test(name)) {
|
||||
commandBlock(this, name);
|
||||
} else if (commandsReg.inline.test(name)) {
|
||||
commandOverall(this, name, value);
|
||||
} else if (commandsReg.source.test(name)) {
|
||||
commandLink(this, name, value);
|
||||
} else if (commandsReg.insert.test(name)) {
|
||||
commandInsert(this, name, value);
|
||||
} else if (commandsReg.wrap.test(name)) {
|
||||
commandWrap(this, name, value);
|
||||
} else {
|
||||
utils.log('can not find command function for name: ' + name + (value ? (', value: ' + value) : ''), true);
|
||||
}
|
||||
if (name === 'indent') this.checkContentChange();
|
||||
else this.cleanContent({cleanAttrs: ['style']});
|
||||
};
|
||||
|
||||
// remove attrs and tags
|
||||
// pen.cleanContent({cleanAttrs: ['style'], cleanTags: ['id']})
|
||||
Pen.prototype.cleanContent = function(options) {
|
||||
var editor = this.config.editor;
|
||||
|
||||
if (!options) options = this.config;
|
||||
utils.forEach(options.cleanAttrs, function (attr) {
|
||||
utils.forEach(editor.querySelectorAll('[' + attr + ']'), function(item) {
|
||||
item.removeAttribute(attr);
|
||||
}, true);
|
||||
}, true);
|
||||
utils.forEach(options.cleanTags, function (tag) {
|
||||
utils.forEach(editor.querySelectorAll(tag), function(item) {
|
||||
item.parentNode.removeChild(item);
|
||||
}, true);
|
||||
}, true);
|
||||
|
||||
checkPlaceholder(this);
|
||||
this.checkContentChange();
|
||||
return this;
|
||||
};
|
||||
|
||||
// auto link content, return content
|
||||
Pen.prototype.autoLink = function() {
|
||||
autoLink(this.config.editor);
|
||||
return this.getContent();
|
||||
};
|
||||
|
||||
// highlight menu
|
||||
Pen.prototype.highlight = function() {
|
||||
var toolbar = this._toolbar || this._menu
|
||||
, node = getNode(this);
|
||||
// remove all highlights
|
||||
utils.forEach(toolbar.querySelectorAll('.active'), function(el) {
|
||||
el.classList.remove('active');
|
||||
}, true);
|
||||
|
||||
if (!node) return this;
|
||||
|
||||
var effects = effectNode(this, node)
|
||||
, inputBar = this._inputBar
|
||||
, highlight;
|
||||
|
||||
if (inputBar && toolbar === this._menu) {
|
||||
// display link input if createlink enabled
|
||||
inputBar.style.display = 'none';
|
||||
// reset link input value
|
||||
inputBar.value = '';
|
||||
}
|
||||
|
||||
highlight = function(str) {
|
||||
if (!str) return;
|
||||
var el = toolbar.querySelector('[data-action=' + str + ']');
|
||||
return el && el.classList.add('active');
|
||||
};
|
||||
utils.forEach(effects, function(item) {
|
||||
var tag = item.nodeName.toLowerCase();
|
||||
switch(tag) {
|
||||
case 'a':
|
||||
if (inputBar) inputBar.value = item.getAttribute('href');
|
||||
tag = 'createlink';
|
||||
break;
|
||||
case 'img':
|
||||
if (inputBar) inputBar.value = item.getAttribute('src');
|
||||
tag = 'insertimage';
|
||||
break;
|
||||
case 'i':
|
||||
tag = 'italic';
|
||||
break;
|
||||
case 'u':
|
||||
tag = 'underline';
|
||||
break;
|
||||
case 'b':
|
||||
tag = 'bold';
|
||||
break;
|
||||
case 'pre':
|
||||
case 'code':
|
||||
tag = 'code';
|
||||
break;
|
||||
case 'ul':
|
||||
tag = 'insertunorderedlist';
|
||||
break;
|
||||
case 'ol':
|
||||
tag = 'insertorderedlist';
|
||||
break;
|
||||
case 'li':
|
||||
tag = 'indent';
|
||||
break;
|
||||
}
|
||||
highlight(tag);
|
||||
}, true);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// show menu
|
||||
Pen.prototype.menu = function() {
|
||||
if (!this._menu) return this;
|
||||
if (selection.isCollapsed) {
|
||||
this._menu.style.display = 'none'; //hide menu
|
||||
this._inputActive = false;
|
||||
return this;
|
||||
}
|
||||
if (this._toolbar) {
|
||||
if (!this._inputBar || !this._inputActive) return this;
|
||||
}
|
||||
var offset = this._range.getBoundingClientRect()
|
||||
, menuPadding = 10
|
||||
, top = offset.top - menuPadding
|
||||
, left = offset.left + (offset.width / 2)
|
||||
, menu = this._menu
|
||||
, menuOffset = {x: 0, y: 0}
|
||||
, stylesheet = this._stylesheet;
|
||||
|
||||
// fixes some browser double click visual discontinuity
|
||||
// if the offset has no width or height it should not be used
|
||||
if (offset.width === 0 && offset.height === 0) return this;
|
||||
|
||||
// store the stylesheet used for positioning the menu horizontally
|
||||
if (this._stylesheet === undefined) {
|
||||
var style = document.createElement("style");
|
||||
document.head.appendChild(style);
|
||||
this._stylesheet = stylesheet = style.sheet;
|
||||
}
|
||||
// display block to caculate its width & height
|
||||
menu.style.display = 'block';
|
||||
|
||||
menuOffset.x = left - (menu.clientWidth / 2);
|
||||
menuOffset.y = top - menu.clientHeight;
|
||||
|
||||
// check to see if menu has over-extended its bounding box. if it has,
|
||||
// 1) apply a new class if overflowed on top;
|
||||
// 2) apply a new rule if overflowed on the left
|
||||
if (stylesheet.cssRules.length > 0) {
|
||||
stylesheet.deleteRule(0);
|
||||
}
|
||||
if (menuOffset.x < 0) {
|
||||
menuOffset.x = 0;
|
||||
stylesheet.insertRule('.pen-menu:after {left: ' + left + 'px;}', 0);
|
||||
} else {
|
||||
stylesheet.insertRule('.pen-menu:after {left: 50%; }', 0);
|
||||
}
|
||||
if (menuOffset.y < 0) {
|
||||
menu.classList.add('pen-menu-below');
|
||||
menuOffset.y = offset.top + offset.height + menuPadding;
|
||||
} else {
|
||||
menu.classList.remove('pen-menu-below');
|
||||
}
|
||||
|
||||
menu.style.top = menuOffset.y + 'px';
|
||||
menu.style.left = menuOffset.x + 'px';
|
||||
return this;
|
||||
};
|
||||
|
||||
Pen.prototype.stay = function(config) {
|
||||
var ctx = this;
|
||||
if (!window.onbeforeunload) {
|
||||
window.onbeforeunload = function() {
|
||||
if (!ctx._isDestroyed) return config.stayMsg;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Pen.prototype.destroy = function(isAJoke) {
|
||||
var destroy = isAJoke ? false : true
|
||||
, attr = isAJoke ? 'setAttribute' : 'removeAttribute';
|
||||
|
||||
if (!isAJoke) {
|
||||
removeAllListeners(this);
|
||||
try {
|
||||
selection.removeAllRanges();
|
||||
if (this._menu) this._menu.parentNode.removeChild(this._menu);
|
||||
} catch (e) {/* IE throws error sometimes*/}
|
||||
} else {
|
||||
initToolbar(this);
|
||||
initEvents(this);
|
||||
}
|
||||
this._isDestroyed = destroy;
|
||||
this.config.editor[attr]('contenteditable', '');
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Pen.prototype.rebuild = function() {
|
||||
return this.destroy('it\'s a joke');
|
||||
};
|
||||
|
||||
// a fallback for old browers
|
||||
root.Pen = function(config) {
|
||||
if (!config) return utils.log('can\'t find config', true);
|
||||
|
||||
var defaults = utils.merge(config)
|
||||
, klass = defaults.editor.getAttribute('class');
|
||||
|
||||
klass = klass ? klass.replace(/\bpen\b/g, '') + ' pen-textarea ' + defaults.class : 'pen pen-textarea';
|
||||
defaults.editor.setAttribute('class', klass);
|
||||
defaults.editor.innerHTML = defaults.textarea;
|
||||
return defaults.editor;
|
||||
};
|
||||
|
||||
// export content as markdown
|
||||
var regs = {
|
||||
a: [/<a\b[^>]*href=["']([^"]+|[^']+)\b[^>]*>(.*?)<\/a>/ig, '[$2]($1)'],
|
||||
img: [/<img\b[^>]*src=["']([^\"+|[^']+)[^>]*>/ig, ''],
|
||||
b: [/<b\b[^>]*>(.*?)<\/b>/ig, '**$1**'],
|
||||
i: [/<i\b[^>]*>(.*?)<\/i>/ig, '***$1***'],
|
||||
h: [/<h([1-6])\b[^>]*>(.*?)<\/h\1>/ig, function(a, b, c) {
|
||||
return '\n' + ('######'.slice(0, b)) + ' ' + c + '\n';
|
||||
}],
|
||||
li: [/<(li)\b[^>]*>(.*?)<\/\1>/ig, '* $2\n'],
|
||||
blockquote: [/<(blockquote)\b[^>]*>(.*?)<\/\1>/ig, '\n> $2\n'],
|
||||
pre: [/<pre\b[^>]*>(.*?)<\/pre>/ig, '\n```\n$1\n```\n'],
|
||||
code: [/<code\b[^>]*>(.*?)<\/code>/ig, '\n`\n$1\n`\n'],
|
||||
p: [/<p\b[^>]*>(.*?)<\/p>/ig, '\n$1\n'],
|
||||
hr: [/<hr\b[^>]*>/ig, '\n---\n']
|
||||
};
|
||||
|
||||
Pen.prototype.toMd = function() {
|
||||
var html = this.getContent()
|
||||
.replace(/\n+/g, '') // remove line break
|
||||
.replace(/<([uo])l\b[^>]*>(.*?)<\/\1l>/ig, '$2'); // remove ul/ol
|
||||
|
||||
for(var p in regs) {
|
||||
if (regs.hasOwnProperty(p))
|
||||
html = html.replace.apply(html, regs[p]);
|
||||
}
|
||||
return html.replace(/\*{5}/g, '**');
|
||||
};
|
||||
|
||||
// make it accessible
|
||||
if (doc.getSelection) {
|
||||
selection = doc.getSelection();
|
||||
root.Pen = Pen;
|
||||
}
|
||||
|
||||
}(window, document));
|
||||
@@ -51,7 +51,9 @@ const html5Template = `
|
||||
color: #e6fbfb;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
|
||||
<link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor.min.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user