flush -- WIP admin panel
This commit is contained in:
80
include/article.php
Normal file
80
include/article.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
class NoPostException extends Exception {}
|
||||
|
||||
class Article {
|
||||
private $pid;
|
||||
private $author;
|
||||
private $name;
|
||||
private $title;
|
||||
private $content;
|
||||
private $time;
|
||||
private $likes_count;
|
||||
private $comments_count;
|
||||
private $islike = false;
|
||||
|
||||
public function __construct($data) {
|
||||
if (is_int($data)) {
|
||||
$this->pid = $data;
|
||||
$query = cavern_query_result("SELECT `post`.*, `user`.name FROM `post` INNER JOIN `user` ON `post`.username = `user`.username WHERE `pid`=%d", array($this->pid));
|
||||
if ($query['num_rows'] > 0) {
|
||||
$result = $query['row'];
|
||||
} else {
|
||||
// post doesn't exist
|
||||
throw new NoPostException('There is no post with pid '.$this->pid);
|
||||
}
|
||||
} else if (is_array($data)) {
|
||||
/* pass the sql result directly */
|
||||
$result = $data;
|
||||
$this->pid = $result['pid'];
|
||||
}
|
||||
|
||||
if (isset($result['name'])) $this->name = $result['name']; else $this->name = "";
|
||||
$this->author = $result['username'];
|
||||
$this->title = $result['title'];
|
||||
$this->content = $result['content'];
|
||||
$this->time = $result['time'];
|
||||
$this->likes_count = $result['like'];
|
||||
$this->comments_count = $result['comment'];
|
||||
}
|
||||
|
||||
public function __get($name) {
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
public function is_like(User $user) {
|
||||
if ($this->likes_count > 0 && $user->islogin) {
|
||||
$like_query = cavern_query_result("SELECT * FROM `like` WHERE `pid`='%d' AND `username`='%s'", array($this->pid, $_SESSION['cavern_username']));
|
||||
if ($like_query['num_rows'] > 0) {
|
||||
$this->islike = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->islike;
|
||||
}
|
||||
|
||||
public function modify(User $user, $name, $value) {
|
||||
// article author and admin can edit post
|
||||
if ($user->islogin && ($user->username === $this->author || $user->level >= 8)) {
|
||||
$this->$name = $value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function save() {
|
||||
global $SQL;
|
||||
$SQL->query("UPDATE `post` SET `title`='%s', `content`='%s' WHERE `pid`='%d' AND `username`='%s'", array(htmlspecialchars($_POST['title']), htmlspecialchars($_POST['content']), $this->pid, $this->author));
|
||||
}
|
||||
}
|
||||
|
||||
function article_list($query_result) {
|
||||
$article_list = array();
|
||||
|
||||
if ($query_result['num_rows'] > 0) {
|
||||
do {
|
||||
$article_list[] = new Article($query_result['row']);
|
||||
} while ($query_result['row'] = $query_result['query']->fetch_assoc());
|
||||
}
|
||||
|
||||
return $article_list;
|
||||
}
|
||||
440
include/css/cavern.css
Normal file
440
include/css/cavern.css
Normal file
@@ -0,0 +1,440 @@
|
||||
/* General style */
|
||||
* {
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
div.table.wrapper {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.table.wrapper:not(:last-child) {
|
||||
margin-bottom: .75em;
|
||||
}
|
||||
|
||||
.markdown-body h1,
|
||||
.markdown-body h2,
|
||||
.markdown-body h3,
|
||||
.markdown-body h4,
|
||||
.markdown-body h5,
|
||||
.markdown-body h6 {
|
||||
border: none;
|
||||
margin-top: 0 !important;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-body h1:not(.ts):not(.unstyled) {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
|
||||
.markdown-body h2:not(.ts):not(.unstyled) {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.markdown-body h3:not(.ts):not(.unstyled) {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
.markdown-body h4:not(.ts):not(.unstyled) {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.markdown-body h5:not(.ts):not(.unstyled) {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.markdown-body h6:not(.ts):not(.unstyled) {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.markdown-body > .markdown + * { /* .markdown is invisible and the first element in a post, so neighbor of .markdown is the first visible element */
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.markdown-body > *:last-child:not(div) {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.editormd-html-preview {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.editormd-html-preview code { /* inline code */
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.editormd-html-preview a {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.editormd-html-preview img {
|
||||
margin: .4em 0;
|
||||
}
|
||||
|
||||
img.emoji {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* fonts */
|
||||
/* prevent this rule from overwriting the style of <span> of KaTeX */
|
||||
.markdown-body *:not(span), .markdown-body > :not(.editormd-tex) span,
|
||||
/* editor font */
|
||||
[class*="CodeMirror"] *, [class*="cm"] * {
|
||||
font-family: Consolas,"SF Pro TC","SF Pro Text","SF Pro Icons","PingFang TC","Helvetica Neue","Helvetica","Microsoft JhengHei","Segoe UI",Ubuntu,微軟正黑體,"LiHei Pro","Droid Sans Fallback",Roboto,"Helvetica Neue","Droid Sans","Arial",sans-serif;
|
||||
}
|
||||
|
||||
.katex * { /* hack */
|
||||
font-family: KaTeX_Main, Times New Roman, serif;
|
||||
}
|
||||
|
||||
/* sweet alert */
|
||||
.swal2-popup h2.swal2-title {
|
||||
margin: 0 0 .4em;
|
||||
}
|
||||
|
||||
/* menu */
|
||||
#menu button.login.button {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
/* notification */
|
||||
#menu .notification.icon.item i.icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#menu .notification.icon.item span.counter {
|
||||
display: block;
|
||||
padding: .1em .2em;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
background-color: #F03434;
|
||||
border-radius: .2em;
|
||||
position: absolute;
|
||||
top: .25em;
|
||||
right: .25em;
|
||||
}
|
||||
|
||||
.notification.container {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 11; /* to overlap editormd */
|
||||
top: 1em;
|
||||
right: .2em;
|
||||
width: calc(100vw - 2.16em);
|
||||
max-width: 400px;
|
||||
height: 85vh;
|
||||
max-height: 500px;
|
||||
background-color: white;
|
||||
border-radius: .28571rem;
|
||||
box-shadow: 0 0 3px 0 #888888;
|
||||
}
|
||||
|
||||
.active.notification.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.notification.container > .ts.segment:first-child {
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
.notification.container .ts.feed {
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.notification.container .ts.feed .event {
|
||||
padding-left: .8em;
|
||||
padding-right: .8em;
|
||||
}
|
||||
|
||||
.notification.container .ts.feed .unread.event {
|
||||
background-color: #e4f2f5;
|
||||
}
|
||||
|
||||
.notification.container .ts.feed .event:hover {
|
||||
background-color: #e2edef;
|
||||
}
|
||||
|
||||
.notification.click.handler {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.notification.container .ts.fluid.bottom:last-child {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ts.dividing.header .notification.description {
|
||||
font-size: .6em;
|
||||
color: gray;
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
/* main */
|
||||
#main {
|
||||
padding: 10px 0 20px;
|
||||
}
|
||||
|
||||
/* content */
|
||||
#content {
|
||||
min-height: 50vh;
|
||||
}
|
||||
|
||||
/* pages */
|
||||
.loading#cards ~ #pages {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* cards */
|
||||
#cards {
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
.loading#cards {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ts.card > .content > .header:not(.ts) {
|
||||
font-size: 1.65em;
|
||||
}
|
||||
|
||||
.ts.card > .content > .description.markdown-body {
|
||||
background: transparent;
|
||||
font-size: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ts.card > .content > .description.markdown-body a {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* post */
|
||||
#content .ts.grid > #header > .ts.header {
|
||||
/* align this with post content */
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
#content .ts.grid > .action.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#content > .ts.segments:not(:last-child) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#post {
|
||||
font-size: 15px;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.markdown-body ul:first-child, .markdown-body ol:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown-body#post h1:not(.ts) {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.markdown-body#post h2:not(.ts) {
|
||||
font-size: 1.85em;
|
||||
}
|
||||
|
||||
.markdown-body#post h3:not(.ts) {
|
||||
font-size: 1.7em;
|
||||
}
|
||||
|
||||
.markdown-body#post h4:not(.ts) {
|
||||
font-size: 1.55em;
|
||||
}
|
||||
|
||||
.markdown-body#post h5:not(.ts) {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
.markdown-body#post h6:not(.ts) {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
#toc {
|
||||
min-height: 8em;
|
||||
max-height: calc(95vh - 3em);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* code block */
|
||||
pre.prettyprint ol.linenums:not(.ts) {
|
||||
counter-reset: code 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
pre.prettyprint ol.linenums:not(.ts) > li::before {
|
||||
counter-increment: code;
|
||||
content: counter(code);
|
||||
|
||||
/* line numbers align */
|
||||
right: 100%;
|
||||
margin-left: 0;
|
||||
padding-right: .5em;
|
||||
}
|
||||
|
||||
pre.prettyprint ol.linenums:not(.ts) > li > code {
|
||||
min-height: 1em; /* fixing collapsed empty line */
|
||||
}
|
||||
|
||||
pre.prettyprint ol.linenums:not(.ts) > li > code > span {
|
||||
font-family: "YaHei Consolas Hybrid", 'Consolas', "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace;
|
||||
}
|
||||
|
||||
/* post editor */
|
||||
#edit .action.column {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#markdownEditor:not(.editormd-fullscreen) {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.editormd-fullscreen {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* comments */
|
||||
.ts.comments {
|
||||
min-height: 6em;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ts.comments .comment {
|
||||
padding: 0.25em 0 0.25em;
|
||||
margin: 0.25em 0 0.25em;
|
||||
}
|
||||
|
||||
.ts.no-comment.segment:not(.active), .ts.active.loader + .fetch.button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.comment.header {
|
||||
width: 100%;
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
padding-top: .8em;
|
||||
top: 0;
|
||||
background-color: white;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.ts.comment.divider {
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
.stretched.header.column {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.comment .markdown-body {
|
||||
padding: .2em 0;
|
||||
}
|
||||
|
||||
.comment .markdown-body img {
|
||||
margin: .4em 0;
|
||||
}
|
||||
|
||||
.comment img.emoji {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.emphasized.comment {
|
||||
animation: commentEmphasize 2s ease-in .1s;
|
||||
}
|
||||
|
||||
/* comment editor */
|
||||
#comment > .ts.segment:first-child {
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
#comment > .ts.segment:first-child > .ts.tabbed.menu {
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
#comment > .ts.segment:not(:first-child) {
|
||||
border-top: none;
|
||||
border-bottom-left-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
border-bottom: 1px solid #e9e9e9;
|
||||
}
|
||||
|
||||
#comment .ts.button {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#preview {
|
||||
min-height: 15em;
|
||||
}
|
||||
|
||||
/* account */
|
||||
.ts.label.avatar.tooltip {
|
||||
border: 0;
|
||||
border-radius: .21429rem;
|
||||
}
|
||||
|
||||
.ts.form .disabled.field {
|
||||
cursor: not-allowed;
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
.ts.form .disabled.field input {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* profile */
|
||||
#avatar {
|
||||
width: 7.5em;
|
||||
}
|
||||
|
||||
/* sidebar */
|
||||
#sidebar .ts.header .avatar.image {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#sidebar .ts.header .negative.sub.header {
|
||||
color: #CE5F58;
|
||||
}
|
||||
|
||||
/* footer */
|
||||
footer {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
footer .ts.divider {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@keyframes commentEmphasize {
|
||||
from {
|
||||
background-color: #e4f2f5;
|
||||
}
|
||||
|
||||
to {
|
||||
background-color: unset;
|
||||
}
|
||||
}
|
||||
84
include/db.php
Normal file
84
include/db.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/* Cavern Edition
|
||||
modified by t510599 at 2019/05/30
|
||||
*/
|
||||
/*
|
||||
<Secret Blog>
|
||||
Copyright (C) 2012-2017 太陽部落格站長 Secret <http://gdsecret.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, version 3.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class Database {
|
||||
private $conn;
|
||||
private $addr;
|
||||
private $user;
|
||||
private $pass;
|
||||
private $db;
|
||||
|
||||
public function __construct($addr,$user,$pass,$db){
|
||||
$this->addr = $addr;
|
||||
$this->user = $user;
|
||||
$this->pass = $pass;
|
||||
$this->db = $db;
|
||||
|
||||
$this->conn = new mysqli($addr,$user,$pass,$db);
|
||||
|
||||
if($this->conn->connect_error !== null){
|
||||
throw new Exception($this->conn->connect_error);
|
||||
}
|
||||
}
|
||||
|
||||
private function reconnect(){
|
||||
$this->conn = new mysqli($this->addr,$this->user,$this->pass,$this->db);
|
||||
}
|
||||
|
||||
private function checkConn(){
|
||||
return $this->conn->ping();
|
||||
}
|
||||
|
||||
public function query($query,$data = array()){
|
||||
if(!$this->checkConn()) $this->reconnect();
|
||||
|
||||
foreach($data as $k=>$d){
|
||||
$data[$k] = $this->conn->real_escape_string($d);
|
||||
}
|
||||
|
||||
$result = $this->conn->query(vsprintf($query,$data));
|
||||
|
||||
if($result === false){
|
||||
throw new Exception($this->conn->error);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function insert_id() {
|
||||
return $this->conn->insert_id;
|
||||
}
|
||||
};
|
||||
117
include/function.php
Normal file
117
include/function.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
function cavern_login($username, $password) {
|
||||
global $SQL;
|
||||
if (isset($username) && isset($password)) {
|
||||
$login = $SQL->query("SELECT `username`, `pwd` FROM `user` WHERE `username` = '%s' AND `pwd` = '%s'",array($username, cavern_password_hash($password, $username)));
|
||||
if ($login->num_rows > 0) {
|
||||
$_SESSION['cavern_username'] = $username;
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
function cavern_logout() {
|
||||
$_SESSION['cavern_username'] = NULL;
|
||||
unset($_SESSION['cavern_username']);
|
||||
return 1;
|
||||
}
|
||||
|
||||
function cavern_password_hash($value, $salt) {
|
||||
$temp = substr(sha1(strrev($value).$salt), 0, 24);
|
||||
return hash('sha512', $temp.$value);
|
||||
}
|
||||
|
||||
function cavern_query_result($query, $data=array()) {
|
||||
global $SQL;
|
||||
$result['query'] = $SQL->query($query, $data);
|
||||
$result['row'] = $result['query']->fetch_assoc();
|
||||
$result['num_rows'] = $result['query']->num_rows;
|
||||
if ($result['num_rows'] > 0) {
|
||||
return $result;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
function cavern_level_to_role($level) {
|
||||
switch ($level) {
|
||||
case 9:
|
||||
$role = "站長";
|
||||
break;
|
||||
case 8:
|
||||
$role = "管理員";
|
||||
break;
|
||||
case 1:
|
||||
$role = "作者";
|
||||
break;
|
||||
case 0:
|
||||
$role = "會員";
|
||||
break;
|
||||
default:
|
||||
$role = "麥克雞塊";
|
||||
break;
|
||||
}
|
||||
return $role;
|
||||
}
|
||||
|
||||
function cavern_greeting() {
|
||||
$hour = date('G');
|
||||
if ($hour >= 21 || $hour < 5) {
|
||||
$greeting = "晚安";
|
||||
} else if ($hour >= 12) {
|
||||
$greeting = "午安";
|
||||
} else if ($hour >= 5 && $hour < 12) {
|
||||
$greeting = "早安";
|
||||
}
|
||||
return $greeting;
|
||||
}
|
||||
|
||||
function cavern_pages($now_page, $total, $limit) {
|
||||
$text='<div class="ts basic center aligned segment" id="pages">';
|
||||
$text.='<select class="ts basic dropdown" onchange="location.href=this.options[this.selectedIndex].value;">';
|
||||
$now_page = abs($now_page);
|
||||
$page_num = ceil($total / $limit);
|
||||
for ($i = 1; $i <= $page_num; $i++) {
|
||||
if ($now_page != $i) {
|
||||
$text.='<option value="index.php?page='.$i.'">第 '.$i.' 頁</option>';
|
||||
} else {
|
||||
$text.='<option value="index.php?page='.$i.'" selected="selected">第 '.$i.' 頁</option>';
|
||||
}
|
||||
}
|
||||
$text.='</select>';
|
||||
$text.='</div>';
|
||||
return $text;
|
||||
}
|
||||
|
||||
function sumarize($string, $limit) {
|
||||
$count = 0;
|
||||
$text = "";
|
||||
$content_start = FALSE;
|
||||
foreach (explode("\n", $string) as $line) {
|
||||
if (trim($line) != "" && $content_start == FALSE) {
|
||||
$content_start = TRUE; // don't count the empty line until the main content
|
||||
}
|
||||
if (!$content_start) {
|
||||
continue;
|
||||
}
|
||||
$count++;
|
||||
$text.=$line."\n";
|
||||
if ($count == $limit || mb_strlen($text) >= 200) {
|
||||
if (mb_strlen($text) >= 200) {
|
||||
$text = mb_substr($text, 0, 200)."...\n";
|
||||
}
|
||||
$text.="...(還有更多)\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
102
include/js/account.js
Normal file
102
include/js/account.js
Normal file
@@ -0,0 +1,102 @@
|
||||
if (document.newacc) {
|
||||
// Register
|
||||
let form = document.newacc;
|
||||
eventListenerInitialize(form, [form.password, form.repeat]);
|
||||
$(form.username).on('change', function () {
|
||||
let self = this;
|
||||
if (!/^[a-z][a-z0-9_-]*$/.test(self.value) || (self.value.length > 20 || self.value == "")) {
|
||||
setFieldStatus(self, "error", "請依照格式輸入");
|
||||
setFieldLabel(self, "");
|
||||
} else {
|
||||
setFieldStatus(self, ""); // reset
|
||||
setFieldLabel(self, "");
|
||||
|
||||
axios.request({
|
||||
method: "GET",
|
||||
url: `ajax/user.php?username=${this.value}`,
|
||||
responseType: "json"
|
||||
}).then(function (_res) {
|
||||
// username exist
|
||||
setFieldStatus(self, "error", "此帳號已被使用");
|
||||
setFieldLabel(self, "此帳號已被使用");
|
||||
}).catch(function (_error) {
|
||||
// username not exist
|
||||
setFieldStatus(self, "success");
|
||||
setFieldLabel(self, "此帳號可以使用");
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (document.editacc) {
|
||||
// Manage Profile
|
||||
let form = document.editacc;
|
||||
eventListenerInitialize(form, [form.new, form.repeat]);
|
||||
$(form.new).on('input', function () {
|
||||
if (this.value == "") {
|
||||
form.repeat.removeAttribute("required");
|
||||
setFieldStatus(form.repeat, "", "", false);
|
||||
} else {
|
||||
form.repeat.setAttribute("required", "required");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function eventListenerInitialize (form, inputs) {
|
||||
// first is password input, second is repeat input
|
||||
inputs.forEach(function (el) {
|
||||
$(el).on('input', function (_e) {
|
||||
if (inputs[0].value == inputs[1].value && inputs[0].value != "") {
|
||||
setFieldStatus(inputs[1], "success");
|
||||
} else {
|
||||
setFieldStatus(inputs[1], "error", "密碼不正確,請再試一次。");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(form).on('submit', function (e) {
|
||||
e.preventDefault();
|
||||
if (inputs[0].value != inputs[1].value) {
|
||||
inputs[1].setCustomValidity("密碼不正確,請再試一次。");
|
||||
inputs[1].focus();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let fd = new URLSearchParams(new FormData(this)).toString();
|
||||
axios.request({
|
||||
method: "POST",
|
||||
data: fd,
|
||||
url: "account.php",
|
||||
headers: {
|
||||
'Content-Type': "application/x-www-form-urlencoded"
|
||||
}
|
||||
}).then(function (res) {
|
||||
location.href = res.headers["axios-location"];
|
||||
}).catch(function (error) {
|
||||
if (error.response) {
|
||||
location.href = error.response.headers["axios-location"];
|
||||
} else {
|
||||
ts('.snackbar').snackbar({
|
||||
content: "發送失敗。"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setFieldStatus(el, status, validity="", required=true) {
|
||||
el.parentElement.className = (required) ? `${status} required field` : `${status} field`;
|
||||
el.setCustomValidity(validity);
|
||||
}
|
||||
|
||||
function setFieldLabel(el, text) {
|
||||
let sibling = el.nextElementSibling;
|
||||
if (sibling.tagName == "SMALL" && text != "") {
|
||||
let span = document.createElement('span');
|
||||
span.className = "message";
|
||||
span.innerText = text;
|
||||
el.parentElement.insertBefore(span, sibling);
|
||||
} else if (sibling.tagName == "SPAN" && text == "") {
|
||||
$(sibling).remove();
|
||||
} else if (sibling.tagName != "SMALL") {
|
||||
sibling.innerText = text;
|
||||
}
|
||||
}
|
||||
28
include/js/cards.js
Normal file
28
include/js/cards.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const cdnjs = "https://cdnjs.cloudflare.com/ajax/libs";
|
||||
|
||||
// Load Libraries
|
||||
const libraries = [
|
||||
cdnjs + "/marked/0.5.1/marked.min.js",
|
||||
cdnjs + "/prettify/r298/prettify.min.js",
|
||||
cdnjs + "/underscore.js/1.9.1/underscore-min.js"
|
||||
];
|
||||
|
||||
loadJS(libraries).then(function () {
|
||||
editormd.$marked = marked;
|
||||
editormd.loadFiles.js.push(...libraries.map(url => url.slice(0, -3))); // remove ".js"
|
||||
document.querySelectorAll('.ts.card .description').forEach(function(el) {
|
||||
let id = el.getAttribute('id');
|
||||
parseMarkdown(id, el.children[0].textContent, {
|
||||
toc: false,
|
||||
flowChart: false,
|
||||
sequenceDiagram: false,
|
||||
htmlDecode : "script,iframe,style|on*"
|
||||
}).children('.markdown').hide();
|
||||
});
|
||||
postProcess();
|
||||
setTimeout(function () {
|
||||
// show cards
|
||||
$('.loading#cards').removeClass('loading');
|
||||
$('#content .active.loader').removeClass('active');
|
||||
}, 500);
|
||||
});
|
||||
328
include/js/comment.js
Normal file
328
include/js/comment.js
Normal file
@@ -0,0 +1,328 @@
|
||||
const pid = $('#post').attr('data-id');
|
||||
|
||||
var post = { // post cache
|
||||
fetchTime: undefined,
|
||||
comments: [],
|
||||
idList: [] // ids of comment
|
||||
}
|
||||
|
||||
// Fetch
|
||||
$('.fetch.button').click(function(_e) {
|
||||
fetchComments();
|
||||
});
|
||||
|
||||
var fetchTimer = setInterval(fetchComments, 5*60*1000); // polling comments per 5 minutes
|
||||
|
||||
function fetchComments() {
|
||||
$('.ts.inline.loader').addClass('active');
|
||||
if (!pid) {
|
||||
console.error('An error occurred while fetching comments.');
|
||||
snackbar("無法載入留言。");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (post.fetchTime) document.cookie = `cavern_commentLastFetch=${post.fetchTime}; Max-Age=10`;
|
||||
|
||||
axios.request({
|
||||
method: "GET",
|
||||
url: `ajax/comment.php?pid=${pid}`,
|
||||
responseType: "json"
|
||||
}).then(function (res) {
|
||||
let data = res.data;
|
||||
let t = new Date(data.fetch);
|
||||
post.fetchTime = Math.ceil(data.fetch / 1000); // php timestamp
|
||||
$('span.fetch.time').text(`Last fetch: ${t.getHours()}:${ t.getMinutes() < 10 ? '0' + t.getMinutes() : t.getMinutes() }`);
|
||||
parseComments(data);
|
||||
}).catch(function (error) {
|
||||
if (error.response) {
|
||||
let res = error.response;
|
||||
console.error(`An error occurred while fetching comments of pid ${pid}, status ${res.status}`);
|
||||
} else {
|
||||
console.error(`An error occurred while fetching comments of pid ${pid}`);
|
||||
}
|
||||
snackbar("無法載入留言。");
|
||||
});
|
||||
setTimeout(() => { $('.ts.inline.loader').removeClass('active'); }, 250);
|
||||
}
|
||||
|
||||
function parseComments(data) {
|
||||
const commentTemplate = `<div class="comment" id="comment-{{ id }}" data-comment="{{ id }}"><a class="avatar" href="user.php?username={{ username }}"><img src="https://www.gravatar.com/avatar/{{ hash }}?d=https%3A%2F%2Ftocas-ui.com%2Fassets%2Fimg%2F5e5e3a6.png"></a><div class="content"><a class="author" href="user.php?username={{ username }}">{{ name }}</a><div class="middoted metadata"><div class="time">{{ time }}</div></div><div class="text" id="markdown-comment-{{ id }}"></div></div></div>`;
|
||||
|
||||
let add = data.idList.filter(function(item) { return post.idList.indexOf(item) < 0 }); // id list of new comments
|
||||
let remove = post.idList.filter(function(item) { return data.idList.indexOf(item) < 0 }); // id list of removed comments
|
||||
|
||||
for (postId of remove) {
|
||||
$(`.ts.comments div[data-comment="${postId}"]`).remove();
|
||||
}
|
||||
|
||||
for (c of data.comments) {
|
||||
if (add.indexOf(c.id) == -1 && data.modified.indexOf(c.id) == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (add.indexOf(c.id) != -1) {
|
||||
// render new comment
|
||||
let node = commentTemplate.replace(/{{ id }}/gm, c.id).replace('{{ time }}', c.time).replace(/{{ username }}/gm, c.username).replace('{{ name }}', data.names[c.username]).replace('{{ hash }}', data.hash[c.username]);
|
||||
$(node).appendTo('.ts.comments');
|
||||
if (c.actions.length != 0) {
|
||||
let actions = document.createElement('div');
|
||||
actions.className = "actions";
|
||||
$(`div[data-comment="${c.id}"] .content`).append(actions);
|
||||
for (act of c.actions) {
|
||||
switch (act) {
|
||||
case "reply":
|
||||
actions.insertAdjacentHTML('beforeend',`<a class="reply" data-username="${c.username}">回覆</a>`);
|
||||
break;
|
||||
case "del":
|
||||
actions.insertAdjacentHTML('beforeend',`<a class="delete" data-comment="${c.id}">刪除</a>`);
|
||||
break;
|
||||
case "edit":
|
||||
actions.insertAdjacentHTML('beforeend',`<a class="edit" data-comment="${c.id}">編輯</a>`);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (data.modified.indexOf(c.id) != -1) {
|
||||
// empty the old content
|
||||
$(`#markdown-comment-${c.id}`).html('');
|
||||
}
|
||||
|
||||
if (c.modified) {
|
||||
let $metadata = $(`div[data-comment="${c.id}"] .metadata`);
|
||||
if ($metadata.children('.modified').length) {
|
||||
$metadata.children('.modified').attr('title', c.modified);
|
||||
} else {
|
||||
$metadata.append(`<div class="modified" title="${c.modified}">已編輯</div>`);
|
||||
}
|
||||
}
|
||||
|
||||
parseMarkdown(`markdown-comment-${c.id}`, _.unescape(c.markdown), {
|
||||
toc: false
|
||||
});
|
||||
}
|
||||
|
||||
post.comments = data.comments; // cache data
|
||||
post.idList = data.idList; // cache data
|
||||
postProcess();
|
||||
|
||||
/* jump to the comment and emphasize it */
|
||||
if (location.hash && location.hash.startsWith("#comment-")) {
|
||||
let commentID = location.hash;
|
||||
if (!$(commentID).length) {
|
||||
snackbar("留言已刪除或是不存在。")
|
||||
} else {
|
||||
$(window).scrollTop($(commentID).offset().top - $('.comment.header').outerHeight() - 10);
|
||||
$(commentID).addClass('emphasized');
|
||||
}
|
||||
}
|
||||
|
||||
if (data.idList.length == 0) {
|
||||
$('.ts.no-comment.segment').addClass('active');
|
||||
} else {
|
||||
$('.ts.no-comment.segment').removeClass('active');
|
||||
}
|
||||
}
|
||||
|
||||
// Comment Editor & Preview
|
||||
(function () {
|
||||
let commentContainer = document.querySelector('#comment');
|
||||
let textarea = commentContainer.querySelector('textarea');
|
||||
$('.ts.tabbed.menu a.item[data-tab="preview"]').click(function() {
|
||||
let comment = textarea.value;
|
||||
if (comment.trim() != '') {
|
||||
// reset the container
|
||||
$('#preview').html('');
|
||||
parseMarkdown('preview', comment, {
|
||||
toc: false
|
||||
});
|
||||
postProcess();
|
||||
} else {
|
||||
$('#preview').html('Nothing to preview!');
|
||||
}
|
||||
});
|
||||
|
||||
$('#comment textarea').keydown(function (e) {
|
||||
if (e.ctrlKey && (e.keyCode == 10 || e.keyCode == 13)) { // Ctrl-Enter pressed; Chrome: keyCode == 10
|
||||
document.querySelector('#comment div[data-tab="textarea"] button.submit.positive').click(); // send comment
|
||||
}
|
||||
});
|
||||
|
||||
// Edit
|
||||
$('.ts.comments').on('click', '.edit', function(e) {
|
||||
if (!textarea.disabled) {
|
||||
let el = e.currentTarget;
|
||||
let id = el.dataset.comment;
|
||||
editorEditComment(textarea, id);
|
||||
} else {
|
||||
snackbar("你已被禁言。");
|
||||
}
|
||||
});
|
||||
|
||||
// Reply
|
||||
$('.ts.comments').on('click', '.reply', function(e) {
|
||||
if (!textarea.disabled) {
|
||||
let el = e.currentTarget;
|
||||
textarea.value += ` @${el.dataset.username} `;
|
||||
textarea.focus();
|
||||
} else {
|
||||
snackbar("你已被禁言。");
|
||||
}
|
||||
});
|
||||
|
||||
function editorInitialize(edtior) {
|
||||
delete commentContainer.dataset.editId;
|
||||
if ($('#comment .action.buttons button.cancel').length) {
|
||||
$('#comment .action.buttons button.cancel').remove();
|
||||
}
|
||||
if ($('#comment .menu .indicator').length) {
|
||||
$('#comment .menu .indicator').remove();
|
||||
}
|
||||
edtior.value = ""; // empty the textarea
|
||||
}
|
||||
|
||||
function editorEditComment(editor, commentId) {
|
||||
if (post.idList.indexOf(commentId) == -1) {
|
||||
snackbar('留言已刪除。');
|
||||
return undefined;
|
||||
}
|
||||
commentContainer.dataset.editId = commentId;
|
||||
if (!$('#comment .action.buttons button.cancel').length) {
|
||||
let cancelButton = document.createElement('button');
|
||||
cancelButton.classList.add('ts', 'cancel', 'button');
|
||||
cancelButton.innerText = "取消";
|
||||
commentContainer.querySelector('.action.buttons').appendChild(cancelButton);
|
||||
cancelButton.addEventListener('click', function () {
|
||||
editorInitialize(editor);
|
||||
});
|
||||
}
|
||||
if (!$('#comment .menu .indicator').length) {
|
||||
let indicator = document.createElement('div');
|
||||
indicator.classList.add('right', 'indicator', 'item');
|
||||
indicator.innerText = `Editing: ${commentId}`;
|
||||
commentContainer.querySelector('.menu').appendChild(indicator);
|
||||
} else {
|
||||
$('#comment .menu .indicator').text(`Editing: ${commentId}`);
|
||||
}
|
||||
editor.value = post.comments[post.idList.indexOf(commentId)].markdown;
|
||||
editor.focus();
|
||||
}
|
||||
|
||||
// Send Comment
|
||||
let commentLock = false;
|
||||
const commentRate = 10; // 1 comment per 10 seconds
|
||||
|
||||
$('#comment div[data-tab="textarea"] button.submit.positive').click(function() {
|
||||
var _this = this;
|
||||
let content = textarea.value;
|
||||
if (content.trim() == "") {
|
||||
snackbar("留言不能為空!");
|
||||
return false;
|
||||
}
|
||||
if (commentLock) {
|
||||
snackbar(`每 ${commentRate} 秒只能發一則留言。`);
|
||||
return false;
|
||||
} else if (!commentContainer.dataset.editId) {
|
||||
// only new comment should be limited
|
||||
commentLock = true;
|
||||
}
|
||||
|
||||
if (commentContainer.dataset.editId) {
|
||||
// edit comment
|
||||
var commentData = new URLSearchParams({
|
||||
"edit": commentContainer.dataset.editId,
|
||||
"content": content
|
||||
}).toString();
|
||||
} else {
|
||||
// new comment
|
||||
var commentData = new URLSearchParams({
|
||||
"pid": pid,
|
||||
"content": content
|
||||
}).toString();
|
||||
}
|
||||
|
||||
axios.request({
|
||||
method: "POST",
|
||||
url: "ajax/comment.php",
|
||||
data: commentData,
|
||||
responseType: "json"
|
||||
}).then(function (res) {
|
||||
editorInitialize(textarea);
|
||||
console.log(`Comment sent succeessfully! Comment id is ${res.data["comment_id"]}`);
|
||||
setTimeout(function() { commentLock = false }, commentRate * 1000); // sec -> microsecond
|
||||
fetchComments();
|
||||
}).catch(function (error) {
|
||||
commentLock = false; // unlock the textarea
|
||||
if (error.response) {
|
||||
let res = error.response;
|
||||
let data = res.data;
|
||||
console.error(`An error occurred while sending comments of pid ${pid}, status ${res.status}`);
|
||||
switch (data.status) {
|
||||
case "empty":
|
||||
snackbar("留言不能為空!");
|
||||
break;
|
||||
case "ratelimit":
|
||||
let remainSeconds = res.headers['retry-after'];
|
||||
snackbar(`每 ${commentRate} 秒只能發一則留言。請 ${remainSeconds} 秒後再試!`);
|
||||
break;
|
||||
case "muted":
|
||||
snackbar("你已被禁言。");
|
||||
$('#comment .ts.fluid.input').addClass('disabled');
|
||||
$(textarea).attr("placeholder", "你被禁言了。").val(""); // empty the textarea
|
||||
$(_this).addClass('disabled').text("你被禁言了");
|
||||
break;
|
||||
case "author":
|
||||
snackbar("你不能編輯別人的留言!");
|
||||
break;
|
||||
case "nologin":
|
||||
snackbar("請先登入。");
|
||||
break;
|
||||
default:
|
||||
snackbar("發送失敗。");
|
||||
break;
|
||||
}
|
||||
fetchComments();
|
||||
} else {
|
||||
console.error(`An error occurred while sending comments of pid ${pid}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
// Delete
|
||||
$('.ts.comments').on('click', '.delete', function(e) {
|
||||
let el = e.currentTarget;
|
||||
let id = el.dataset.comment;
|
||||
swal({
|
||||
type: 'question',
|
||||
title: '確定要刪除嗎?',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '確定',
|
||||
cancelButtonText: '取消',
|
||||
}).then((result) => {
|
||||
if (result.value) { // confirm
|
||||
axios.request({
|
||||
method: "GET",
|
||||
url: "ajax/comment.php?del=" + id,
|
||||
responseType: "json"
|
||||
}).then(function (_res) {
|
||||
fetchComments();
|
||||
}).catch(function (error) {
|
||||
if (error.response) {
|
||||
let res = error.response;
|
||||
console.error(`An error occurred while deleting comment of id ${id}, status ${res.status}`);
|
||||
} else {
|
||||
console.error(`An error occurred while deleting comment of id ${id}`);
|
||||
}
|
||||
snackbar('刪除失敗。');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function snackbar(message) {
|
||||
ts('.snackbar').snackbar({
|
||||
content: message
|
||||
});
|
||||
}
|
||||
176
include/js/edit.js
Normal file
176
include/js/edit.js
Normal file
@@ -0,0 +1,176 @@
|
||||
let edtior;
|
||||
editormd.urls = {
|
||||
atLinkBase : "user.php?username="
|
||||
};
|
||||
|
||||
// create editor instance
|
||||
editor = editormd('markdownEditor', {
|
||||
height: 450,
|
||||
path: "https://pandao.github.io/editor.md/lib/",
|
||||
markdown: document.edit.content.value,
|
||||
htmlDecode : "script,iframe|on*",
|
||||
placeholder: '',
|
||||
styleActiveLine: false,
|
||||
"font-size": '14px',
|
||||
emoji: true,
|
||||
taskList: true,
|
||||
tex: true,
|
||||
flowChart: true,
|
||||
sequenceDiagram: true,
|
||||
watch: false,
|
||||
lineNumbers: false,
|
||||
lineWrapping: false,
|
||||
toolbarAutoFixed: false,
|
||||
toolbarIcons : function() {
|
||||
return [
|
||||
"search", "|",
|
||||
"undo", "redo", "|",
|
||||
"bold", "del", "italic", "|",
|
||||
"list-ul", "list-ol", "emoji", "html-entities", "|",
|
||||
"link", "image", "|",
|
||||
"preview", "fullscreen", "||",
|
||||
"help", "info",
|
||||
]
|
||||
},
|
||||
toolbarIconsClass: {
|
||||
preview: 'fa-eye'
|
||||
},
|
||||
onload: function() {
|
||||
var __this__ = this;
|
||||
$('ul.editormd-menu').addClass('unstyled'); // remove style of TocasUI
|
||||
$('ul.editormd-menu i[name="emoji"]').parent().click(function () { // remove style of TocasUI from emoji tab (hack)
|
||||
setTimeout(()=>{ $('ul.editormd-tab-head').addClass('unstyled'); }, 300);
|
||||
});
|
||||
this.resize();
|
||||
loadDraft();
|
||||
|
||||
document.edit.title.addEventListener("keydown", function () {
|
||||
saveDraft(__this__);
|
||||
})
|
||||
|
||||
this.cm.on("change", function(_cm, _changeObj) {
|
||||
saveDraft(__this__);
|
||||
});
|
||||
},
|
||||
onresize: function() {
|
||||
if (this.state.preview) {
|
||||
requestAnimationFrame(()=>{
|
||||
this.previewed();
|
||||
this.previewing();
|
||||
});
|
||||
}
|
||||
},
|
||||
onpreviewing: function() {
|
||||
this.save();
|
||||
// use tocas-ui style tables
|
||||
$('table').each((_i,e) => {
|
||||
$(e).addClass('ts celled table').css('display','table');
|
||||
});
|
||||
|
||||
// prevent user from destroying page style
|
||||
var parser = new cssjs();
|
||||
let stylesheets = document.querySelectorAll('.markdown-body style');
|
||||
for (let style of stylesheets) {
|
||||
let ruleSource = style.innerHTML;
|
||||
let cssObject = parser.parseCSS(ruleSource);
|
||||
for (let rule of cssObject) {
|
||||
let valid = false;
|
||||
let validPrefix = [".markdown-body ", ".editormd-preview-container ", ".markdown-body.editormd-preview-container ", ".editormd-preview-container.markdown-body "];
|
||||
validPrefix.forEach((e, _i) => {
|
||||
valid = valid || rule.selector.startsWith(e);
|
||||
});
|
||||
|
||||
if (!rule.selector.startsWith('@')) { // '@keyframe' & '@import'
|
||||
if (!valid) {
|
||||
rule.selector = ".editormd-preview-container " + rule.selector;
|
||||
}
|
||||
}
|
||||
}
|
||||
style.innerHTML = parser.getCSSForEditor(cssObject);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// save draft data
|
||||
function saveDraft(editor) {
|
||||
localStorage.setItem('cavern_draft_title', document.edit.title.value);
|
||||
localStorage.setItem('cavern_draft_id', document.edit.pid.value);
|
||||
localStorage.setItem('cavern_draft_content', editor.getMarkdown());
|
||||
localStorage.setItem('cavern_draft_time', new Date().getTime());
|
||||
}
|
||||
|
||||
// Ask if user want to load draft
|
||||
function loadDraft() {
|
||||
if ($('#pid').val() == localStorage.getItem('cavern_draft_id')) {
|
||||
swal({
|
||||
type: 'question',
|
||||
title: '要載入上次備份嗎?',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '載入',
|
||||
cancelButtonText: '取消',
|
||||
}).then((result) => {
|
||||
if (result.value) { // confirm
|
||||
document.edit.title.value = localStorage.getItem('cavern_draft_title');
|
||||
editor.setValue(localStorage.getItem('cavern_draft_content'));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Post an article
|
||||
$(document.edit).on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
var _this = this;
|
||||
let fd = new URLSearchParams(new FormData(this)).toString();
|
||||
axios.request({
|
||||
method: "POST",
|
||||
data: fd,
|
||||
url: "post.php",
|
||||
headers: {
|
||||
'Content-Type': "application/x-www-form-urlencoded"
|
||||
}
|
||||
}).then(function (res) {
|
||||
if (_this.pid.value == localStorage.getItem('cavern_draft_id')) {
|
||||
['id', 'title', 'content', 'time'].forEach((name) => {
|
||||
localStorage.removeItem(`cavern_draft_${name}`);
|
||||
});
|
||||
}
|
||||
location.href = res.headers["axios-location"];
|
||||
}).catch(function (error) {
|
||||
if (error.response) {
|
||||
location.href = error.response.headers["axios-location"];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Delete post confirm message
|
||||
$('.action.column .delete').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
var el = this;
|
||||
var href = el.getAttribute('href');
|
||||
swal({
|
||||
type: 'question',
|
||||
title: '確定要刪除嗎?',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '確定',
|
||||
cancelButtonText: '取消',
|
||||
}).then((result) => {
|
||||
if (result.value) { // confirm
|
||||
axios.request({
|
||||
method: "GET",
|
||||
url: href
|
||||
}).then(function (res) {
|
||||
location.href = res.headers["axios-location"];
|
||||
}).catch(function (error){
|
||||
if (error.response) {
|
||||
location.href = error.response.headers["axios-location"];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// session keep alive
|
||||
setInterval(function() {
|
||||
$.get('ajax/posts.php');
|
||||
}, 5*60*1000);
|
||||
3
include/js/lib/css.min.js
vendored
Normal file
3
include/js/lib/css.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4604
include/js/lib/editormd.js
Normal file
4604
include/js/lib/editormd.js
Normal file
File diff suppressed because it is too large
Load Diff
74
include/js/lib/tocas.js
Normal file
74
include/js/lib/tocas.js
Normal file
@@ -0,0 +1,74 @@
|
||||
var Tocas,animationEnd,bindModalButtons,closeModal,contractDropdown,detectDropdown,expandDropdown,quadrant,slider_progressColor,slider_trackColor,z_dropdownActive,z_dropdownHovered,z_dropdownMenu;Tocas=(function(){var compact,dropzoneNumber,emptyArray,filter,isArray,isEmptyOrWhiteSpace,isObject,queue,slice,tocas,ts;ts=void 0;emptyArray=[];slice=emptyArray.slice;filter=emptyArray.filter;queue=[];tocas={};isArray=Array.isArray||function(obj){return obj instanceof Array;};isObject=function(obj){return obj instanceof Object;};isEmptyOrWhiteSpace=function(str){return str===null||str.match(/^\s*$/)!==null;};dropzoneNumber=0;compact=function(array){return filter.call(array,function(item){return item!==null;});};tocas.init=function(selector,context){var dom;dom=void 0;if(typeof selector==='string'){if(selector[0]==='<'){return tocas.fragment(selector);}
|
||||
selector=selector.trim();if(typeof context!=='undefined'){return ts(selector).find(context);}
|
||||
dom=tocas.select(document,selector);}else if(tocas.isTocas(selector)){return selector;}else{if(isArray(selector)){dom=compact(selector);}else if(isObject(selector)){dom=[selector];selector=null;}}
|
||||
return tocas.Tocas(dom,selector);};tocas.fragment=function(selector){var $element,attrObj,attrs,content,contentMatch,contentRegEx,hasAttr,hasContent,i,mainAll,mainAttrs,mainElement,match,noContent,regEx;noContent=/^<([^\/].*?)>$/;regEx=/(?:<)(.*?)( .*?)?(?:>)/;match=regEx.exec(selector);mainAll=match[0];mainElement=match[1];mainAttrs=match[2];hasAttr=typeof mainAttrs!=='undefined';hasContent=!mainAll.match(noContent);if(hasContent){contentRegEx=new RegExp(mainAll+'(.*?)(?:</'+mainElement+'>)$');contentMatch=contentRegEx.exec(selector);content=contentMatch[1];}
|
||||
if(hasAttr){attrs=mainAttrs.split(/(?:\s)?(.*?)=(?:"|')(.*?)(?:"|')/).filter(Boolean);attrObj={};i=0;while(i<attrs.length){if((i+2)%2===0){attrObj[attrs[i]]=attrs[i+1];}
|
||||
i++;}}
|
||||
$element=ts(document.createElement(mainElement));if(hasAttr){$element.attr(attrObj);}
|
||||
if(hasContent){$element.html(content);}
|
||||
return $element;};tocas.isTocas=function(obj){return obj instanceof tocas.Tocas;};tocas.select=function(element,selector){var e;try{return slice.call(element.querySelectorAll(selector));}catch(error){e=error;console.log('TOCAS ERROR: Something wrong while selecting '+selector+' element.');}};tocas.Tocas=function(dom,selector){dom=dom||[];dom.__proto__=ts.fn;dom.selector=selector||'';return dom;};ts=function(selector,context){if(typeof selector==='function'){document.addEventListener('DOMContentLoaded',selector);}else{return tocas.init(selector,context);}};ts.fn={each:function(callback){emptyArray.every.call(this,function(index,element){return callback.call(index,element,index)!==false;});return this;},slice:function(){return ts(slice.apply(this,arguments));},eq:function(index){return this.slice(index,index+1);}};if(!window.ts){window.ts=ts;}})(Tocas);ts.fn.on=function(eventName,selector,handler,once){var hasSelector;once=once||false;hasSelector=true;if(typeof selector!=='string'){hasSelector=false;handler=selector;}
|
||||
if(typeof handler!=='function'){once=handler;}
|
||||
return this.each(function(){var data,event,eventHandler,events,i;if(typeof this.addEventListener==='undefined'){console.log('TOCAS ERROR: Event listener is not worked with this element.');return false;}
|
||||
if(typeof this.ts_eventHandler==='undefined'){this.ts_eventHandler={};}
|
||||
events=eventName.split(' ');for(i in events){event=events[i];if(typeof this.ts_eventHandler[event]==='undefined'){this.ts_eventHandler[event]={registered:false,list:[]};}
|
||||
if(this.ts_eventHandler[event].registered===false){this.addEventListener(event,function(evt){var e,inSelector;if(typeof this.ts_eventHandler[event]!=='undefined'){for(e in this.ts_eventHandler[event].list){if(typeof this.ts_eventHandler[event].list[e].selector!=='undefined'){inSelector=false;ts(this.ts_eventHandler[event].list[e].selector).each(function(i,el){if(evt.target===el){inSelector=true;}});if(!inSelector){return;}}
|
||||
this.ts_eventHandler[event].list[e].func.call(this,evt);if(this.ts_eventHandler[event].list[e].once){delete this.ts_eventHandler[event].list[e];}}}});this.ts_eventHandler[event].registered=true;}
|
||||
eventHandler=this.ts_eventHandler[event].list;data={func:handler,once:once};if(hasSelector){data.selector=selector;}
|
||||
eventHandler.push(data);this.ts_eventHandler[event].list=eventHandler;}});};ts.fn.one=function(eventName,selector,handler){return this.each(function(){ts(this).on(eventName,selector,handler,true);});};ts.fn.off=function(eventName,handler){return this.each(function(){var e;if(typeof this.ts_eventHandler==='undefined'){return;}
|
||||
if(typeof this.ts_eventHandler[eventName]==='undefined'){return;}
|
||||
console.log(handler);if(typeof handler==='undefined'){this.ts_eventHandler[eventName].list=[];return;}
|
||||
for(e in this.ts_eventHandler[eventName].list){if(handler===this.ts_eventHandler[eventName].list[e].func){delete this.ts_eventHandler[eventName].list[e];}}});};ts.fn.css=function(property,value){var css,cssObject,i;css='';if(property!==null&&value!==null){css=property+':'+value+';';}else if(typeof property==='object'&&!Array.isArray(property)&&value===null){for(i in property){if(property.hasOwnProperty(i)){css+=i+':'+property[i]+';';}}}else if(Array.isArray(property)&&value===null){cssObject={};this.each(function(){var i;for(i in property){cssObject[property[i]]=ts(this).getCss(property[i]);}});return cssObject;}else if(property!==null&&value===null){return ts(this).getCss(property);}
|
||||
return this.each(function(){if(typeof this.style==='undefined'){return;}
|
||||
this.style.cssText=this.style.cssText+css;});};ts.fn.hasClass=function(classes){if(0 in this){if(this[0].classList){return this[0].classList.contains(classes);}else{return new RegExp('(^| )'+classes+'( |$)','gi').test(this[0].className);}}};ts.fn.classList=function(){var i;var classes,i;classes=[];if(0 in this){if(this[0].classList){i=0;while(i<this[0].classList.length){classes.push(this[0].classList[i]);i++;}}else{for(i in this[0].className.split(' ')){classes.push(this[0].className.split(' ')[i]);}}}
|
||||
return classes;};ts.fn.addClass=function(classes){if(classes===null){return;}
|
||||
return this.each(function(){var i,list;list=classes.split(' ');for(i in list){if(list[i]===''){i++;continue;}
|
||||
if(this.classList){this.classList.add(list[i]);}else{this.className+=' '+list[i];}}});};ts.fn.removeClass=function(classes){return this.each(function(){var i,list;if(!classes){this.className='';}else{list=classes.split(' ');for(i in list){if(list[i]===''){i++;continue;}
|
||||
if(this.classList){this.classList.remove(list[i]);}else if(typeof this.className!=='undefined'){this.className=this.className.replace(new RegExp('(^|\\b)'+classes.split(' ').join('|')+'(\\b|$)','gi'),' ');}}}});};ts.fn.toggleClass=function(classes){return this.each(function(){var i,index,list,objClassList;list=void 0;index=void 0;objClassList=void 0;list=classes.split(' ');for(i in list){if(this.classList){this.classList.toggle(list[i]);}else{objClassList=this.className.split(' ');index=list.indexOf(list[i]);if(index>=0){objClassList.splice(index,1);}else{objClassList.push(list[i]);}
|
||||
this.className=list[i].join(' ');}}});};ts.fn.getCss=function(property){var err;try{if(0 in this){return document.defaultView.getComputedStyle(this[0],null).getPropertyValue(property);}else{return null;}}catch(error){err=error;return null;}};ts.fn.remove=function(){return this.each(function(){this.parentNode.removeChild(this);});};ts.fn.children=function(){var list;list=[];this.each(function(i,el){list.push.apply(list,el.children);});return ts(list);};ts.fn.find=function(selector){var list;if(typeof selector!=='string'){return null;}
|
||||
list=[];this.each(function(i,el){list.push.apply(list,el.querySelectorAll(selector));});if(list.length){return ts(list);}else{return null;}};ts.fn.parent=function(){if(0 in this){return ts(this[0].parentNode);}else{return null;}};ts.fn.parents=function(selector){var selector;var selector;var parents,that;that=this;selector=selector||null;parents=[];if(selector!==null){selector=ts(selector);}
|
||||
while(that){that=ts(that).parent()[0];if(!that){break;}
|
||||
if(selector===null||selector!==null&&Array.prototype.indexOf.call(selector,that)!==-1){parents.push(that);}}
|
||||
return ts(parents);};ts.fn.closest=function(selector){var selector;var that;that=this;selector=ts(selector);while(true){that=ts(that).parent()[0];if(!that){return null;}
|
||||
if(Array.prototype.indexOf.call(selector,that)!==-1){return ts(that);}}};ts.fn.contains=function(wants){var isTrue,selector;selector=ts(wants);isTrue=false;this.each(function(i,el){var children,si;children=el.childNodes;si=0;while(si<selector.length){if(Array.prototype.indexOf.call(children,selector[si])!==-1){isTrue=true;}
|
||||
si++;}});return isTrue;};ts.fn.attr=function(attr,value){value=value===null?null:value;if(typeof attr==='object'&&!value){return this.each(function(){var i;for(i in attr){this.setAttribute(i,attr[i]);}});}else if(attr!==null&&typeof value!=='undefined'){return this.each(function(){this.setAttribute(attr,value);});}else if(attr!==null&&!value){if(0 in this){return this[0].getAttribute(attr);}else{return null;}}};ts.fn.removeAttr=function(attr){return this.each(function(){this.removeAttribute(attr);});};animationEnd='webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend';quadrant=function(el){var height,heightHalf,position,width,widthHalf;position=el.getBoundingClientRect();width=window.innerWidth;widthHalf=width / 2;height=window.innerHeight;heightHalf=height / 2;if(position.left<widthHalf&&position.top<heightHalf){return 2;}else if(position.left<widthHalf&&position.top>heightHalf){return 3;}else if(position.left>widthHalf&&position.top>heightHalf){return 4;}else if(position.left>widthHalf&&position.top<heightHalf){return 1;}};z_dropdownMenu=9;z_dropdownActive=10;z_dropdownHovered=11;slider_trackColor="#e9e9e9";slider_progressColor="rgb(150, 150, 150)";expandDropdown=function(target){return ts(target).css('z-index',z_dropdownActive).removeClass('hidden').addClass('visible').addClass('animating').one(animationEnd,function(){return ts(target).removeClass('animating');});};contractDropdown=function(target){return ts(target).css('z-index',z_dropdownMenu).removeClass('visible').addClass('hidden').addClass('animating').one(animationEnd,function(){return ts(target).removeClass('animating');});};detectDropdown=function(target,event){var hasDropdownParent,isDropdown,isDropdownIcon,isDropdownImage,isDropdownText,isItem,isTsMenuItem,parentIsItem,targetIsDropdown;isDropdown=ts(target).hasClass('dropdown');isDropdownText=ts(event.target).hasClass('text');isDropdownIcon=ts(event.target).hasClass('icon');isDropdownImage=ts(event.target).hasClass('image');hasDropdownParent=ts(event.target).parent().hasClass('dropdown');parentIsItem=ts(event.target).parent().hasClass('item');targetIsDropdown=ts(event.target).hasClass('dropdown');isItem=ts(event.target).hasClass('item');isTsMenuItem=ts(event.target).closest('.ts.menu');if((isTsMenuItem&&isDropdown&&parentIsItem&&targetIsDropdown)||(isTsMenuItem&&isDropdown&&!parentIsItem&&targetIsDropdown)||(isTsMenuItem&&isDropdown&&hasDropdownParent&&parentIsItem)){return expandDropdown(target);}else if((isDropdown&&isItem)||(isDropdown&&parentIsItem)){return contractDropdown('.ts.dropdown.visible');}else if(isDropdown&&isTsMenuItem){return expandDropdown(target);}else if(isDropdown&&targetIsDropdown){return expandDropdown(target);}else if(isDropdown&&isDropdownIcon&&hasDropdownParent){return expandDropdown(target);}else if(isDropdown&&isDropdownImage&&hasDropdownParent){return expandDropdown(target);}else if(isDropdown&&isDropdownText&&hasDropdownParent){return expandDropdown(target);}};ts(document).on('click',function(event){if(ts(event.target).closest('.dropdown:not(.basic)')===null&&!ts(event.target).hasClass('dropdown')){return contractDropdown('.ts.dropdown:not(.basic).visible');}});ts.fn.dropdown=function(command){return this.each(function(){return ts(this).on('click',function(e){ts(this).removeClass('upward downward leftward rightward');if(quadrant(this)===2){ts(this).addClass('downward rightward');}else if(quadrant(this)===3){ts(this).addClass('upward rightward');}else if(quadrant(this)===1){ts(this).addClass('downward leftward');}else if(quadrant(this)===4){ts(this).addClass('upward leftward');}
|
||||
contractDropdown('.ts.dropdown.visible');return detectDropdown(this,e);});});};ts.fn.checkbox=function(){return this.each(function(){return ts(this).on('click',function(e){var isRadio,name,tsThis;isRadio=ts(this).hasClass('radio');if(isRadio){tsThis=ts(this).find('input[type="radio"]');}else{tsThis=ts(this).find('input[type="checkbox"]');}
|
||||
if(tsThis===null){}else if(isRadio){name=tsThis.attr('name');ts(`input[type='radio'][name='${name}']`).removeAttr('checked');return tsThis.attr('checked','checked');}else{if(tsThis.attr('checked')==='checked'){return tsThis.removeAttr('checked');}else{return tsThis.attr('checked','checked');}}});});};ts.fn.tablesort=function(){return this.each(function(){var table;if(!ts(this).hasClass("sortable")){return;}
|
||||
table=this;return ts(this).find("thead th").each(function(i){return ts(this).on("click",function(){var isAsc,sortTable;isAsc=ts(this).hasClass('ascending');ts(this).closest('thead').find('th').removeClass('sorted ascending descending');sortTable=function(table,col,reverse){var element,j,len,results,tb,tr;tb=table.tBodies[0];tr=Array.prototype.slice.call(tb.rows,0);reverse=-((+reverse)||-1);tr=tr.sort(function(a,b){return reverse*(a.cells[col].textContent.trim().localeCompare(b.cells[col].textContent.trim()));});results=[];for(j=0,len=tr.length;j<len;j++){element=tr[j];results.push(tb.appendChild(element));}
|
||||
return results;};sortTable(table,i,isAsc);return ts(this).addClass(isAsc?'sorted descending':'sorted ascending');});});});};closeModal=function(modal){if(ts(modal).hasClass('opening')||ts(modal).hasClass('closing')){return;}
|
||||
ts(modal).closest('.ts.modals.dimmer').addClass('closing').one(animationEnd,function(){var dimmer;dimmer=this;return setTimeout(function(){ts(dimmer).removeClass('closing').removeClass('active');return ts('body').removeAttr('data-modal-lock');},30);});return ts(modal).addClass('closing').one(animationEnd,function(){return ts(this).removeClass('closing').removeAttr('open');});};bindModalButtons=function(modal,approve,deny,approveCallback,denyCalback,overwrite){var isset,tsApprove,tsDeny;tsApprove=ts(modal).find(approve);tsDeny=ts(modal).find(deny);isset=ts(modal).attr("data-modal-initialized")!==null;if(tsApprove!==null){if(overwrite){tsApprove.off('click');}
|
||||
if(overwrite||!isset&&!overwrite){tsApprove.on('click',function(){if(approveCallback.call(modal)!==false){return closeModal(modal);}});}}
|
||||
if(tsDeny!==null){if(overwrite){tsDeny.off('click');}
|
||||
if(overwrite||!isset&&!overwrite){tsDeny.on('click',function(){if(denyCalback.call(modal)!==false){return closeModal(modal);}});}}
|
||||
return ts(modal).attr('data-modal-initialized','true');};ts.fn.modal=function(option){return this.each(function(i){var approve,closeBtn,deny,modal,onApprove,onDeny,tsDimmer,tsModal;if(i>0||typeof this==='undefined'){return;}
|
||||
modal=this;tsModal=ts(this);tsDimmer=tsModal.closest('.ts.modals.dimmer');closeBtn=tsModal.find('.close.icon');if(tsDimmer===null){return;}
|
||||
if(option==='show'){ts('body').attr('data-modal-lock','true');tsDimmer.addClass('active').addClass('opening').one(animationEnd,function(){return ts(this).removeClass('opening');}).on('click',function(e){if(ts(modal).hasClass('closable')){if(e.target===this){return closeModal(modal);}}});if(closeBtn!==null){closeBtn.on('click',function(){return closeModal(modal);});}
|
||||
bindModalButtons(modal,'.positive, .approve, .ok','.negative, .deny, .cancel',function(){return true;},function(){return true;},false);return tsModal.attr('open','open').addClass('opening').one(animationEnd,function(){return tsModal.removeClass('opening');});}else if(option==='hide'){return closeModal(this);}else if(typeof option==='object'){approve=option.approve||'.positive, .approve, .ok';deny=option.deny||'.negative, .deny, .cancel';onDeny=option.onDeny||function(){return true;};onApprove=option.onApprove||function(){return true;};modal=this;return bindModalButtons(modal,approve,deny,onApprove,onDeny,true);}});};ts.fn.sidebar=function(options,selector,eventName){var closable,closeVisibleSidebars,dimPage,exclusive,pusher,scrollLock;dimPage=(options!=null?options.dimPage:void 0)||false;exclusive=(options!=null?options.exclusive:void 0)||false;scrollLock=(options!=null?options.scrollLock:void 0)||false;closable=(options!=null?options.closable:void 0)||true;pusher=document.querySelector('.pusher');closeVisibleSidebars=function(){ts('.ts.sidebar.visible:not(.static)').addClass('animating').removeClass('visible').one(animationEnd,function(){return ts(this).removeClass('animating');});return ts('.pusher').removeClass('dimmed').removeAttr('data-pusher-lock');};if(pusher.getAttribute('data-closable-bind')!=='true'){pusher.addEventListener('click',function(e){if(pusher.getAttribute('data-sidebar-closing')!=='true'){return closeVisibleSidebars();}});}
|
||||
pusher.setAttribute('data-closable-bind',true);return this.each(function(){var that;if(options==='toggle'||options==='hide'||options==='show'){ts(this).addClass('animating');pusher.setAttribute('data-sidebar-closing','true');setTimeout(function(){return pusher.removeAttribute('data-sidebar-closing');},300);if(this.getAttribute('data-dim-page')===null){this.setAttribute('data-dim-page',dimPage);}
|
||||
if(this.getAttribute('data-scroll-lock')===null){this.setAttribute('data-scroll-lock',scrollLock);}
|
||||
if(!ts(this).hasClass('visible')&&options==='hide'){ts(this).removeClass('animating');}
|
||||
if((ts(this).hasClass('visible')&&options==='toggle')||options==='hide'){ts('.pusher').removeClass('dimmed').removeAttr('data-pusher-lock');return ts(this).removeClass('visible').one(animationEnd,function(){return ts(this).removeClass('animating');});}else{if(this.getAttribute('data-exclusive')==='true'){closeVisibleSidebars();}
|
||||
if(this.getAttribute('data-dim-page')==='true'){ts('.pusher').addClass('dimmed');}
|
||||
if(this.getAttribute('data-scroll-lock')==='true'){ts('.pusher').attr('data-pusher-lock','true');}
|
||||
return ts(this).addClass('visible').removeClass('animating');}}else if(options==='attach events'){that=this;switch(eventName){case'show':return ts(selector).attr('data-sidebar-trigger','true').on('click',function(){return ts(that).sidebar('show');});case'hide':return ts(selector).attr('data-sidebar-trigger','true').on('click',function(){return ts(that).sidebar('hide');});case'toggle':return ts(selector).attr('data-sidebar-trigger','true').on('click',function(){return ts(that).sidebar('toggle');});}}else if(typeof options==='object'){this.setAttribute('data-closable',closable);this.setAttribute('data-scroll-lock',scrollLock);this.setAttribute('data-exclusive',exclusive);return this.setAttribute('data-dim-page',dimPage);}});};ts.fn.tab=function(option){return this.each(function(){var onSwitch;onSwitch=(option!=null?option.onSwitch:void 0)||function(){};return ts(this).on('click',function(){var tabGroup,tabName;if(ts(this).hasClass('active')){return;}
|
||||
tabName=ts(this).attr('data-tab');if(tabName===null){return;}
|
||||
tabGroup=ts(this).attr('data-tab-group');onSwitch(tabName,tabGroup);if(tabGroup===null){ts('[data-tab]:not(.tab):not([data-tab-group])').removeClass('active');ts('[data-tab]:not([data-tab-group])').removeClass('active');ts(`.tab[data-tab='${tabName}']:not([data-tab-group])`).addClass('active');}else{ts(`[data-tab-group='${tabGroup}']:not(.tab)`).removeClass('active');ts(`.tab[data-tab-group='${tabGroup}']`).removeClass('active');ts(`.tab[data-tab='${tabName}'][data-tab-group='${tabGroup}']`).addClass('active');}
|
||||
return ts(this).addClass('active');});});};ts.fn.popup=function(){return this.each(function(){var android,iOS,userAgent,winPhone;userAgent=navigator.userAgent||navigator.vendor||window.opera;winPhone=new RegExp("windows phone","i");android=new RegExp("android","i");iOS=new RegExp("iPad|iPhone|iPod","i");if(winPhone.test(userAgent)||android.test(userAgent)||(iOS.test(userAgent)&&!window.MSStream)){return ts(this).addClass('untooltipped');}});};ts.fn.slider=function(option){var counter,modify,outerCounter;outerCounter=option!=null?option.outerCounter:void 0;counter=option!=null?option.counter:void 0;modify=function(sliderEl,inputEl,counter,outerCounter){var counterEl,value;value=(inputEl.value-inputEl.getAttribute('min'))/(inputEl.getAttribute('max'-inputEl.getAttribute('min')));if(value===Number.POSITIVE_INFINITY){value=inputEl.value / 100;}
|
||||
if(counter!=null){counterEl=ts(sliderEl).find(counter);if(counterEl!=null){counterEl[0].innerText=inputEl.value;}}
|
||||
if(outerCounter!=null){ts(outerCounter).innerText=inputEl.value;}
|
||||
return ts(inputEl).css('background-image',`-webkit-gradient(linear,left top,right top,color-stop(${value},${slider_progressColor}),color-stop(${value},${slider_trackColor}))`);};return this.each(function(){var inputEl,sliderEl;sliderEl=this;inputEl=ts(this).find('input[type="range"]');modify(this,inputEl[0],counter,outerCounter);return inputEl.on('input',function(){return modify(sliderEl,this,counter,outerCounter);});});};ts.fn.editable=function(option){var autoClose,autoReplace,inputWrapper,onEdit,onEdited;autoReplace=(option!=null?option.autoReplace:void 0)||true;onEdit=(option!=null?option.onEdit:void 0)||function(){};onEdited=(option!=null?option.onEdited:void 0)||function(){};autoClose=(option!=null?option.autoClose:void 0)||true;inputWrapper=this;if(autoClose){ts(document).on('click',function(event){if(ts(event.target).closest('.ts.input')===null){return inputWrapper.each(function(){var contenteditable,input,text;input=ts(this).find('input');contenteditable=ts(this).find('[contenteditable]');text=ts(this).find('.text')[0];if(autoReplace){if(input!=null){text.innerText=input[0].value;}else if(contenteditable!=null){text.innerText=contenteditable[0].value;}}
|
||||
onEdited(this);return ts(this).removeClass('editing');});}});}
|
||||
return this.each(function(){var contenteditable,input;input=ts(this).find('input');contenteditable=ts(this).find('[contenteditable]');return ts(this).on('click',function(){ts(this).addClass('editing');onEdit(this);if(input!=null){return input[0].focus();}else if(contenteditable!=null){return contenteditable[0].focus();}});});};ts.fn.message=function(){return this.each(function(){return ts(this).find('i.close').on('click',function(){return ts(this).closest('.ts.message').addClass('hidden');});});};ts.fn.snackbar=function(option){var action,actionEmphasis,content,hoverStay,interval,onAction,onClose;content=(option!=null?option.content:void 0)||null;action=(option!=null?option.action:void 0)||null;actionEmphasis=(option!=null?option.actionEmphasis:void 0)||null;onClose=(option!=null?option.onClose:void 0)||function(){};onAction=(option!=null?option.onAction:void 0)||function(){};hoverStay=(option!=null?option.hoverStay:void 0)||false;interval=3500;if(content===null){return;}
|
||||
return this.each(function(){var ActionEl,close,contentEl,snackbar;snackbar=this;contentEl=ts(snackbar).find('.content');ActionEl=ts(snackbar).find('.action');ts(snackbar).removeClass('active animating').addClass('active animating').one(animationEnd,function(){return ts(this).removeClass('animating');}).attr('data-mouseon','false');contentEl[0].innerText=content;if(ActionEl!=null){ActionEl[0].innerText=action;}
|
||||
if((actionEmphasis!=null)&&(ActionEl!=null)){ActionEl.removeClass('primary info warning negative positive').addClass(actionEmphasis);}
|
||||
close=function(){ts(snackbar).removeClass('active').addClass('animating').one(animationEnd,function(){ts(this).removeClass('animating');return onClose(snackbar,content,action);});return clearTimeout(snackbar.snackbarTimer);};if(ActionEl!=null){ActionEl.off('click');ActionEl.on('click',function(){close();return onAction(snackbar,content,action);});}
|
||||
if(hoverStay){ts(snackbar).on('mouseenter',function(){return ts(this).attr('data-mouseon','true');});ts(snackbar).on('mouseleave',function(){return ts(this).attr('data-mouseon','false');});}
|
||||
clearTimeout(snackbar.snackbarTimer);return snackbar.snackbarTimer=setTimeout(function(){var hoverChecker;if(hoverStay){return hoverChecker=setInterval(function(){if(ts(snackbar).attr('data-mouseon')==='false'){close();return clearInterval(hoverChecker);}},600);}else{return close();}},interval);});};ts.fn.contextmenu=function(option){var menu;menu=(option!=null?option.menu:void 0)||null;ts(document).on('click',function(event){return ts('.ts.contextmenu.visible').removeClass('visible').addClass('hidden animating').one(animationEnd,function(){return ts(this).removeClass('visible animating downward upward rightward leftward');});});return this.each(function(){return ts(this).on('contextmenu',function(e){var h,r,w;event.preventDefault();ts(menu).addClass('visible');r=ts(menu)[0].getBoundingClientRect();ts(menu).removeClass('visible');w=window.innerWidth / 2;h=window.innerHeight / 2;ts(menu).removeClass('downward upward rightward leftward');if(e.clientX<w&&e.clientY<h){ts(menu).addClass('downward rightward').css('left',e.clientX+'px').css('top',e.clientY+'px');}else if(e.clientX<w&&e.clientY>h){ts(menu).addClass('upward rightward').css('left',e.clientX+'px').css('top',e.clientY-r.height+'px');}else if(e.clientX>w&&e.clientY>h){ts(menu).addClass('upward leftward').css('left',e.clientX-r.width+'px').css('top',e.clientY-r.height+'px');}else if(e.clientX>w&&e.clientY<h){ts(menu).addClass('downward leftward').css('left',e.clientX-r.width+'px').css('top',e.clientY+'px');}
|
||||
return ts(menu).removeClass('hidden').addClass('visible animating').one(animationEnd,function(){return ts(this).removeClass('animating');});});});};ts.fn.embed=function(option){return this.each(function(){var embedEl,icon,iconEl,id,options,placeholder,placeholderEl,query,source,url;source=this.getAttribute('data-source');url=this.getAttribute('data-url');id=this.getAttribute('data-id');placeholder=this.getAttribute('data-placeholder');options=this.getAttribute('data-options')||'';query=this.getAttribute('data-query')||'';icon=this.getAttribute('data-icon')||'video play';embedEl=this;if(this.getAttribute('data-embed-actived')){return;}
|
||||
if(query!==''){query='?'+query;}
|
||||
if(placeholder){placeholderEl=document.createElement('img');placeholderEl.src=placeholder;placeholderEl.className='placeholder';this.appendChild(placeholderEl);}
|
||||
if(icon&&(source||url||id)){iconEl=document.createElement('i');iconEl.className=icon+' icon';ts(iconEl).on('click',function(){var iframeEl,urlExtension,videoEl;urlExtension=url?url.split('.').pop():'';if(urlExtension.toUpperCase().indexOf('MOV')!==-1||urlExtension.toUpperCase().indexOf('MP4')!==-1||urlExtension.toUpperCase().indexOf('WEBM')!==-1||urlExtension.toUpperCase().indexOf('OGG')!==-1){videoEl=document.createElement('video');videoEl.src=url;if(options!==''){options.split(',').forEach(function(pair){var key,p,value;p=pair.split('=');key=p[0];value=p[1]||'';return videoEl.setAttribute(key.trim(),value.trim());});}
|
||||
ts(embedEl).addClass('active');return embedEl.appendChild(videoEl);}else{iframeEl=document.createElement('iframe');iframeEl.width='100%';iframeEl.height='100%';iframeEl.frameborder='0';iframeEl.scrolling='no';iframeEl.setAttribute('webkitAllowFullScreen','');iframeEl.setAttribute('mozallowfullscreen','');iframeEl.setAttribute('allowFullScreen','');if(source){switch(source){case'youtube':iframeEl.src='https://www.youtube.com/embed/'+id+query;break;case'vimeo':iframeEl.src='https://player.vimeo.com/video/'+id+query;}}else if(url){iframeEl.src=url+query;}
|
||||
ts(embedEl).addClass('active');return embedEl.appendChild(iframeEl);}});this.appendChild(iconEl);}
|
||||
return this.setAttribute('data-embed-actived','true');});};ts.fn.accordion=function(){};ts.fn.scrollspy=function(options){var anchors,container,target,tsTarget;target=document.querySelector(options.target);tsTarget=ts(target);container=this[0];anchors=document.querySelectorAll(`[data-scrollspy='${target.id}']`);if(this[0]===document.body){container=document;}
|
||||
return Array.from(anchors).forEach(function(element,index,array){var anchor,event,link;anchor=element;link=`[href='#${anchor.id}']`;event=function(){var containerRect,containerTop,continerIsBottom,length,rect;rect=anchor.getBoundingClientRect();if(container===document){containerRect=document.documentElement.getBoundingClientRect();continerIsBottom=document.body.scrollHeight-(document.body.scrollTop+window.innerHeight)===0;}else{containerRect=container.getBoundingClientRect();continerIsBottom=container.scrollHeight-(container.scrollTop+container.clientHeight)===0;}
|
||||
containerTop=containerRect.top<0?0:containerRect.top;if(rect.top-containerTop<10||(continerIsBottom&&(index===array.length-1))){tsTarget.find(link).addClass('active');length=tsTarget.find('.active').length;return tsTarget.find('.active').each(function(index){if(index!==length-1){return ts(this).removeClass('active');}});}else{return tsTarget.find(link).removeClass('active');}};event.call(this);container.addEventListener('scroll',event);return window.addEventListener('hashchange',event);});};
|
||||
127
include/js/lib/zh-tw.js
Normal file
127
include/js/lib/zh-tw.js
Normal file
@@ -0,0 +1,127 @@
|
||||
(function(){
|
||||
var factory = function (exports) {
|
||||
var lang = {
|
||||
name : "zh-tw",
|
||||
description : "開源在線Markdown編輯器<br/>Open source online Markdown editor.",
|
||||
tocTitle : "選單",
|
||||
toolbar : {
|
||||
undo : "復原(Ctrl+Z)",
|
||||
redo : "重做(Ctrl+Y)",
|
||||
bold : "粗體",
|
||||
del : "刪除線",
|
||||
italic : "斜體",
|
||||
quote : "引用",
|
||||
ucwords : "將所選的每個單字首字母轉成大寫",
|
||||
uppercase : "將所選文字轉成大寫",
|
||||
lowercase : "將所選文字轉成小寫",
|
||||
h1 : "標題1",
|
||||
h2 : "標題2",
|
||||
h3 : "標題3",
|
||||
h4 : "標題4",
|
||||
h5 : "標題5",
|
||||
h6 : "標題6",
|
||||
"list-ul" : "無序清單",
|
||||
"list-ol" : "有序清單",
|
||||
hr : "分隔線",
|
||||
link : "連結",
|
||||
"reference-link" : "引用連結",
|
||||
image : "圖片",
|
||||
code : "行內代碼",
|
||||
"preformatted-text" : "預格式文本 / 代碼塊(縮進風格)",
|
||||
"code-block" : "代碼塊(多語言風格)",
|
||||
table : "添加表格",
|
||||
datetime : "日期時間",
|
||||
emoji : "Emoji 表情",
|
||||
"html-entities" : "HTML 實體字符",
|
||||
pagebreak : "插入分頁符",
|
||||
watch : "關閉實時預覽",
|
||||
unwatch : "開啟實時預覽",
|
||||
preview : "預覽(按 Shift + ESC 退出)",
|
||||
fullscreen : "全螢幕(按 ESC 退出)",
|
||||
clear : "清空",
|
||||
search : "搜尋",
|
||||
help : "幫助",
|
||||
info : "關於" + exports.title
|
||||
},
|
||||
buttons : {
|
||||
enter : "確定",
|
||||
cancel : "取消",
|
||||
close : "關閉"
|
||||
},
|
||||
dialog : {
|
||||
link : {
|
||||
title : "添加連結",
|
||||
url : "連結位址",
|
||||
urlTitle : "連結標題",
|
||||
urlEmpty : "錯誤:請填寫連結位址。"
|
||||
},
|
||||
referenceLink : {
|
||||
title : "添加引用連結",
|
||||
name : "引用名稱",
|
||||
url : "連結位址",
|
||||
urlId : "連結ID",
|
||||
urlTitle : "連結標題",
|
||||
nameEmpty: "錯誤:引用連結的名稱不能為空。",
|
||||
idEmpty : "錯誤:請填寫引用連結的ID。",
|
||||
urlEmpty : "錯誤:請填寫引用連結的URL地址。"
|
||||
},
|
||||
image : {
|
||||
title : "添加圖片",
|
||||
url : "圖片位址",
|
||||
link : "圖片連結",
|
||||
alt : "圖片描述",
|
||||
uploadButton : "本地上傳",
|
||||
imageURLEmpty : "錯誤:圖片地址不能為空。",
|
||||
uploadFileEmpty : "錯誤:上傳的圖片不能為空!",
|
||||
formatNotAllowed : "錯誤:只允許上傳圖片文件,允許上傳的圖片文件格式有:"
|
||||
},
|
||||
preformattedText : {
|
||||
title : "添加預格式文本或代碼塊",
|
||||
emptyAlert : "錯誤:請填寫預格式文本或代碼的內容。"
|
||||
},
|
||||
codeBlock : {
|
||||
title : "添加代碼塊",
|
||||
selectLabel : "代碼語言:",
|
||||
selectDefaultText : "請語言代碼語言",
|
||||
otherLanguage : "其他語言",
|
||||
unselectedLanguageAlert : "錯誤:請選擇代碼所屬的語言類型。",
|
||||
codeEmptyAlert : "錯誤:請填寫代碼內容。"
|
||||
},
|
||||
htmlEntities : {
|
||||
title : "HTML實體字符"
|
||||
},
|
||||
help : {
|
||||
title : "幫助"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.defaults.lang = lang;
|
||||
};
|
||||
|
||||
// CommonJS/Node.js
|
||||
if (typeof require === "function" && typeof exports === "object" && typeof module === "object")
|
||||
{
|
||||
module.exports = factory;
|
||||
}
|
||||
else if (typeof define === "function") // AMD/CMD/Sea.js
|
||||
{
|
||||
if (define.amd) { // for Require.js
|
||||
|
||||
define(["editormd"], function(editormd) {
|
||||
factory(editormd);
|
||||
});
|
||||
|
||||
} else { // for Sea.js
|
||||
define(function(require) {
|
||||
var editormd = require("../editormd");
|
||||
factory(editormd);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
factory(window.editormd);
|
||||
}
|
||||
|
||||
})();
|
||||
46
include/js/like.js
Normal file
46
include/js/like.js
Normal file
@@ -0,0 +1,46 @@
|
||||
$('#content').on('click', 'button.like.button', function(e){
|
||||
var el = e.currentTarget;
|
||||
var id = el.dataset.id;
|
||||
axios.request({
|
||||
method: "GET",
|
||||
url: "./ajax/like.php?pid=" + id,
|
||||
responseType: "json",
|
||||
}).then(function (res) {
|
||||
var data = res.data;
|
||||
if (data.status == true) {
|
||||
$(`button.like.button[data-id="${data.id}"]`).html(
|
||||
'<i class="thumbs up icon"></i> ' + data.likes
|
||||
);
|
||||
} else if (data.status == false) {
|
||||
$(`button.like.button[data-id="${data.id}"]`).html(
|
||||
'<i class="thumbs outline up icon"></i> ' + data.likes
|
||||
);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
if (error.response) {
|
||||
let data = error.response.data;
|
||||
if (data.status == 'nologin') {
|
||||
$(`button.like.button[data-id="${data.id}"]`).html(
|
||||
'<i class="thumbs outline up icon"></i> ' + data.likes
|
||||
);
|
||||
swal({
|
||||
type: 'warning',
|
||||
title: '請先登入!',
|
||||
text: '登入以按讚或發表留言。',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '登入',
|
||||
cancelButtonText: '取消',
|
||||
}).then((result) => {
|
||||
if (result.value) { // confirm
|
||||
location.href = 'login.php';
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$(`button.like.button[data-id="${id}"]`).html(
|
||||
'<i class="thumbs outline up icon"></i> ' + "--"
|
||||
);
|
||||
console.error(`An error occurred when get likes of pid ${id}, status ${error.response.status}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
40
include/js/markdown.js
Normal file
40
include/js/markdown.js
Normal file
@@ -0,0 +1,40 @@
|
||||
editormd.urls = {
|
||||
atLinkBase : "user.php?username="
|
||||
};
|
||||
|
||||
function postProcess(...callbacks) {
|
||||
tableStyling();
|
||||
linkSanitize();
|
||||
|
||||
callbacks.forEach(func => {
|
||||
func();
|
||||
});
|
||||
|
||||
function linkSanitize() {
|
||||
$('.markdown-body a').each((_i, e) => {
|
||||
href = (e.getAttribute('href')) ? _.unescape(e.getAttribute('href').toLowerCase()) : "";
|
||||
if (href.indexOf('javascript:') != -1) {
|
||||
e.setAttribute('href', '#');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function tableStyling() {
|
||||
$('table').each((_i,e) => {
|
||||
$(e).addClass("ts celled table").css('display', 'table').wrap('<div class="table wrapper"></div>');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function parseMarkdown(id, markdown, options) {
|
||||
let defaultOptions = {
|
||||
htmlDecode : "script,iframe|on*",
|
||||
toc: true,
|
||||
emoji: true,
|
||||
taskList: true,
|
||||
tex: true,
|
||||
flowChart: true,
|
||||
sequenceDiagram: true
|
||||
}
|
||||
return editormd.markdownToHTML(id, $.extend(true, defaultOptions, options, { markdown: markdown }));
|
||||
}
|
||||
126
include/js/notification.js
Normal file
126
include/js/notification.js
Normal file
@@ -0,0 +1,126 @@
|
||||
var notifications = {
|
||||
toFetch: true,
|
||||
unreadCount: 0,
|
||||
feeds: []
|
||||
};
|
||||
|
||||
$('#menu .notification.icon.item').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
let el = e.currentTarget;
|
||||
|
||||
let $wrapper = $('#notification-wrapper');
|
||||
let $container = $('.notification.container');
|
||||
|
||||
if ($container.hasClass('active')) {
|
||||
// dismiss the notification window
|
||||
$('.notification.click.handler').remove();
|
||||
} else {
|
||||
// render the notification window
|
||||
let handler = document.createElement('div')
|
||||
handler.className = "notification click handler";
|
||||
$wrapper.after(handler);
|
||||
handler.addEventListener('click', function (e) {
|
||||
el.click();
|
||||
});
|
||||
setNotificationCounter(0); // remove counter
|
||||
if (notifications.toFetch){
|
||||
fetchNotification();
|
||||
}
|
||||
}
|
||||
|
||||
el.classList.toggle('active');
|
||||
$container.toggleClass('active');
|
||||
});
|
||||
|
||||
function fetchNotificationCount() {
|
||||
axios.request({
|
||||
method: 'GET',
|
||||
url: "./ajax/notification.php?count",
|
||||
responseType: 'json'
|
||||
}).then(function (res) {
|
||||
let count = res.data['unread_count'];
|
||||
setNotificationCounter(count);
|
||||
if (count != notifications.unreadCount) {
|
||||
// if count changes, fetching notifications while next click
|
||||
notifications.toFetch = true;
|
||||
notifications.unreadCount = count;
|
||||
}
|
||||
}).catch(function (_error) {
|
||||
console.error("Error occurred while fetching notification count.");
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('load', function () {
|
||||
fetchNotificationCount();
|
||||
});
|
||||
var notificationFetchTimer = setInterval(fetchNotificationCount, 1 * 5 * 1000); // fetch notification count every 1 minute
|
||||
|
||||
function fetchNotification() {
|
||||
axios.request({
|
||||
method: 'GET',
|
||||
url: './ajax/notification.php?fetch',
|
||||
responseType: 'json'
|
||||
}).then(function (res) {
|
||||
parseNotification(res.data);
|
||||
notifications.toFetch = false;
|
||||
}).catch(function (error) {
|
||||
console.log("Error occurred while fetching notification count.");
|
||||
});
|
||||
}
|
||||
|
||||
function parseNotification(data) {
|
||||
const feedTemplate = `<div class="event"><div class="label"><i class="volume up icon"></i></div><div class="content"><div class="date">{{ time }}</div><div class="summary">{{ message }}</div></div></div>`;
|
||||
let $feed = $('.ts.feed');
|
||||
|
||||
$feed.html(""); // container clean up
|
||||
|
||||
for (f of data.feeds) {
|
||||
let message = parseMessage(f.message, f.url);
|
||||
let node = feedTemplate.replace("{{ time }}", f.time).replace("{{ message }}", message);
|
||||
$node = $(node).appendTo($feed);
|
||||
|
||||
if (f.read == 0) {
|
||||
$node.addClass('unread');
|
||||
}
|
||||
}
|
||||
|
||||
notifications.feeds = data.feeds; // cache data
|
||||
|
||||
function parseMessage(message, url) {
|
||||
let regex = {
|
||||
"username": /\{([^\{\}]+)\}@(\w+)/g,
|
||||
"url": /\[([^\[\[]*)\]/g
|
||||
};
|
||||
|
||||
return message.replace(regex.username, function (_match, name, id, _offset, _string) {
|
||||
return `<a href="user.php?username=${id}">${name}</a>`;
|
||||
}).replace(regex.url, function (_match, title, _offset, _string) {
|
||||
return `<a href="${url}">${title}</a>`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setNotificationCounter(count) {
|
||||
let $notify = $('#menu .notification.icon.item');
|
||||
let $icon = $notify.children('i.icon');
|
||||
let $counter = $notify.children('span.counter');
|
||||
|
||||
if (count == 0) {
|
||||
if ($counter.length) {
|
||||
$counter.remove();
|
||||
}
|
||||
$icon.toggleClass('outline', true); // set icon style
|
||||
} else {
|
||||
if ($counter.length) {
|
||||
$counter.text(count);
|
||||
} else {
|
||||
let counter = document.createElement('span');
|
||||
counter.className = "counter";
|
||||
counter.textContent = count;
|
||||
$notify.append(counter);
|
||||
}
|
||||
$icon.toggleClass('outline', false); // set icon style
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
86
include/js/post.js
Normal file
86
include/js/post.js
Normal file
@@ -0,0 +1,86 @@
|
||||
const cdnjs = "https://cdnjs.cloudflare.com/ajax/libs";
|
||||
|
||||
// Load Libraries
|
||||
const libraries = [
|
||||
cdnjs + "/marked/0.5.1/marked.min.js",
|
||||
cdnjs + "/prettify/r298/prettify.min.js",
|
||||
cdnjs + "/raphael/2.2.7/raphael.min.js",
|
||||
cdnjs + "/underscore.js/1.9.1/underscore-min.js",
|
||||
cdnjs + "/flowchart/1.11.3/flowchart.min.js",
|
||||
"https://pandao.github.io/editor.md/lib/jquery.flowchart.min.js",
|
||||
cdnjs + "/js-sequence-diagrams/1.0.6/sequence-diagram-min.js"
|
||||
];
|
||||
|
||||
loadJS(libraries).then(function () {
|
||||
editormd.$marked = marked;
|
||||
editormd.loadFiles.js.push(...libraries.map(url => url.slice(0, -3))); // remove ".js"
|
||||
parsePost();
|
||||
fetchComments();
|
||||
postProcess(sanitizeStyleTag());
|
||||
|
||||
function sanitizeStyleTag() { // prevent the style tag in post from destorying the style of page
|
||||
return function() {
|
||||
var parser = new cssjs();
|
||||
let stylesheets = document.querySelectorAll('#post style');
|
||||
for (let style of stylesheets) {
|
||||
let ruleSource = style.innerHTML;
|
||||
let cssObject = parser.parseCSS(ruleSource);
|
||||
for (let rule of cssObject) {
|
||||
let valid = false;
|
||||
let validPrefix = ["#post ", "#post.markdown-body ", "#post.editormd-html-preview "];
|
||||
validPrefix.forEach((e, _i) => {
|
||||
valid = valid || rule.selector.startsWith(e);
|
||||
});
|
||||
|
||||
if (!rule.selector.startsWith('@')) { // '@keyframe' & '@import'
|
||||
if (valid) {
|
||||
// do nothing
|
||||
} else if (rule.selector.startsWith('.markdown-body ') || rule.selector.startsWith(".editormd-html-preview")) {
|
||||
rule.selector = "#post" + rule.selector;
|
||||
} else {
|
||||
rule.selector = "#post " + rule.selector;
|
||||
}
|
||||
}
|
||||
}
|
||||
style.innerHTML = parser.getCSSForEditor(cssObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function parsePost() {
|
||||
var postContent = document.querySelector('#post .markdown').textContent;
|
||||
|
||||
if (postContent.search(/.{0}\[TOC\]\n/) != -1) { // if TOC is used in post
|
||||
$('#sidebar .ts.fluid.input').after(`<div class="ts tertiary top attached center aligned segment">目錄</div><div class="ts bottom attached loading segment" id="toc"></div>`);
|
||||
}
|
||||
|
||||
parseMarkdown('post', postContent, {
|
||||
tocDropdown: false,
|
||||
tocContainer: '#toc'
|
||||
}).children('.markdown').hide();
|
||||
$('#toc').removeClass('loading');
|
||||
}
|
||||
|
||||
// Delete post confirm message
|
||||
$('.action.column .delete').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
var el = this;
|
||||
var next = el.getAttribute('href');
|
||||
swal({
|
||||
type: 'question',
|
||||
title: '確定要刪除嗎?',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '確定',
|
||||
cancelButtonText: '取消',
|
||||
}).then((result) => {
|
||||
if (result.value) { // confirm
|
||||
axios.request({
|
||||
method: "GET",
|
||||
url: next
|
||||
}).then(function (res) {
|
||||
location.href = res.headers["axios-location"];
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
19
include/js/security.js
Normal file
19
include/js/security.js
Normal file
@@ -0,0 +1,19 @@
|
||||
axios.defaults.withCredentials = true;
|
||||
|
||||
axios.interceptors.request.use(function (config) {
|
||||
var crypto = window.crypto || window.msCrypto;
|
||||
let csrfToken = btoa(String(crypto.getRandomValues(new Uint32Array(1))[0]));
|
||||
document.cookie = `${axios.defaults.xsrfCookieName}=${csrfToken}`;
|
||||
return config;
|
||||
}, function (error) {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
$("#logout").on("click", function (e) {
|
||||
e.preventDefault();
|
||||
axios.get("login.php?logout").then(function (res) {
|
||||
location.href = res.headers["axios-location"];
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
});
|
||||
});
|
||||
21
include/notification.php
Normal file
21
include/notification.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
function cavern_notify_user($username, $message="你有新的通知!", $url="", $type="") {
|
||||
global $SQL;
|
||||
$time = date('Y-m-d H:i:s');
|
||||
$SQL->query("INSERT INTO `notification` (`username`, `message`, `url`, `type`, `time`) VALUES ('%s', '%s', '%s', '%s', '%s')", array($username, $message, $url, $type, $time));
|
||||
}
|
||||
|
||||
function parse_user_tag($markdown) {
|
||||
$regex = array(
|
||||
"code_block" => "/(`{1,3}[^`]*`{1,3})/",
|
||||
"email" => "/[^@\s]*@[^@\s]*\.[^@\s]*/",
|
||||
"username" => "/@(\w+)/"
|
||||
);
|
||||
|
||||
$tmp = preg_replace($regex["code_block"], " ", $markdown);
|
||||
$tmp = preg_replace($regex["email"], " ", $tmp);
|
||||
|
||||
preg_match_all($regex["username"], $tmp, $username_list);
|
||||
|
||||
return array_unique($username_list[1]);
|
||||
}
|
||||
9
include/security.php
Normal file
9
include/security.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
function validate_csrf() {
|
||||
if (isset($_COOKIE["XSRF-TOKEN"]) && isset($_SERVER["HTTP_X_XSRF_TOKEN"]) && ($_COOKIE["XSRF-TOKEN"] === $_SERVER["HTTP_X_XSRF_TOKEN"])) {
|
||||
return TRUE;
|
||||
} else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
?>
|
||||
65
include/user.php
Normal file
65
include/user.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
class NoUserException extends Exception {}
|
||||
|
||||
class User {
|
||||
private $valid = false;
|
||||
private $islogin = false;
|
||||
|
||||
private $username;
|
||||
private $name;
|
||||
private $level;
|
||||
private $muted;
|
||||
private $email;
|
||||
|
||||
public function __construct($username="") {
|
||||
// if $username is empty indicates that user is not logged in
|
||||
if ($username !== "") {
|
||||
// the user might be banned or removed, so we validate him here
|
||||
$query = cavern_query_result("SELECT * FROM `user` WHERE `username` = '%s'", array($username));
|
||||
|
||||
if ($query['num_rows'] > 0){
|
||||
$this->valid = true;
|
||||
|
||||
$data = $query['row'];
|
||||
$this->username = $data["username"];
|
||||
$this->name = $data['name'];
|
||||
$this->level = $data['level'];
|
||||
$this->muted = ($data['muted'] == 1 ? true : false);
|
||||
$this->email = $data['email'];
|
||||
} else {
|
||||
throw new NoUserException($username);
|
||||
}
|
||||
|
||||
if ($this->username === @$_SESSION["cavern_username"]) {
|
||||
$this->islogin = true;
|
||||
}
|
||||
} else {
|
||||
// even though the user hasn't logged in, he is still a valid user
|
||||
$this->username = "";
|
||||
$this->valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function __get($name) {
|
||||
return $this->$name;
|
||||
}
|
||||
}
|
||||
|
||||
function validate_user() {
|
||||
if (isset($_SESSION['cavern_username'])) {
|
||||
$username = $_SESSION['cavern_username'];
|
||||
} else {
|
||||
$username = "";
|
||||
}
|
||||
|
||||
try {
|
||||
$user = new User($username);
|
||||
} catch (NoUserException $e) {}
|
||||
|
||||
if (!$user->valid) {
|
||||
session_destroy();
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
?>
|
||||
100
include/view.php
Normal file
100
include/view.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/* Cavern Edition
|
||||
modified by t510599 at 2019/05/30
|
||||
*/
|
||||
/*
|
||||
<Secret Blog>
|
||||
Copyright (C) 2012-2017 太陽部落格站長 Secret <http://gdsecret.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, version 3.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class View {
|
||||
private $master_content;
|
||||
private $nav_content;
|
||||
private $sidebar_content;
|
||||
private $message = array();
|
||||
private $script = array();
|
||||
private $title;
|
||||
private $part;
|
||||
|
||||
public function __construct($master,$nav,$sidebar,$title,$part) {
|
||||
$this->load($master,$nav,$sidebar);
|
||||
$this->title = $title;
|
||||
$this->part = $part;
|
||||
ob_start();
|
||||
}
|
||||
|
||||
private function load($master,$nav,$sidebar) {
|
||||
ob_start();
|
||||
include($master);
|
||||
$this->master_content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
ob_start();
|
||||
include($nav);
|
||||
$this->nav_content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
if ($sidebar!='') {
|
||||
ob_start();
|
||||
include($sidebar);
|
||||
$this->sidebar_content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
}
|
||||
}
|
||||
|
||||
public function add_script($src) {
|
||||
$this->script[] = "<script src=\"$src\"></script>";
|
||||
}
|
||||
|
||||
public function add_script_source($source) {
|
||||
$this->script[] = "<script>$source</script>";
|
||||
}
|
||||
|
||||
public function show_message($class, $msg) {
|
||||
$this->message[] = "<div class=\"ts $class message\"><p>$msg</p></div>";
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
echo strtr($this->master_content, array(
|
||||
'{title}' => $this->title,
|
||||
'{part}' => $this->part,
|
||||
'{script}' => join(PHP_EOL, $this->script),
|
||||
'{nav}' => $this->nav_content,
|
||||
'{sidebar}' => $this->sidebar_content,
|
||||
'{message}' => join(PHP_EOL, $this->message),
|
||||
'{content}' => $content
|
||||
));
|
||||
@ob_flush();
|
||||
flush();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user