Compare commits

..
12 Commits
41 changed files with 1246 additions and 532 deletions

No files matched your search

+12 -9
View File
@@ -32,9 +32,9 @@
## 食用
> 生产环境版下载:[Artalk.js](https://raw.githubusercontent.com/qwqcode/Artalk/master/dist/Artalk.js) | [Artalk.css](https://raw.githubusercontent.com/qwqcode/Artalk/master/dist/Artalk.css)
> 前端资源下载:[Artalk.js](./dist/Artalk.js) | [Artalk.css](./dist/Artalk.css)
1. 部署 Artalk 的后端,[PHP 版传送门](https://github.com/qwqcode/Artalk-API-PHP)
1. 部署 Artalk 的后端,传送门:[PHP API](https://github.com/qwqcode/Artalk-API-PHP) / Go API / Node API / Python API
2. 前端页面配置 Artalk 如下:
```html
@@ -43,19 +43,20 @@
<head>
<!-- ... -->
<link href="dist/Artalk.css" rel="stylesheet">
<script type="text/javascript" src="dist/Artalk.js"></script>
</head>
<body>
<div id="ArtalkComments"></div>
<!-- ... -->
<script src="dist/Artalk.js"></script>
<script>
new Artalk({
el: '#ArtalkComments',
placeholder: '来啊,快活啊 (/ω\)',
noComment: '快来成为第一个评论的人吧~',
defaultAvatar: 'mp',
el: '#ArtalkComments', // 元素选择
placeholder: '来啊,快活啊 ( ゜- ゜)', // 占位符
noComment: '快来成为第一个评论的人吧~', // 无评论时显示
defaultAvatar: 'mp', // 参考 https://cn.gravatar.com/site/implement/images/#default-image
pageKey: '[页面唯一标识]',
serverUrl: '[后端程序URL]',
readMore: {
readMore: { // 阅读更多配置
pageSize: 15, // 每次请求获取评论数
autoLoad: true // 滚动到底部自动加载
}
@@ -65,7 +66,9 @@
</html>
```
更多配置项详见 [types/artalk-config.d.ts](./types/artalk-config.d.ts)
前端更多栗子,请参考 [/example/](./example/) 目录
前端更多配置项详见 [/types/artalk-config.d.ts](./types/artalk-config.d.ts)
## 开发
+2 -2
View File
@@ -1,5 +1,5 @@
/*!
* Artalk v1.0.4
* Artalk v1.0.6
* (c) 2016-2020 qwqaq.com
* Link: https://github.com/qwqcode/Artalk
*/.artalk{position:relative;width:100%;min-height:200px}.artalk *,.artalk-layer-wrap *{line-height:1.6em;color:#565a5f;font-family:Helvetica Neue,PingFangSC-Light,Hiragino Sans GB,Microsoft YaHei,WenQuanYi Micro Hei,sans-serif;word-wrap:break-word}.artalk-layer-wrap pre,.artalk pre{line-height:1.6em;background-color:#f6f6f6;color:#555;padding:5px 10px;border-radius:2px;margin:0;font-family:Source Code Pro,courier new,Input Mono,PT Mono,SFMono-Regular,Consolas,Monaco,Menlo,PingFang SC,Liberation Mono,Microsoft YaHei,Courier,monospace}.artalk-layer-wrap a,.artalk a{color:#0083ff;text-decoration:none}.artalk-layer-wrap blockquote,.artalk blockquote{position:static;margin:10px 0;padding:10px 20px;background:#fcfcfc;border-left:4px solid #eee}.artalk-layer-wrap p:first-child,.artalk p:first-child{margin-top:0}.artalk-layer-wrap p:last-child,.artalk p:last-child{margin-bottom:0}.artalk *,.artalk-layer-wrap *{box-sizing:border-box}.artalk-error-layer,.artalk-layer-dialog-wrap,.artalk-loading,.full-layer{width:100%;height:100%;position:absolute;top:0;left:0;background:#fff;z-index:10;align-items:center;justify-content:center;flex-flow:column;display:flex}.artalk-loading{z-index:11}.artalk-loading-spinner{position:relative;width:50px;height:50px}.artalk-loading-spinner svg{-webkit-animation:artalkRotate 2s linear infinite;animation:artalkRotate 2s linear infinite;transform-origin:center center;width:100%;height:100%;position:absolute;top:0;left:0}.artalk-loading-spinner svg circle{stroke-dasharray:1,200;stroke-dashoffset:0;-webkit-animation:artalkDash 1.5s ease-in-out infinite,artalkColor 6s ease-in-out infinite;animation:artalkDash 1.5s ease-in-out infinite,artalkColor 6s ease-in-out infinite;stroke-linecap:round}@-webkit-keyframes artalkRotate{to{transform:rotate(1turn)}}@keyframes artalkRotate{to{transform:rotate(1turn)}}@-webkit-keyframes artalkDash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:89,200;stroke-dashoffset:-35px}to{stroke-dasharray:89,200;stroke-dashoffset:-124px}}@keyframes artalkDash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:89,200;stroke-dashoffset:-35px}to{stroke-dasharray:89,200;stroke-dashoffset:-124px}}@-webkit-keyframes artalkColor{0%,to{stroke:#ff5652}40%{stroke:#2196f3}66%{stroke:#32c787}80%,90%{stroke:#ffc107}}@keyframes artalkColor{0%,to{stroke:#ff5652}40%{stroke:#2196f3}66%{stroke:#32c787}80%,90%{stroke:#ffc107}}@-webkit-keyframes artalkLoadingIconRotate{0%{transform:rotate(0)}50%{transform:rotate(180deg)}to{transform:rotate(1turn)}}@keyframes artalkLoadingIconRotate{0%{transform:rotate(0)}50%{transform:rotate(180deg)}to{transform:rotate(1turn)}}.artalk-loading-icon{border-radius:100%;margin:0;border:1px solid #0083ff;border-bottom-color:transparent;height:15px;width:15px;background:0 0!important;display:inline-block;-webkit-animation:artalkLoadingIconRotate .75s linear 0s infinite;animation:artalkLoadingIconRotate .75s linear 0s infinite;vertical-align:middle}.artalk-fade-in{-webkit-animation:artalkFadeIn .3s both;animation:artalkFadeIn .3s both}.artalk-fade-out{-webkit-animation:artalkFadeOut .2s both;animation:artalkFadeOut .2s both}@-webkit-keyframes artalkFadeIn{0%{opacity:0}to{opacity:1}}@keyframes artalkFadeIn{0%{opacity:0}to{opacity:1}}@-webkit-keyframes artalkFadeOut{to{opacity:0}}@keyframes artalkFadeOut{to{opacity:0}}.artalk-error-layer{background-color:hsla(0,0%,100%,.94)}.artalk-error-layer .artalk-error-title{color:#ff5652}.artalk-error-layer .artalk-error-text{text-align:center}.artalk-layer-dialog-wrap{background-color:hsla(0,0%,100%,.94)}.artalk-layer-dialog-wrap>.artalk-layer-dialog{width:30%}.artalk-layer-dialog-wrap>.artalk-layer-dialog>.artalk-layer-dialog-content .artalk-captcha-img{cursor:pointer;width:100px;height:auto;margin-right:10px;padding-right:10px;border-right:1px solid #eee;vertical-align:bottom}.artalk-layer-dialog-wrap>.artalk-layer-dialog>.artalk-layer-dialog-content input{width:100%;line-height:29px;border:1px solid #999;border-radius:2px;outline:none;padding:0 6px;display:block;margin-top:10px;margin-bottom:5px;text-align:center}.artalk-layer-dialog-wrap>.artalk-layer-dialog>.artalk-layer-dialog-action{display:flex;flex-direction:row}.artalk-layer-dialog-wrap>.artalk-layer-dialog>.artalk-layer-dialog-action button{flex:1;display:block;cursor:pointer;border:1px solid #0083ff;background:transparent;color:#0083ff;border-radius:2px;padding:0 15px;line-height:25px;outline:none}.artalk-layer-dialog-wrap>.artalk-layer-dialog>.artalk-layer-dialog-action button:active{color:#fff;background:#0083ff}.artalk-layer-dialog-wrap>.artalk-layer-dialog>.artalk-layer-dialog-action button:not(:last-child){margin-right:5px}.artalk-layer-dialog-wrap>.artalk-layer-dialog>.artalk-layer-dialog-action button.error{color:#fff;background:#ff5652;border-color:#ff5652}@media only screen and (max-width:768px){.artalk-layer-dialog-wrap>.artalk-layer-dialog{width:90%!important}}.artalk-notify{display:block;overflow:hidden;background-cLine truncated
*/.artalk{position:relative;width:100%;min-height:200px}.artalk *,.artalk-layer-wrap *{line-height:1.6em;color:#565a5f;font-family:Helvetica Neue,PingFangSC-Light,Hiragino Sans GB,Microsoft YaHei,WenQuanYi Micro Hei,sans-serif;word-wrap:break-word;word-break:break-all}.artalk-layer-wrap pre,.artalk pre{line-height:1.6em;background-color:#f6f6f6;color:#555;padding:5px 10px;border-radius:2px;margin:0;font-family:Source Code Pro,courier new,Input Mono,PT Mono,SFMono-Regular,Consolas,Monaco,Menlo,PingFang SC,Liberation Mono,Microsoft YaHei,Courier,monospace}.artalk-layer-wrap a,.artalk a{color:#0083ff;text-decoration:none}.artalk-layer-wrap blockquote,.artalk blockquote{position:static;margin:10px 0;padding:10px 20px;background:#fcfcfc;border-left:4px solid #eee}.artalk-layer-wrap p:first-child,.artalk p:first-child{margin-top:0}.artalk-layer-wrap p:last-child,.artalk p:last-child{margin-bottom:0}.artalk *,.artalk-layer-wrap *{box-sizing:border-box}.artalk-layer-wrap img,.artalk img{max-width:100%}.artalk-error-layer,.artalk-layer-dialog-wrap,.artalk-loading,.full-layer{width:100%;height:100%;position:absolute;top:0;left:0;background:#fff;z-index:10;align-items:center;justify-content:center;flex-flow:column;display:flex}.artalk-loading{z-index:11}.artalk-loading-spinner{position:relative;width:50px;height:50px}.artalk-loading-spinner svg{-webkit-animation:artalkRotate 2s linear infinite;animation:artalkRotate 2s linear infinite;transform-origin:center center;width:100%;height:100%;position:absolute;top:0;left:0}.artalk-loading-spinner svg circle{stroke-dasharray:1,200;stroke-dashoffset:0;-webkit-animation:artalkDash 1.5s ease-in-out infinite,artalkColor 6s ease-in-out infinite;animation:artalkDash 1.5s ease-in-out infinite,artalkColor 6s ease-in-out infinite;stroke-linecap:round}@-webkit-keyframes artalkRotate{to{transform:rotate(1turn)}}@keyframes artalkRotate{to{transform:rotate(1turn)}}@-webkit-keyframes artalkDash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:89,200;stroke-dashoffset:-35px}to{stroke-dasharray:89,200;stroke-dashoffset:-124px}}@keyframes artalkDash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:89,200;stroke-dashoffset:-35px}to{stroke-dasharray:89,200;stroke-dashoffset:-124px}}@-webkit-keyframes artalkColor{0%,to{stroke:#ff5652}40%{stroke:#2196f3}66%{stroke:#32c787}80%,90%{stroke:#ffc107}}@keyframes artalkColor{0%,to{stroke:#ff5652}40%{stroke:#2196f3}66%{stroke:#32c787}80%,90%{stroke:#ffc107}}@-webkit-keyframes artalkLoadingIconRotate{0%{transform:rotate(0)}to{transform:rotate(1turn)}}@keyframes artalkLoadingIconRotate{0%{transform:rotate(0)}to{transform:rotate(1turn)}}.artalk-loading-icon{width:18px;height:18px;box-sizing:border-box;border-color:#29d transparent transparent #29d;border-style:solid;border-width:1px;border-radius:50%;-webkit-animation:artalkLoadingIconRotate .4s linear infinite;animation:artalkLoadingIconRotate .4s linear infinite}.artalk-fade-in{-webkit-animation:artalkFadeIn .3s both;animation:artalkFadeIn .3s both}.artalk-fade-out{-webkit-animation:artalkFadeOut .2s both;animation:artalkFadeOut .2s both}@-webkit-keyframes artalkFadeIn{0%{opacity:0}to{opacity:1}}@keyframes artalkFadeIn{0%{opacity:0}to{opacity:1}}@-webkit-keyframes artalkFadeOut{to{opacity:0}}@keyframes artalkFadeOut{to{opacity:0}}.artalk-error-layer{background-color:hsla(0,0%,100%,.94)}.artalk-error-layer .artalk-error-title{color:#ff5652}.artalk-error-layer .artalk-error-text{text-align:center}.artalk-layer-dialog-wrap{background-color:hsla(0,0%,100%,.94)}.artalk-layer-dialog-wrap>.artalk-layer-dialog{width:25%}.artalk-layer-dialog-wrap>.artalk-layer-dialog>.artalk-layer-dialog-content .artalk-captcha-img{cursor:pointer;width:100px;height:auto;margin-right:10px;padding-right:10px;border-right:1px solid #eee;vertical-align:bottom}.artalk-layer-dialog-wrap>.artalk-layer-dialog>.artalk-layer-dialog-content input{width:100%;line-height:34px;border:1px solid #999;border-radius:2px;outline:none;padding:0 6px;display:block;margin-top:10px;margin-bottom:5px;text-align:center}.artalk-layer-dialog-wrap>.artalk-layer-dialog>.artalk-layer-dialog-action{display:flex;flex-direction:row}.artalk-layer-dialog-wrap>.artalk-layer-dialog>.artalk-layer-dialog-action button{flex:1;display:block;cursor:pointer;border:1px solid #0083ff;background:transparent;color:#0083ff;border-radius:2px;padding:0 15px;line-height:30px;outline:none}.artalk-layer-dialog-wrap>.artalk-layer-dialog>.artalk-layer-dialog-action button:active{color:#fff;background:#0083ff}.artalk-layer-dialog-wrap>.artalk-layer-dialog>.artalk-layer-dialog-action button:not(:last-child){margin-right:5px}.artalk-layer-dialog-wrap>.artalk-layer-dialog>.artalk-layer-dialog-action button.error{color:#fff;background:#ff5652;border-color:#ff5652}@media only screen and (max-width:768px){.artalk-layer-dialog-wrap>.artalk-layer-dialog{width:90%!important}}.artalk-notify{display:block;overflow:hidden;background-color:#2c2c2c;color:#fff;bLine truncated
+3 -3
View File
@@ -1,13 +1,13 @@
/*!
* Artalk v1.0.4
* Artalk v1.0.6
* (c) 2016-2020 qwqaq.com
* Link: https://github.com/qwqcode/Artalk
*/
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("Artalk",[],t):"object"==typeof exports?exports.Artalk=t():e.Artalk=t()}(window,function(){return d=[function(e,t){e.exports=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t){e.exports=function(e){return e&&e.__esModule?e:{default:e}}},function(e,t){function r(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}e.exports=function(e,t,n){return t&&r(e.prototype,t),n&&r(e,n),e}},function(e,t,n){var r=n(20);e.exports=function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&r(e,t)}},function(e,t,n){var r=n(11),a=n(8);e.exports=function(e,t){return!t||"object"!==r(t)&&"function"!=typeof t?a(e):t}},function(t,e){function n(e){return t.exports=n=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},n(e)}t.exports=n},function(i,l,s){var e,t,n;t=[l,s(0),s(9)],void 0===(n="function"==typeof(e=function(e,t,n){"use strict";var r=s(1);Object.defineProperty(e,"__esModule",{value:true}),e["default"]=void 0,t=r(t);var a=function e(){(0,t["default"])(this,e);this.artalk=n.ArtalkInstance};(e["default"]=a).displayName="ArtalkContext",i.exports=l.default})?e.apply(l,t):e)||(i.exports=n)},function(i,l,s){var e,t,n;t=[l,s(0),s(2)],void 0===(n="function"==typeof(e=function(e,t,n){"use strict";var r=s(1);Object.defineProperty(e,"__esModule",{value:true}),e["default"]=void 0,t=r(t),n=r(n);var a=function(){function e(){(0,t["default"])(this,e)}(0,n["default"])(e,null,[{key:"createElement",value:function e(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"";var n=document.createElement("div");n.innerHTML=t.trim();return n.firstElementChild||n}},{key:"getHeight",value:function e(t){return parseFloat(getComputedStyle(t,null).height.replace("px",""))}},{key:"htmlEncode",value:function e(t){var n=document.createElement("div");n.innerText=t;var r=n.innerHTML;n=null;return r}},{key:"htmlDecode",value:function e(t){var n=document.createElement("div");n.innerHTML=t;var r=n.innerText;n=null;return r}},{key:"getLocationParmByName",value:function e(t){var n=RegExp("[?&]".concat(t,"=([^&]*)")).exec(window.location.search);return n&&decodeURIComponent(n[1].replace(/\+/g," "))}},{key:"getOffset",value:function e(t){var n=t.getBoundingClientRect();return{top:n.top+window.scrollY,left:n.left+window.scrollX}}}]);return e}();(e["default"]=a).displayName="Utils",i.exports=l.default})?e.apply(l,t):e)||(i.exports=n)},function(e,t){e.exports=function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}},function(e,t,h){var n,r,a;r=[t,h(0),h(2),h(15),h(16),h(18),h(19),h(32),h(38),h(41)],void 0===(a="function"==typeof(n=function(n,r,e,t,l,s,a,i,o,c){"use strict";var u=h(1);Object.defineProperty(n,"__esModule",{value:true}),n["default"]=n.ArtalkInstance=void 0,r=u(r),e=u(e),l=u(l),s=u(s),a=u(a),i=u(i),o=u(o),c=u(c);var d={el:"",placeholder:"来啊,快活啊 (/ω\)",noComment:"快来成为第一个评论的人吧~",defaultAvatar:"mp",pageKey:"",serverUrl:"",emoticons:h(42),gravatar:{cdn:"https://gravatar.loli.net/avatar/"}},f=null;n.ArtalkInstance=f;var p=function(){function t(e){(0,r["default"])(this,t);this.conf=e;console.log("\n %c "+"Artalk v".concat("1.0.4"," %c 一款简洁有趣的自托管评论系统 \n\n%c")+"> https://artalk.js.org\n"+"> https://github.com/qwqcode/Artalk\n"+"> https://qwqaq.com\n","color: #FFF; background: #1DAAFF; padding:5px 0;","color: #FFF; background: #656565; padding:5px 0;","");n.ArtalkInstance=f=this;this.conf=Object.assign(d,this.conf);try{this.el=document.querySelector(this.conf.el);if(this.el===null){throw Error('Sorry, Target element "'.concat(this.conf.el,'" was not found.'))}}catch(e){console.error(e);throw new Error("Artalk config `el` error")}this.el.classList.add("artalk");this.ui=new c["default"];this.initUser();this.initMarked();this.editor=new a["default"];this.list=new i["default"];this.sidebar=new o["default"]}(0,e["default"])(t,[{key:"initUser",value:function e(){var t=JSON.parse(window.localStorage.getItem("ArtalkUser")||"{}");this.user={nick:t.nick||"",email:t.email||"",link:t.link||"",password:t.password||"",isAdmin:t.isAdmin||false}}},{key:"saveUser",value:function e(){window.localStorage.setItem("ArtalkUser",JSON.stringify(this.user))}},{key:"checkHasBasicUserInfo",value:function e(){return!!this.user.nick&&!!this.user.email}},{key:"initMarked",value:function e(){var a=new l["default"].Renderer;var i=a.link;a.link=function(e,t,n){var r=i.call(a,e,t,n);return r.replace(/^<a /,'<a target="_blank" rel="nofollow" ')};var t=l["default"];tLine truncated
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("Artalk",[],t):"object"==typeof exports?exports.Artalk=t():e.Artalk=t()}(this,function(){return d=[function(e,t){e.exports=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t){e.exports=function(e){return e&&e.__esModule?e:{default:e}}},function(e,t){function a(e,t){for(var n=0;n<t.length;n++){var a=t[n];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),Object.defineProperty(e,a.key,a)}}e.exports=function(e,t,n){return t&&a(e.prototype,t),n&&a(e,n),e}},function(e,t,n){var a=n(20);e.exports=function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&a(e,t)}},function(e,t,n){var a=n(10),r=n(8);e.exports=function(e,t){return!t||"object"!==a(t)&&"function"!=typeof t?r(e):t}},function(t,e){function n(e){return t.exports=n=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},n(e)}t.exports=n},function(r,i,l){var e,t,n;t=[i,l(0)],void 0===(n="function"==typeof(e=function(e,n){"use strict";var t=l(1);Object.defineProperty(e,"__esModule",{value:true}),e["default"]=void 0,n=t(n);var a=function e(t){(0,n["default"])(this,e);this.artalk=t};(e["default"]=a).displayName="ArtalkContext",r.exports=i.default})?e.apply(i,t):e)||(r.exports=n)},function(i,l,s){var e,t,n;t=[l,s(0),s(2)],void 0===(n="function"==typeof(e=function(e,t,n){"use strict";var a=s(1);Object.defineProperty(e,"__esModule",{value:true}),e["default"]=void 0,t=a(t),n=a(n);var r=function(){function e(){(0,t["default"])(this,e)}(0,n["default"])(e,null,[{key:"createElement",value:function e(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"";var n=document.createElement("div");n.innerHTML=t.trim();return n.firstElementChild||n}},{key:"getHeight",value:function e(t){return parseFloat(getComputedStyle(t,null).height.replace("px",""))}},{key:"htmlEncode",value:function e(t){var n=document.createElement("div");n.innerText=t;var a=n.innerHTML;n=null;return a}},{key:"htmlDecode",value:function e(t){var n=document.createElement("div");n.innerHTML=t;var a=n.innerText;n=null;return a}},{key:"getLocationParmByName",value:function e(t){var n=RegExp("[?&]".concat(t,"=([^&]*)")).exec(window.location.search);return n&&decodeURIComponent(n[1].replace(/\+/g," "))}},{key:"getOffset",value:function e(t){var n=t.getBoundingClientRect();return{top:n.top+window.scrollY,left:n.left+window.scrollX}}},{key:"timeAgo",value:function e(t){try{var n=t.getTime();var a=(new Date).getTime();var r=a-n;var i=Math.floor(r/(24*3600*1e3));if(i===0){var l=r%(24*3600*1e3);var s=Math.floor(l/(3600*1e3));if(s===0){var o=l%(3600*1e3);var c=Math.floor(o/(60*1e3));if(c===0){var u=o%(60*1e3);var d=Math.round(u/1e3);return"".concat(d," 秒前")}return"".concat(c," 分钟前")}return"".concat(s," 小时前")}if(i<0)return"刚刚";if(i<8){return"".concat(i," 天前")}return this.dateFormat(t)}catch(e){console.error(e);return" - "}}},{key:"padWithZeros",value:function e(t,n){var a=t.toString();while(a.length<n){a="0".concat(a)}return a}},{key:"dateFormat",value:function e(t){var n=this.padWithZeros(t.getDate(),2);var a=this.padWithZeros(t.getMonth()+1,2);var r=this.padWithZeros(t.getFullYear(),2);return"".concat(r,"-").concat(a,"-").concat(n)}}]);return e}();(e["default"]=r).displayName="Utils",i.exports=l.default})?e.apply(l,t):e)||(i.exports=n)},function(e,t){e.exports=function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}},function(h,m,g){var e,t,n;t=[m,g(0),g(2),g(3),g(4),g(5),g(24),g(25),g(6),g(7)],void 0===(n="function"==typeof(e=function(e,l,t,n,r,i,a,s,o,c){"use strict";var u=g(1);function d(a){return function(){var e=(0,i["default"])(a),t;if(f()){var n=(0,i["default"])(this).constructor;t=Reflect.construct(e,arguments,n)}else{t=e.apply(this,arguments)}return(0,r["default"])(this,t)}}function f(){if(typeof Reflect==="undefined"||!Reflect.construct)return false;if(Reflect.construct.sham)return false;if(typeof Proxy==="function")return true;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return true}catch(e){return false}}Object.defineProperty(e,"__esModule",{value:true}),e["default"]=void 0,l=u(l),t=u(t),n=u(n),r=u(r),i=u(i),s=u(s),o=u(o),c=u(c);var p=function(e){(0,n["default"])(i,e);var r=d(i);function i(e,t,n){var a;(0,l["default"])(this,i);a=r.call(this,e);a.list=t;a.data=n;a.maxNestedNo=3;a.children=[];a.data=Object.assign({},a.data);a.parent=null;a.nestedNum=1;a.renderElem();return a}(0,t["default"])(i,[{key:"renderElem",value:function e(){var t=this;this.elem=c["default"].createElement(g(26)(this));this.mainEl=this.elem.querySelector(".artalk-comment-main");this.bodyEl=this.Line truncated
/*!
* Determine if an object is a Buffer
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
e.exports=function(e){return null!=e&&(n(e)||"function"==typeof(t=e).readFloatLE&&"function"==typeof t.slice&&n(t.slice(0,0))||!!e._isBuffer);var t}},function(e,t,n){},function(module,exports){module.exports=function anonymous(locals,filters,escape,rethrow){escape=escape||function(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/'/g,"&#39;").replace(/"/g,"&quot;")};var __stack={lineno:1,input:'<div class="artalk-list">\n <div class="artalk-list-header">\n <div class="artalk-comment-count">\n <span class="artalk-comment-count-num">0</span>\n 条评论\n </div>\n <div class="artalk-right-action">\n <span data-action="open-sidebar" style="display: none;">通知中心</span>\n </div>\n </div>\n <div class="artalk-list-body">\n <div class="artalk-list-comments-wrap">\n </div>\n <div class="artalk-list-read-more" style="display: none;">\n <div class="artalk-loading-icon" style="display: none;"></div>\n <span class="artalk-text">查看更多</span>\n </div>\n </div>\n <div class="artalk-list-footer">\n <div class="artalk-copyright">\n Powered By <a href="https://artalk.js.org" target="_blank" title="qwqcode/Artalk v<%= ARTALK_VERSION %>">Artalk</a>\n </div>\n </div>\n</div>\n',filename:"."};function rethrow(e,t,n,r){var a=t.split("\n"),i=Math.max(r-3,0),l=Math.min(a.length,r+3),s=a.slice(i,l).map(function(e,t){var n=t+i+1;return(n==r?" >> ":" ")+n+"| "+e}).join("\n");throw e.path=n,e.message=(n||"ejs")+":"+r+"\n"+s+"\n\n"+e.message,e}try{var buf=[];with(locals||{})buf.push('<div class="artalk-list">\n <div class="artalk-list-header">\n <div class="artalk-comment-count">\n <span class="artalk-comment-count-num">0</span>\n 条评论\n </div>\n <div class="artalk-right-action">\n <span data-action="open-sidebar" style="display: none;">通知中心</span>\n </div>\n </div>\n <div class="artalk-list-body">\n <div class="artalk-list-comments-wrap">\n </div>\n <div class="artalk-list-read-more" style="display: none;">\n <div class="artalk-loading-icon" style="display: none;"></div>\n <span class="artalk-text">查看更多</span>\n </div>\n </div>\n <div class="artalk-list-footer">\n <div class="artalk-copyright">\n Powered By <a href="https://artalk.js.org" target="_blank" title="qwqcode/Artalk v',escape((__stack.lineno=21,"1.0.4")),'">Artalk</a>\n </div>\n </div>\n</div>\n');return buf.join("")}catch(e){rethrow(e,__stack.input,__stack.filename,__stack.lineno)}}},function(g,v,k){var e,t,n;t=[v,k(0),k(2),k(8),k(3),k(4),k(5),k(10),k(39),k(6),k(7),k(12)],void 0===(n="function"==typeof(e=function(e,r,a,i,l,s,o,c,t,n,u,d){"use strict";var f=k(1);function p(r){return function(){var e=(0,o["default"])(r),t;if(h()){var n=(0,o["default"])(this).constructor;t=Reflect.construct(e,arguments,n)}else{t=e.apply(this,arguments)}return(0,s["default"])(this,t)}}function h(){if(typeof Reflect==="undefined"||!Reflect.construct)return false;if(Reflect.construct.sham)return false;if(typeof Proxy==="function")return true;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return true}catch(e){return false}}Object.defineProperty(e,"__esModule",{value:true}),e["default"]=void 0,r=f(r),a=f(a),i=f(i),l=f(l),s=f(s),o=f(o),c=f(c),n=f(n),u=f(u),d=f(d);var m=function(e){(0,l["default"])(n,e);var t=p(n);function n(){var e;(0,r["default"])(this,n);e=t.call(this);e.el=u["default"].createElement(k(40)((0,i["default"])(e)));e.layer=new d["default"]("sidebar",e.el);e.contentEl=e.el.querySelector(".artalk-sidebar-content");e.el.querySelector(".artalk-sidebar-close").addEventListener("click",function(){e.hide()});return e}(0,a["default"])(n,[{key:"show",value:function e(){var n=this;this.layer.show();this.contentEl.scrollTo(0,0);setTimeout(function(){n.el.style.transform="translate(0, 0)"},20);this.artalk.request("CommentReplyGet",{nick:this.artalk.user.nick,email:this.artalk.user.email},function(){n.artalk.ui.showLoading(n.contentEl)},function(){n.artalk.ui.hideLoading(n.contentEl)},function(e,t){n.contentEl.innerHTML="";if(Array.isArray(t.reply_comments)){t.reply_comments.forEach(function(e){n.putComment(e)})}},function(e,t){})}},{key:"hide",value:function e(){this.el.style.transform="";this.layer.hide()}},{key:"putComment",value:function e(t){var n=new c["default"](null,t);n.elem.querySelector('[data-comment-action="reply"]').remove();n.elem.style.cursor="pointer";n.elem.addEventListener("mouseover",function(){n.elem.style.backgroundColor="#F4F4F4"});n.elem.addEventListener("mouseout",function(){n.elem.style.backgroundColor=""});n.elem.addEventListener("click",function(e){e.preventDefault();window.location.href="".concat(t.page_key,"#artalk-comment-").concat(n.data.id)});this.contentEl.appendChild(n.getElem())}}]);return n}(n["default"]);(e["default"]=m).displayName="Sidebar",g.exports=v.default})?e.apply(v,t):e)||(g.exports=n)},function(e,t,n){},function(moLine truncated
e.exports=function(e){return null!=e&&(n(e)||"function"==typeof(t=e).readFloatLE&&"function"==typeof t.slice&&n(t.slice(0,0))||!!e._isBuffer);var t}},function(e,t,n){},function(module,exports){module.exports=function anonymous(locals,filters,escape,rethrow){escape=escape||function(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/'/g,"&#39;").replace(/"/g,"&quot;")};var __stack={lineno:1,input:'<div class="artalk-list">\n <div class="artalk-list-header">\n <div class="artalk-comment-count">\n <span class="artalk-comment-count-num">0</span>\n 条评论\n </div>\n <div class="artalk-right-action">\n <span data-action="admin-close-comment" class="artalk-hide" data-list-ui-only-admin>关闭评论</span>\n <span data-action="open-sidebar" class="artalk-hide artalk-on">通知中心</span>\n </div>\n </div>\n <div class="artalk-list-body">\n <div class="artalk-list-comments-wrap">\n </div>\n <div class="artalk-list-read-more" style="display: none;">\n <div class="artalk-loading-icon" style="display: none;"></div>\n <span class="artalk-text">查看更多</span>\n </div>\n </div>\n <div class="artalk-list-footer">\n <div class="artalk-copyright">\n Powered By <a href="https://artalk.js.org" target="_blank" title="qwqcode/Artalk v<%= ARTALK_VERSION %>">Artalk</a>\n </div>\n </div>\n</div>\n',filename:"."};function rethrow(e,t,n,a){var r=t.split("\n"),i=Math.max(a-3,0),l=Math.min(r.length,a+3),s=r.slice(i,l).map(function(e,t){var n=t+i+1;return(n==a?" >> ":" ")+n+"| "+e}).join("\n");throw e.path=n,e.message=(n||"ejs")+":"+a+"\n"+s+"\n\n"+e.message,e}try{var buf=[];with(locals||{})buf.push('<div class="artalk-list">\n <div class="artalk-list-header">\n <div class="artalk-comment-count">\n <span class="artalk-comment-count-num">0</span>\n 条评论\n </div>\n <div class="artalk-right-action">\n <span data-action="admin-close-comment" class="artalk-hide" data-list-ui-only-admin>关闭评论</span>\n <span data-action="open-sidebar" class="artalk-hide artalk-on">通知中心</span>\n </div>\n </div>\n <div class="artalk-list-body">\n <div class="artalk-list-comments-wrap">\n </div>\n <div class="artalk-list-read-more" style="display: none;">\n <div class="artalk-loading-icon" style="display: none;"></div>\n <span class="artalk-text">查看更多</span>\n </div>\n </div>\n <div class="artalk-list-footer">\n <div class="artalk-copyright">\n Powered By <a href="https://artalk.js.org" target="_blank" title="qwqcode/Artalk v',escape((__stack.lineno=22,"1.0.6")),'">Artalk</a>\n </div>\n </div>\n</div>\n');return buf.join("")}catch(e){rethrow(e,__stack.input,__stack.filename,__stack.lineno)}}},function(g,k,v){var e,t,n;t=[k,v(0),v(2),v(8),v(3),v(4),v(5),v(9),v(46),v(6),v(7),v(11)],void 0===(n="function"==typeof(e=function(e,r,t,i,l,s,o,c,n,a,u,d){"use strict";var f=v(1);function p(a){return function(){var e=(0,o["default"])(a),t;if(h()){var n=(0,o["default"])(this).constructor;t=Reflect.construct(e,arguments,n)}else{t=e.apply(this,arguments)}return(0,s["default"])(this,t)}}function h(){if(typeof Reflect==="undefined"||!Reflect.construct)return false;if(Reflect.construct.sham)return false;if(typeof Proxy==="function")return true;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return true}catch(e){return false}}Object.defineProperty(e,"__esModule",{value:true}),e["default"]=void 0,r=f(r),t=f(t),i=f(i),l=f(l),s=f(s),o=f(o),c=f(c),a=f(a),u=f(u),d=f(d);var m=function(e){(0,l["default"])(a,e);var n=p(a);function a(e){var t;(0,r["default"])(this,a);t=n.call(this,e);t.el=u["default"].createElement(v(47)((0,i["default"])(t)));t.contentEl=t.el.querySelector(".artalk-sidebar-content");t.el.querySelector(".artalk-sidebar-close").addEventListener("click",function(){t.hide()});return t}(0,t["default"])(a,[{key:"show",value:function e(){var n=this;this.layer=(0,d["default"])(this.artalk,"sidebar",this.el);this.layer.show();this.contentEl.scrollTo(0,0);setTimeout(function(){n.el.style.transform="translate(0, 0)"},20);this.artalk.request("CommentReplyGet",{nick:this.artalk.user.data.nick,email:this.artalk.user.data.email},function(){n.artalk.ui.showLoading(n.contentEl)},function(){n.artalk.ui.hideLoading(n.contentEl)},function(e,t){n.contentEl.innerHTML="";if(Array.isArray(t.reply_comments)){t.reply_comments.forEach(function(e){n.putComment(e)})}},function(e,t){})}},{key:"hide",value:function e(){this.el.style.transform="";this.layer.dispose()}},{key:"putComment",value:function e(t){var n=new c["default"](this.artalk,null,t);n.elem.querySelector('[data-comment-action="reply"]').remove();n.elem.style.cursor="pointer";n.elem.addEventListener("mouseover",function(){n.elem.style.backgroundColor="#F4F4F4"});n.elem.addEventListener("mouseout",function(){n.elem.style.backgroundColor=""});n.elem.addEventListener("click",function(e){e.preventDLine truncated
+55
View File
@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multiple Artalk On One Page Example</title>
<!-- 1. 引入 Artalk.css -->
<link rel="stylesheet" href="/dist/Artalk.css">
</head>
<body>
<h1>Multiple Artalk On One Page Example</h1>
<div class="main">
<h2>Artalk One</h2>
<div id="ArtalkComments-1"></div>
<h2>Artalk Two</h2>
<div id="ArtalkComments-2"></div>
</div>
<!-- 2. 引入 Artalk.js -->
<script src="/dist/Artalk.js"></script>
<!-- 3. 配置 Artalk -->
<script>
(function () {
// 公用配置
const commonConf = {
placeholder: '来啊,快活啊 ( ゜- ゜)',
noComment: '快来成为第一个评论的人吧~',
defaultAvatar: 'mp',
serverUrl: 'http://localhost:23366/',
readMore: {
pageSize: 15,
autoLoad: true
}
}
// 第一个评论
new Artalk({
el: '#ArtalkComments-1',
pageKey: 'multiple-on-one-page-1',
...commonConf
})
// 第二个评论
new Artalk({
el: '#ArtalkComments-2',
pageKey: 'multiple-on-one-page-2',
...commonConf
})
})();
</script>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Artalk Normal Example</title>
<!-- 1. 引入 Artalk.css -->
<link rel="stylesheet" href="/dist/Artalk.css">
</head>
<body>
<h1>Artalk Normal Example</h1>
<div class="main">
<div id="ArtalkComments"></div>
</div>
<!-- 2. 引入 Artalk.js -->
<script src="/dist/Artalk.js"></script>
<!-- 3. 配置 Artalk -->
<script>
var artalk = new Artalk({
el: '#ArtalkComments', // 元素选择
placeholder: '来啊,快活啊 ( ゜- ゜)', // 占位符
noComment: '快来成为第一个评论的人吧~', // 无评论时显示
defaultAvatar: 'mp', // 参考 https://cn.gravatar.com/site/implement/images/#default-image
pageKey: 'Artalk Normal Example',
serverUrl: 'http://localhost:23366/',
readMore: { // 阅读更多配置
pageSize: 15, // 每次请求获取评论数
autoLoad: true // 滚动到底部自动加载
}
})
</script>
</body>
</html>
+29
View File
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Artalk Pjax Example</title>
<!-- 1. 引入 Artalk.css -->
<link rel="stylesheet" href="/dist/Artalk.css">
</head>
<body>
<h1>Artalk Pjax Example</h1>
<div>
Go to <a href="./res1.html">第一页</a> | <a href="./res2.html">第二页</a>
</div>
<div id="container"></div>
</body>
<!-- 2. 引入 Artalk.js -->
<script src="/dist/Artalk.js"></script>
<!-- Pjax 配置 -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-pjax@2.0.1/jquery.pjax.min.js"></script>
<script type="text/javascript">
$(document).pjax('a', '#container', {
fragment: '.main' // △ 提取 AJAX 响应内容中指定的元素内容片段;参考:http://bsify.admui.com/jquery-pjax/
})
</script>
</html>
+41
View File
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Res</title>
<!-- 1. 引入 Artalk.css -->
<link rel="stylesheet" href="/dist/Artalk.css">
</head>
<body>
<div class="main"> <!-- △ fragment: .main -->
<section id="comments">
<div id="ArtalkComments"></div>
</section>
<!-- 2. 引入 Artalk.js -->
<script src="/dist/Artalk.js"></script>
<!-- 3. 配置 Artalk -->
<script>
window.artalk = new Artalk({
el: '#ArtalkComments',
placeholder: '来啊,快活啊 ( ゜- ゜)',
noComment: '快来成为第一个评论的人吧~',
defaultAvatar: 'mp',
pageKey: 'pjax-example-page1',
serverUrl: 'http://localhost:23366/index.php',
readMore: {
pageSize: 15,
autoLoad: true
}
});
</script>
</div>
<!-- Pjax 配置 -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-pjax@2.0.1/jquery.pjax.min.js"></script>
</body>
</html>
+41
View File
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Res</title>
<!-- 1. 引入 Artalk.css -->
<link rel="stylesheet" href="/dist/Artalk.css">
</head>
<body>
<div class="main"> <!-- △ fragment: .main -->
<section id="comments">
<div id="ArtalkComments"></div>
</section>
<!-- 2. 引入 Artalk.js -->
<script src="/dist/Artalk.js"></script>
<!-- 3. 配置 Artalk -->
<script>
window.artalk = new Artalk({
el: '#ArtalkComments',
placeholder: '来啊,快活啊 ( ゜- ゜)',
noComment: '快来成为第一个评论的人吧~',
defaultAvatar: 'mp',
pageKey: 'pjax-example-page2',
serverUrl: 'http://localhost:23366/index.php',
readMore: {
pageSize: 15,
autoLoad: true
}
});
</script>
</div>
<!-- Pjax 配置 -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-pjax@2.0.1/jquery.pjax.min.js"></script>
</body>
</html>
+36 -3
View File
@@ -5,6 +5,7 @@
<title><%= htmlWebpackPlugin.options.title %></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { box-sizing: border-box; }
body { padding:45px 10px;margin:0;background:#f4f4f4 }
.card { margin:0 auto;max-width:700px;position:relative;overflow:hidden;background:#FFF;border-radius:4px;padding:20px }
.card:not(:last-child) { margin-bottom:10px }
@@ -16,10 +17,16 @@
.desc { font-size:12px;color:#757e91 }
.social { margin-top:20px }
.social > a:not(:last-child) { margin-right:2px }
@media only screen and (max-width: 768px) {
body { padding: 0;background: #FFF; }
.card.billboard { padding-top: 20px; }
.card { width: 100%;padding: 5px; }
}
.to-top { right: 25px;bottom: 25px;display: block;cursor: pointer;text-align: center;width: 60px;height: 37px;line-height: 37px;font-size: 13px;color: #757e91;background: #fff;opacity: 0.8;border-radius: 4px;border: 1px solid #eceff2;position: fixed; }
</style>
</head>
<body>
<div class="card billboard">
<div class="card billboard artalk-fade-in">
<div class="title"><a href="https://github.com/qwqcode/Artalk" target="_blank"><span>&gt; </span>Artalk<span> &lt;</span></a></div>
<div class="desc">一款简洁有趣的自托管评论系统</div>
<div class="social">
@@ -31,13 +38,14 @@
<a href="https://github.com/qwqcode/Artalk" target="_blank"><img src="https://img.shields.io/github/stars/qwqcode/Artalk.svg?style=social"></a>
</div>
</div>
<div class="card">
<div class="card artalk-fade-in">
<div id="comments"></div>
</div>
<div class="to-top artalk-fade-in" style="display: none;">TOP</div>
<script>
var artalk = new Artalk({
el: '#comments',
placeholder: '来啊,快活啊 (/ω\)',
placeholder: '来啊,快活啊 ( ゜- ゜)',
defaultAvatar: 'mp',
pageKey: 'https://artalk.js.org/',
serverUrl: (location.hostname === 'localhost' || location.hostname === '127.0.0.1') ? 'http://localhost:23366/' : 'https://qwqaq.com/Artalk/index.php',
@@ -47,5 +55,30 @@
}
})
</script>
<script>
(function () {
const toTopElem = document.querySelector('.to-top')
toTopElem.addEventListener('click', () => {
window.scrollTo(0, 0)
})
checkShowToTop()
document.addEventListener('scroll', () => {
checkShowToTop()
})
function checkShowToTop() {
const scrollTop = window.scrollY
const whereShow = window.screen.height
const isNeedShow = (scrollTop > 0) && (scrollTop > whereShow)
if (isNeedShow) {
toTopElem.style.display = ''
} else {
toTopElem.style.display = 'none'
}
}
})()
</script>
</body>
</html>
+37 -4
View File
@@ -5,6 +5,7 @@
<title>Artalk DEMO</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { box-sizing: border-box; }
body { padding:45px 10px;margin:0;background:#f4f4f4 }
.card { margin:0 auto;max-width:700px;position:relative;overflow:hidden;background:#FFF;border-radius:4px;padding:20px }
.card:not(:last-child) { margin-bottom:10px }
@@ -16,10 +17,16 @@
.desc { font-size:12px;color:#757e91 }
.social { margin-top:20px }
.social > a:not(:last-child) { margin-right:2px }
@media only screen and (max-width: 768px) {
body { padding: 0;background: #FFF; }
.card.billboard { padding-top: 20px; }
.card { width: 100%;padding: 5px; }
}
.to-top { right: 25px;bottom: 25px;display: block;cursor: pointer;text-align: center;width: 60px;height: 37px;line-height: 37px;font-size: 13px;color: #757e91;background: #fff;opacity: 0.8;border-radius: 4px;border: 1px solid #eceff2;position: fixed; }
</style>
<link href="dist/Artalk.css?864ece85c9b2d3868105" rel="stylesheet"><script type="text/javascript" src="dist/Artalk.js?864ece85c9b2d3868105"></script></head>
<link href="dist/Artalk.css?32605c1999ff344cda74" rel="stylesheet"><script type="text/javascript" src="dist/Artalk.js?32605c1999ff344cda74"></script></head>
<body>
<div class="card billboard">
<div class="card billboard artalk-fade-in">
<div class="title"><a href="https://github.com/qwqcode/Artalk" target="_blank"><span>&gt; </span>Artalk<span> &lt;</span></a></div>
<div class="desc">一款简洁有趣的自托管评论系统</div>
<div class="social">
@@ -31,13 +38,14 @@
<a href="https://github.com/qwqcode/Artalk" target="_blank"><img src="https://img.shields.io/github/stars/qwqcode/Artalk.svg?style=social"></a>
</div>
</div>
<div class="card">
<div class="card artalk-fade-in">
<div id="comments"></div>
</div>
<div class="to-top artalk-fade-in" style="display: none;">TOP</div>
<script>
var artalk = new Artalk({
el: '#comments',
placeholder: '来啊,快活啊 (/ω\)',
placeholder: '来啊,快活啊 ( ゜- ゜)',
defaultAvatar: 'mp',
pageKey: 'https://artalk.js.org/',
serverUrl: (location.hostname === 'localhost' || location.hostname === '127.0.0.1') ? 'http://localhost:23366/' : 'https://qwqaq.com/Artalk/index.php',
@@ -47,5 +55,30 @@
}
})
</script>
<script>
(function () {
const toTopElem = document.querySelector('.to-top')
toTopElem.addEventListener('click', () => {
window.scrollTo(0, 0)
})
checkShowToTop()
document.addEventListener('scroll', () => {
checkShowToTop()
})
function checkShowToTop() {
const scrollTop = window.scrollY
const whereShow = window.screen.height
const isNeedShow = (scrollTop > 0) && (scrollTop > whereShow)
if (isNeedShow) {
toTopElem.style.display = ''
} else {
toTopElem.style.display = 'none'
}
}
})()
</script>
</body>
</html>
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "artalk",
"version": "1.0.4",
"version": "1.0.6",
"description": "一款简洁有趣的自托管评论系统",
"author": "qwqcode <1149527164@qq.com> (https://github.com/qwqcode)",
"keywords": [
@@ -55,6 +55,7 @@
"eslint-plugin-prettier": "^3.1.0",
"gh-pages": "^2.1.1",
"hanabi": "^0.4.0",
"html-webpack-harddisk-plugin": "^1.0.1",
"html-webpack-plugin": "^3.2.0",
"less": "^3.11.1",
"less-loader": "^6.0.0",
+134 -65
View File
@@ -1,18 +1,23 @@
import './css/main.less'
import marked from 'marked'
import hanabi from 'hanabi'
import User from './components/User'
import Checker from './components/Checker'
import Editor from './components/Editor'
import List from './components/List'
import Comment from './components/Comment'
import Sidebar from './components/Sidebar'
import Ui from './utils/ui'
import Utils from './utils'
import { ArtalkConfig } from '~/types/artalk-config'
/* global ARTALK_VERSION */
const defaultOpts: ArtalkConfig = {
el: '',
placeholder: '来啊,快活啊 (/ω\)',
placeholder: '来啊,快活啊 ( ゜- ゜)',
noComment: '快来成为第一个评论的人吧~',
sendBtn: '发送评论',
defaultAvatar: 'mp',
pageKey: '',
serverUrl: '',
@@ -22,26 +27,21 @@ const defaultOpts: ArtalkConfig = {
}
}
// eslint-disable-next-line
export let ArtalkInstance: Artalk = null
export default class Artalk {
public conf: ArtalkConfig
public el: HTMLElement
public readonly runId: number = new Date().getTime() // 实例唯一 ID
public ui: Ui
public user: User
public checker: Checker
public editor: Editor
public list: List
public sidebar: Sidebar
public user: {
nick: string|null,
email: string|null,
link: string|null,
password: string|null,
isAdmin: boolean
}
public comments: Comment[] = []
constructor (public conf: ArtalkConfig) {
constructor (conf: ArtalkConfig) {
// Version Information
console.log(`\n %c `
+ `Artalk v${ARTALK_VERSION} %c 一款简洁有趣的自托管评论系统 \n\n%c`
@@ -50,10 +50,8 @@ export default class Artalk {
+ `> https://qwqaq.com\n`,
'color: #FFF; background: #1DAAFF; padding:5px 0;', 'color: #FFF; background: #656565; padding:5px 0;', '')
ArtalkInstance = this
// Options
this.conf = Object.assign(defaultOpts, this.conf)
this.conf = { ...defaultOpts, ...conf }
// Main Element
try {
@@ -67,68 +65,87 @@ export default class Artalk {
}
this.el.classList.add('artalk')
this.el.setAttribute('artalk-run-id', this.runId.toString())
// 若该元素中 artalk 已装载
if (this.el.innerHTML.trim() !== '') this.el.innerHTML = ''
// Components
this.ui = new Ui()
this.initUser()
this.ui = new Ui(this)
this.user = new User(this)
this.checker = new Checker(this)
this.initMarked()
this.editor = new Editor()
this.list = new List()
this.sidebar = new Sidebar()
this.editor = new Editor(this)
this.list = new List(this)
this.sidebar = new Sidebar(this)
// 请求获取评论
this.list.reqComments()
// 锚点快速跳转评论
window.addEventListener('hashchange', () => {
this.checkGoToCommentByUrlHash()
})
}
/** 初始化用户数据 */
initUser () {
// 从 localStorage 导入
const localUser = JSON.parse(window.localStorage.getItem('ArtalkUser') || '{}')
this.user = {
nick: localUser.nick || '',
email: localUser.email || '',
link: localUser.link || '',
password: localUser.password || '',
isAdmin: localUser.isAdmin || false
}
/** 遍历操作 Comment (包括子评论) */
public eachComment (commentList: Comment[], action: (comment?: Comment, levelList?: Comment[]) => boolean|void) {
if (commentList.length === 0) return
commentList.every((item) => {
if (action(item, commentList) === false) return false
this.eachComment(item.getChildren(), action)
return true
})
}
/** 保存用户到 localStorage 中 */
saveUser () {
window.localStorage.setItem('ArtalkUser', JSON.stringify(this.user))
}
/** 查找评论项 */
public findComment (id: number) {
let comment: Comment|null = null
/** 是否已填写基本用户信息 */
checkHasBasicUserInfo () {
return !!this.user.nick && !!this.user.email
}
public marked: (src: string, callback?: (error: any, parseResult: string) => void) => string
private initMarked () {
const renderer = new marked.Renderer()
const linkRenderer = renderer.link
renderer.link = (href, title, text) => {
const html = linkRenderer.call(renderer, href, title, text)
return html.replace(/^<a /, '<a target="_blank" rel="nofollow" ')
}
const nMarked = marked
nMarked.setOptions({
renderer,
highlight: (code) => {
return hanabi(code)
},
pedantic: false,
gfm: true,
tables: true,
breaks: true,
sanitize: true, // 净化
smartLists: true,
smartypants: true,
xhtml: false
this.eachComment(this.comments, (item) => {
if (item.data.id === id) {
comment = item
return false
}
return true
})
this.marked = nMarked
return comment
}
/** 获取评论总数 */
public getCommentCount (): number {
let count = 0
this.eachComment(this.comments, () => { count++ })
return count
}
/** 删除评论 */
public deleteComment (comment: number|Comment) {
let findComment: Comment
if (typeof comment === 'number') {
findComment = this.findComment(comment)
if (!findComment) throw Error(`未找到评论 ${comment}`)
} else findComment = comment
findComment.getElem().remove()
this.eachComment(this.comments, (item, levelList) => {
if (item === findComment) {
levelList.splice(levelList.indexOf(item), 1)
return false
}
return true
})
}
/** 清空所有评论 */
public clearComments () {
this.list.commentsWrapEl.innerHTML = ''
this.list.data = undefined
this.comments = []
}
/** 公共请求 */
public request (action: string, data: object, before: () => void, after: () => void, success: (msg: string, data: any) => void, error: (msg: string, data: any) => void) {
before()
@@ -161,4 +178,56 @@ export default class Artalk {
xhr.send(formData)
}
/** 跳到评论项位置 - 根据 `location.hash` */
public checkGoToCommentByUrlHash () {
let commentId: number = Number(Utils.getLocationParmByName('artalk_comment'))
if (!commentId) {
const match = window.location.hash.match(/#artalk-comment-([0-9]+)/)
if (!match || !match[1] || Number.isNaN(Number(match[1]))) return
commentId = Number(match[1])
}
const comment = this.findComment(commentId)
if (!comment && this.list.hasMoreComments) {
this.list.readMore()
return
}
if (!comment) { return }
this.ui.scrollIntoView(comment.getElem(), false)
setTimeout(() => {
comment.getElem().classList.add('artalk-flash-once')
}, 800)
}
public marked: (src: string, callback?: (error: any, parseResult: string) => void) => string
/** 初始化 Marked */
private initMarked () {
const renderer = new marked.Renderer()
const linkRenderer = renderer.link
renderer.link = (href, title, text) => {
const html = linkRenderer.call(renderer, href, title, text)
return html.replace(/^<a /, '<a target="_blank" rel="nofollow" ')
}
const nMarked = marked
nMarked.setOptions({
renderer,
highlight: (code) => {
return hanabi(code)
},
pedantic: false,
gfm: true,
tables: true,
breaks: true,
sanitize: true, // 净化
smartLists: true,
smartypants: true,
xhtml: false
})
this.marked = nMarked
}
}
+6 -2
View File
@@ -1,5 +1,9 @@
import Artalk, { ArtalkInstance } from './Artalk'
import Artalk from './Artalk'
export default class ArtalkContext {
protected artalk: Artalk = ArtalkInstance
public artalk: Artalk
public constructor (artalk: Artalk) {
this.artalk = artalk
}
}
+52 -36
View File
@@ -9,13 +9,13 @@
"流口水": " ̄﹃ ̄",
"捂脸": "(/ω\)",
"给跪": "∠( ᐛ 」∠)_",
"蛤?": "(๑•̀ㅁ•́ฅ)",
"哈?": "(๑•̀ㅁ•́ฅ)",
"斜眼": "→_→",
"加油": "୧(๑•̀⌄•́๑)૭",
"有木有WiFi": "٩(ˊᗜˋ*)و",
"前方高能预警": "(ノ°ο°)ノ",
"我从未见过如此厚颜无耻之人": "(´இ皿இ`)",
"吓死宝宝惹": "⌇●﹏●⌇",
"纳尼": "(´இ皿இ`)",
"吓死惹": "⌇●﹏●⌇",
"已阅留爪": "(ฅ´ω`ฅ)",
"去吧大师球": "(╯°A°)╯︵○○○",
"太萌惹": "φ( ̄∇ ̄o)",
@@ -41,44 +41,60 @@
"inputType": "image",
"container": {
"原味稽": "https://i.loli.net/2019/02/01/5c53d26b7ae13.png",
"微笑默叹以为妙绝": "https://i.loli.net/2019/02/01/5c53daa84f24a.png",
"突然滑稽": "https://i.loli.net/2019/02/01/5c53cf2a457f1.jpg",
"滑稽摇摆": "https://i.loli.net/2019/02/01/5c53d1904dcb2.gif",
"高兴": "https://i.loli.net/2019/02/01/5c53d1b9e5f38.jpg",
"惊讶": "https://i.loli.net/2019/02/01/5c53d1e2ad89f.jpg",
"紧张": "https://i.loli.net/2017/02/05/5896e8a408253.jpg",
"可这和我的帅有什么关系": "https://i.loli.net/2017/02/05/5896ece29a8e0.jpg",
"相视一笑": "https://i.loli.net/2019/02/01/5c53d5a093149.jpg",
"气稽败坏": "https://i.loli.net/2019/02/01/5c53d216f3c60.jpg",
"嘤雄豪杰": "https://i.loli.net/2019/02/01/5c53d08faa9e8.jpg",
"笑死": "https://i.loli.net/2019/02/01/5c53d1560e13d.gif",
"啊哈哈": "https://i.loli.net/2019/02/01/5c53dc2947d84.jpg",
"暗中观察": "https://i.loli.net/2019/02/01/5c53dd21a2e7b.jpg",
"吃鱼": "https://i.loli.net/2017/02/05/5896ec2cb7f39.gif",
"跃跃欲试": "https://i.loli.net/2017/02/05/5896e9710dfd5.jpg",
"跃跃欲试 2": "https://i.loli.net/2019/02/01/5c53dcc057350.jpg",
"一时语塞": "https://i.loli.net/2019/02/01/5c53d5d28e22c.jpg",
"无法接受": "https://i.loli.net/2019/02/01/5c53cee8422fc.jpg",
"坐地": "https://i.loli.net/2019/02/01/5c53dbae85687.jpg",
"坐地 2": "https://i.loli.net/2019/02/01/5c53dbe510bcf.jpg",
"绝望": "https://i.loli.net/2019/02/01/5c53dc0ba2303.jpg",
"蠕动": "https://i.loli.net/2017/02/05/5896e9712a3c1.gif",
"委屈": "https://i.loli.net/2019/02/01/5c53d76e250da.jpg",
"还是算了": "https://i.loli.net/2019/02/01/5c53db746b2c8.jpg",
"还是算了": "https://i.loli.net/2020/04/30/riySFlu75fJdG4p.png",
"蓝纹稽": "https://i.loli.net/2020/04/30/jyh5IVzpqXsHuvU.jpg",
"随稽应变": "https://i.loli.net/2017/02/05/5896e6ec1d528.jpg",
"还是喝酒": "https://i.loli.net/2019/02/01/5c53d78c3f4a5.jpg",
"又不是不能用": "https://i.loli.net/2019/02/01/5c53ce897ab55.jpg",
"蠕动": "https://i.loli.net/2017/02/05/5896e9712a3c1.gif",
"束手无稽": "https://i.loli.net/2020/04/30/dF8sTOpgomj7qf5.jpg",
"微笑默叹以为妙绝": "https://i.loli.net/2019/02/01/5c53daa84f24a.png",
"喝嘤料": "https://i.loli.net/2019/02/01/5c53d63d8c6af.jpg",
"暗中观察": "https://i.loli.net/2019/02/01/5c53dd21a2e7b.jpg",
"高兴": "https://i.loli.net/2019/02/01/5c53d1b9e5f38.jpg",
"惊稽": "https://i.loli.net/2019/02/01/5c53d1e2ad89f.jpg",
"可这和我的帅有什么关系": "https://i.loli.net/2017/02/05/5896ece29a8e0.jpg",
"狱稽": "https://i.loli.net/2020/04/30/cUEQrVYGFiDjqhy.jpg",
"梆": "https://i.loli.net/2020/04/30/TlAGjm6IvJSMVpq.jpg",
"吃鱼摆摆": "https://i.loli.net/2017/02/05/5896ec2cb7f39.gif",
"跃跃欲试 3": "https://i.loli.net/2017/02/05/5896ece2ac5a2.gif",
"突然滑稽": "https://i.loli.net/2019/02/01/5c53cf2a457f1.jpg",
"扶墙怂": "https://i.loli.net/2017/02/05/5896ece2ab57a.jpg",
"阔以": "https://i.loli.net/2020/04/30/7EYyq1TcBKa3eQ2.jpg",
"不得行": "https://i.loli.net/2020/04/30/KoqBGauX7TEfeyn.jpg",
"少儿不宜": "https://i.loli.net/2020/04/30/nt2ZWRozUNjBxAK.jpg",
"稽日可期": "https://i.loli.net/2020/04/30/FmfYcoMJesi2Ddq.jpg",
"哎": "https://i.loli.net/2020/04/30/ps7PTIANgSErqnU.jpg",
"别看丢人": "https://i.loli.net/2019/02/01/5c53d4f89ea29.jpg",
"地稽 2": "https://i.loli.net/2019/02/01/5c53dbae85687.jpg",
"地稽": "https://i.loli.net/2020/04/30/BnTMX35EPxleVmA.jpg",
"老阔有点扣": "https://i.loli.net/2020/04/30/fhDXbA9T1zJPlKk.gif",
"啊哈哈": "https://i.loli.net/2019/02/01/5c53dc2947d84.jpg",
"无稽可奈": "https://i.loli.net/2020/04/30/UyxTzB2fS3LtH7Q.jpg",
"老实巴交": "https://i.loli.net/2020/04/30/7DgSoyqwtYBxchE.jpg",
"紧张": "https://i.loli.net/2017/02/05/5896e8a408253.jpg",
"摇摆稽": "https://i.loli.net/2019/02/01/5c53d1904dcb2.gif",
"又不是不能用": "https://i.loli.net/2019/02/01/5c53ce897ab55.jpg",
"一时滑稽": "https://i.loli.net/2019/02/01/5c53d5d28e22c.jpg",
"无法接受": "https://i.loli.net/2019/02/01/5c53cee8422fc.jpg",
"嘤雄豪稽": "https://i.loli.net/2020/04/30/sbtw6o7iKaM4Nmq.jpg",
"相视双稽": "https://i.loli.net/2019/02/01/5c53d5a093149.jpg",
"稽皮发麻": "https://i.loli.net/2017/02/05/5896ece2a019f.jpg",
"地稽 3": "https://i.loli.net/2019/02/01/5c53dbe510bcf.jpg",
"地稽委屈": "https://i.loli.net/2019/02/01/5c53d76e250da.jpg",
"地稽抚摸": "https://i.loli.net/2020/04/30/cavZ6nNzMPimLy7.gif",
"地稽捶打": "https://i.loli.net/2020/04/30/vFVPynXaHR5sitk.gif",
"绝望": "https://i.loli.net/2019/02/01/5c53dc0ba2303.jpg",
"气稽败坏": "https://i.loli.net/2019/02/01/5c53d216f3c60.jpg",
"当场去世": "https://i.loli.net/2020/04/30/sogxHMTFWbE2lrP.jpg",
"喝酒": "https://i.loli.net/2019/02/01/5c53d78c3f4a5.jpg",
"老衲摆摊算命": "https://i.loli.net/2017/02/05/5896ece29d8a5.gif",
"老哥,稳": "https://i.loli.net/2017/02/05/5896ece29ebb0.jpg",
"自闭了": "https://i.loli.net/2019/02/01/5c53d6603ee24.jpg",
"自闭稽": "https://i.loli.net/2019/02/01/5c53d6603ee24.jpg",
"无话可说": "https://i.loli.net/2019/02/01/5c53d6a77b7e4.jpg",
"头皮发麻": "https://i.loli.net/2017/02/05/5896ece2a019f.jpg",
"哎~": "https://i.loli.net/2019/02/01/5c53d7c08a2c0.jpg",
"别看,丢人!": "https://i.loli.net/2019/02/01/5c53d4f89ea29.jpg",
"瑟瑟发抖": "https://i.loli.net/2017/02/05/5896ece2ab57a.jpg",
"跃跃欲试?": "https://i.loli.net/2017/02/05/5896ece2ac5a2.gif",
"稽之舞": "https://i.loli.net/2019/02/01/5c53de1a4d14d.gif"
"跃跃欲试": "https://i.loli.net/2017/02/05/5896e9710dfd5.jpg",
"跃跃欲试 2": "https://i.loli.net/2019/02/01/5c53dcc057350.jpg",
"满脑子骚操作": "https://i.loli.net/2020/04/30/xJXcUtO2BryHAsa.gif",
"稽之舞": "https://i.loli.net/2019/02/01/5c53de1a4d14d.gif",
"将稽就稽": "https://i.loli.net/2020/04/30/KVwf8qCrZts6WOT.gif"
}
}
}
+20 -21
View File
@@ -1,7 +1,6 @@
import ArtalkContext from '../ArtalkContext'
import Utils from '../utils'
import Layer from './Layer'
import { ArtalkInstance } from '../Artalk'
import BuildLayer from './Layer'
interface CheckerItem {
elem?: HTMLElement
@@ -13,32 +12,32 @@ interface CheckerItem {
}
export default class Checker extends ArtalkContext {
public static submitCaptchaVal: string
public static submitCaptchaImgData: string
public submitCaptchaVal: string
public submitCaptchaImgData: string
private static readonly LIST: {[key: string]: CheckerItem} = {
private readonly LIST: {[key: string]: CheckerItem} = {
'管理员': {
body: () => Utils.createElement('<span>敲入密码来验证管理员身份:</span>'),
reqAct: 'AdminCheck',
reqObj: (inputVal) => {
return {
nick: ArtalkInstance.user.nick,
email: ArtalkInstance.user.email,
nick: this.artalk.user.data.nick,
email: this.artalk.user.data.email,
password: inputVal
}
},
onSuccess: (msg, data, inputVal) => {
ArtalkInstance.user.isAdmin = true
ArtalkInstance.user.password = inputVal
ArtalkInstance.editor.saveUser()
this.artalk.user.data.isAdmin = true
this.artalk.user.data.password = inputVal
this.artalk.user.save()
}
},
'验证码': {
body: () => {
const elem = Utils.createElement(`<span><img class="artalk-captcha-img" src="${Checker.submitCaptchaImgData || ''}" alt="验证码">敲入验证码继续:</span>`)
Checker.LIST['验证码'].elem = elem;
const elem = Utils.createElement(`<span><img class="artalk-captcha-img" src="${this.submitCaptchaImgData || ''}" alt="验证码">敲入验证码继续:</span>`)
this.LIST['验证码'].elem = elem;
(elem.querySelector('.artalk-captcha-img') as HTMLElement).onclick = () => {
Checker.LIST['验证码'].refresh()
this.LIST['验证码'].refresh()
}
return elem
},
@@ -49,13 +48,13 @@ export default class Checker extends ArtalkContext {
}
},
onSuccess: (msg, data, inputVal) => {
Checker.submitCaptchaVal = inputVal
this.submitCaptchaVal = inputVal
},
refresh: (imgData?: string) => {
const { elem } = Checker.LIST['验证码']
const { elem } = this.LIST['验证码']
const imgEl = elem.querySelector('.artalk-captcha-img')
if (!imgData) {
ArtalkInstance.request('CaptchaCheck', { refresh: true }, () => {}, () => {}, (msg, data) => {
this.artalk.request('CaptchaCheck', { refresh: true }, () => {}, () => {}, (msg, data) => {
imgEl.setAttribute('src', data.img_data)
}, () => {})
} else {
@@ -65,8 +64,8 @@ export default class Checker extends ArtalkContext {
}
}
public static checkAction (name: '管理员'|'验证码', action: () => void) {
const checker = Checker.LIST[name]
public action (name: '管理员'|'验证码', action: () => void) {
const checker = this.LIST[name]
const formEl = Utils.createElement()
formEl.appendChild(checker.body())
@@ -77,7 +76,7 @@ export default class Checker extends ArtalkContext {
input.focus() // 延迟以保证有效
}, 80)
const layer = new Layer(`checker-${new Date().getTime()}`)
const layer = BuildLayer(this.artalk, `checker-${new Date().getTime()}`)
layer.setMaskClickHide(false)
layer.show()
@@ -88,7 +87,7 @@ export default class Checker extends ArtalkContext {
}
}
ArtalkInstance.ui.showDialog(layer.getEl(), formEl, (dialogElem, btnElem: HTMLElement) => {
this.artalk.ui.showDialog(layer.getEl(), formEl, (dialogElem, btnElem: HTMLElement) => {
const inputVal = input.value.trim()
const btnRawText = btnElem.innerText
const btnTextSet = (btnText: string) => {
@@ -100,7 +99,7 @@ export default class Checker extends ArtalkContext {
btnElem.classList.remove('error')
}
ArtalkInstance.request(checker.reqAct, checker.reqObj(inputVal), () => {
this.artalk.request(checker.reqAct, checker.reqObj(inputVal), () => {
btnElem.innerText = '加载中...'
}, () => {
+126 -96
View File
@@ -2,11 +2,14 @@ import '../css/comment.less'
import List from './List'
import UADetect from '../utils/detect'
import { CommentData } from '~/types/artalk-data'
import Artalk from '../Artalk'
import ArtalkContext from '../ArtalkContext'
import Utils from '../utils'
export default class Comment extends ArtalkContext {
public elem: HTMLElement
public mainEl: HTMLElement
public bodyEl: HTMLElement
public contentEl: HTMLElement
public childrenEl: HTMLElement
public actionsEl: HTMLElement
@@ -16,55 +19,107 @@ export default class Comment extends ArtalkContext {
private readonly maxNestedNo = 3 // 最多嵌套层数
public children: Comment[] = []
constructor (public list: List, public data: CommentData) {
super()
constructor (artalk: Artalk, public list: List, public data: CommentData) {
super(artalk)
this.data = { ...this.data }
this.elem = Utils.createElement(require('../templates/Comment.ejs')(this))
this.contentEl = this.elem.querySelector('.artalk-content')
this.actionsEl = this.elem.querySelector('.artalk-comment-actions')
this.parent = null
this.nestedNum = 1 // 现在已嵌套 n 层
this.renderElem()
}
private renderElem () {
this.elem = Utils.createElement(require('../templates/Comment.ejs')(this))
this.mainEl = this.elem.querySelector('.artalk-comment-main')
this.bodyEl = this.elem.querySelector('.artalk-body')
this.contentEl = this.bodyEl.querySelector('.artalk-content')
this.actionsEl = this.elem.querySelector('.artalk-comment-actions')
this.childrenEl = null
// 绑定回复按钮事件
this.actionsEl.querySelector('[data-comment-action="reply"]').addEventListener('click', () => {
this.artalk.editor.setReply(this)
// 已折叠的评论
const contentShowBtn = this.mainEl.querySelector('.artalk-collapsed .artalk-show-btn')
if (contentShowBtn) {
contentShowBtn.addEventListener('click', () => {
if (this.contentEl.classList.contains('artalk-hide')) {
this.contentEl.classList.remove('artalk-hide')
this.artalk.ui.playFadeInAnim(this.contentEl)
contentShowBtn.innerHTML = '收起内容'
} else {
this.contentEl.classList.add('artalk-hide')
contentShowBtn.innerHTML = '查看内容'
}
})
}
this.initActionBtn()
return this.elem
}
private refreshUI () {
const originalEl = this.elem
const newEl = this.renderElem()
originalEl.replaceWith(newEl) // 替换 document 中的的 elem
this.playFadeInAnim()
// 重建子评论元素
this.artalk.eachComment(this.children, (child) => {
child.parent.getChildrenEl().appendChild(child.renderElem())
child.playFadeInAnim()
})
}
initActionBtn () {
// 绑定回复按钮事件
const replyBtn = this.actionsEl.querySelector('[data-comment-action="reply"]') as HTMLElement
if (replyBtn) {
replyBtn.addEventListener('click', () => {
this.artalk.editor.setReply(this)
})
}
// 绑定折叠按钮事件
const collapseBtn = this.actionsEl.querySelector('[data-comment-action="collapse"]') as HTMLElement
if (collapseBtn) {
collapseBtn.addEventListener('click', () => {
this.adminCollapse(collapseBtn)
})
}
// 绑定删除按钮事件
const actionDelBtnEl: HTMLElement = this.actionsEl.querySelector('[data-comment-action="delete"]')
actionDelBtnEl.addEventListener('click', () => {
this.adminDelete(this.data.id, actionDelBtnEl)
})
const delBtn = this.actionsEl.querySelector('[data-comment-action="delete"]') as HTMLElement
if (delBtn) {
delBtn.addEventListener('click', () => {
this.adminDelete(delBtn)
})
}
}
getIsRoot () {
return this.parent === null
}
setChild (comment: Comment) {
this.children.push(comment)
this.getChildrenEl().appendChild(comment.getElem())
comment.parent = this
comment.nestedNum = this.nestedNum + 1 // 嵌套层数 +1
comment.playFadeInAnim()
this.list.refreshUI()
}
getChildren () {
return this.children
}
putChild (childC: Comment) {
childC.parent = this
childC.nestedNum = this.nestedNum + 1 // 嵌套层数 +1
this.children.push(childC)
this.getChildrenEl().appendChild(childC.getElem())
childC.playFadeInAnim()
}
getChildrenEl () {
if (this.childrenEl === null) {
// console.log(this.nestedNo)
if (this.nestedNum < this.maxNestedNo) {
this.childrenEl = Utils.createElement('<div class="artalk-comment-children"></div>')
this.contentEl.appendChild(this.childrenEl)
this.mainEl.appendChild(this.childrenEl)
} else {
this.childrenEl = this.parent.getChildrenEl()
}
@@ -92,60 +147,8 @@ export default class Comment extends ArtalkContext {
return this.artalk.marked(this.data.content)
}
padWithZeros (vNumber: number, width: number) {
let numAsString = vNumber.toString()
while (numAsString.length < width) {
numAsString = `0${numAsString}`
}
return numAsString
}
dateFormat (date: Date) {
const vDay = this.padWithZeros(date.getDate(), 2)
const vMonth = this.padWithZeros(date.getMonth() + 1, 2)
const vYear = this.padWithZeros(date.getFullYear(), 2)
// var vHour = padWithZeros(date.getHours(), 2);
// var vMinute = padWithZeros(date.getMinutes(), 2);
// var vSecond = padWithZeros(date.getSeconds(), 2);
return `${vYear}-${vMonth}-${vDay}`
}
timeAgo (date: Date) {
try {
const oldTime = date.getTime()
const currTime = new Date().getTime()
const diffValue = currTime - oldTime
const days = Math.floor(diffValue / (24 * 3600 * 1000))
if (days === 0) {
// 计算相差小时数
const leave1 = diffValue % (24 * 3600 * 1000) // 计算天数后剩余的毫秒数
const hours = Math.floor(leave1 / (3600 * 1000))
if (hours === 0) {
// 计算相差分钟数
const leave2 = leave1 % (3600 * 1000) // 计算小时数后剩余的毫秒数
const minutes = Math.floor(leave2 / (60 * 1000))
if (minutes === 0) {
// 计算相差秒数
const leave3 = leave2 % (60 * 1000) // 计算分钟数后剩余的毫秒数
const seconds = Math.round(leave3 / 1000)
return `${seconds} 秒前`
}
return `${minutes} 分钟前`
}
return `${hours} 小时前`
}
if (days < 0) return '刚刚'
if (days < 8) {
return `${days} 天前`
}
return this.dateFormat(date)
} catch (error) {
console.error(error)
return ' - '
}
getDateFormatted () {
return Utils.timeAgo(new Date(this.data.date))
}
getUserUaBrowser () {
@@ -160,20 +163,46 @@ export default class Comment extends ArtalkContext {
/** 渐出动画 */
playFadeInAnim () {
this.elem.classList.add('artalk-fade-in')
// 动画结束清除 class
const onAnimEnded = () => {
this.elem.classList.remove('artalk-fade-in')
this.elem.removeEventListener('animationend', onAnimEnded)
}
this.elem.addEventListener('animationend', onAnimEnded)
this.artalk.ui.playFadeInAnim(this.elem)
}
/** 管理员 - 评论折叠 */
adminCollapse (btnElem: HTMLElement) {
if (btnElem.classList.contains('artalk-in-process')) return // 若正在折叠中
const btnTextOrg = btnElem.innerText
const isCollapse = !this.data.is_collapsed
this.artalk.request('CommentCollapse', {
id: this.data.id,
nick: this.artalk.user.data.nick,
email: this.artalk.user.data.email,
password: this.artalk.user.data.password,
is_collapsed: Number(isCollapse)
}, () => {
btnElem.classList.add('artalk-in-process')
btnElem.innerText = isCollapse ? '折叠中...' : '展开中...'
}, () => {
}, (msg, data) => {
btnElem.classList.remove('artalk-in-process')
this.data.is_collapsed = data.is_collapsed
this.artalk.eachComment([this], (item) => {
item.data.is_allow_reply = !data.is_collapsed // 禁止回复
})
this.refreshUI()
this.artalk.ui.playFadeInAnim(this.bodyEl)
this.list.refreshUI()
}, (msg, data) => {
btnElem.classList.add('artalk-error')
btnElem.innerText = isCollapse ? '折叠失败' : '展开失败'
setTimeout(() => {
btnElem.innerText = btnTextOrg
btnElem.classList.remove('artalk-error')
btnElem.classList.remove('artalk-in-process')
}, 2000)
})
}
/** 管理员 - 评论删除 */
adminDelete (commentId: number, btnElem: HTMLElement) {
const comment = this.list.findComment(commentId)
if (!comment) throw Error(`未找到评论 ${commentId}`)
adminDelete (btnElem: HTMLElement) {
if (btnElem.classList.contains('artalk-in-process')) return // 若正在删除中
// 删除确认
@@ -192,18 +221,19 @@ export default class Comment extends ArtalkContext {
}
const btnTextOrg = btnElem.innerText
this.artalk.request('CommentDel', {
id: commentId,
nick: this.artalk.user.nick,
email: this.artalk.user.email,
password: this.artalk.user.password
id: this.data.id,
nick: this.artalk.user.data.nick,
email: this.artalk.user.data.email,
password: this.artalk.user.data.password
}, () => {
btnElem.classList.add('artalk-in-process')
btnElem.innerText = '删除中...'
}, () => {
btnElem.innerText = btnTextOrg
}, (msg, data) => {
this.list.deleteComment(comment)
this.list.refreshUI()
btnElem.innerText = btnTextOrg
this.artalk.deleteComment(this)
this.list.data.total -= 1 // 评论数 -1
this.list.refreshUI() // 刷新 list
btnElem.classList.remove('artalk-in-process')
}, (msg, data) => {
btnElem.classList.add('artalk-error')
+102 -39
View File
@@ -2,21 +2,23 @@ import '../css/editor.less'
import Comment from './Comment'
import EmoticonsPlug from './editor-plugs/EmoticonsPlug'
import PreviewPlug from './editor-plugs/PreviewPlug'
import Artalk from '../Artalk'
import ArtalkContext from '../ArtalkContext'
import Utils from '../utils'
import Layer from './Layer'
import Checker from './Checker'
export default class Editor extends ArtalkContext {
private readonly LOADABLE_PLUG_LIST = [EmoticonsPlug, PreviewPlug]
public plugList: { [name: string]: any }
public plugList: { [name: string]: any } = {}
public el: HTMLElement
public headerEl: HTMLElement
public textareaWrapEl: HTMLElement
public textareaEl: HTMLTextAreaElement
public closeCommentEl: HTMLTextAreaElement
public plugWrapEl: HTMLElement
public bottomEl: HTMLElement
public bottomPartLeftEl: HTMLElement
public plugSwitcherWrapEl: HTMLElement
public bottomPartRightEl: HTMLElement
@@ -30,8 +32,8 @@ export default class Editor extends ArtalkContext {
return this.artalk.user
}
constructor () {
super()
constructor (artalk: Artalk) {
super(artalk)
this.el = Utils.createElement(require('../templates/Editor.ejs')(this))
this.artalk.el.appendChild(this.el)
@@ -39,7 +41,9 @@ export default class Editor extends ArtalkContext {
this.headerEl = this.el.querySelector('.artalk-editor-header')
this.textareaWrapEl = this.el.querySelector('.artalk-editor-textarea-wrap')
this.textareaEl = this.el.querySelector('.artalk-editor-textarea')
this.closeCommentEl = this.el.querySelector('.artalk-close-comment')
this.plugWrapEl = this.el.querySelector('.artalk-editor-plug-wrap')
this.bottomEl = this.el.querySelector('.artalk-editor-bottom')
this.bottomPartLeftEl = this.el.querySelector('.artalk-editor-bottom-part.artalk-left')
this.plugSwitcherWrapEl = this.el.querySelector('.artalk-editor-plug-switcher-wrap')
this.bottomPartRightEl = this.el.querySelector('.artalk-editor-bottom-part.artalk-right')
@@ -65,36 +69,40 @@ export default class Editor extends ArtalkContext {
}
initHeader () {
Object.keys(this.user).forEach((field) => {
Object.keys(this.user.data).forEach((field) => {
const inputEl: HTMLInputElement = this.headerEl.querySelector(`[name="${field}"]`)
if (inputEl !== null && inputEl instanceof HTMLInputElement) {
inputEl.value = this.user[field] || ''
inputEl.value = this.user.data[field] || ''
// 输入框内容变化事件
inputEl.addEventListener('input', (evt) => {
this.user[field] = inputEl.value.trim()
this.user.password = ''
this.user.isAdmin = false
this.user.data[field] = inputEl.value.trim()
// 若修改的是 nick or email
if (field !== 'link') {
this.user.data.password = ''
this.user.data.isAdmin = false
if (this.user.checkHasBasicUserInfo()
&& this.artalk.list.checkNickEmailIsAdmin(this.user.data.nick, this.user.data.email)) {
// 昵称为管理员,显示管理员密码验证 dialog
this.artalk.checker.action('管理员', () => {
this.artalk.list.refreshUI()
})
}
}
this.saveUser()
this.artalk.list.refreshUI()
if (this.artalk.checkHasBasicUserInfo()
&& this.artalk.list.checkNickEmailIsAdmin(this.user.nick, this.user.email)) {
// 昵称为管理员,显示管理员密码验证 dialog
Checker.checkAction('管理员', () => {
this.artalk.list.refreshUI()
})
}
})
}
})
}
saveUser () {
this.artalk.saveUser()
this.user.save()
}
saveContent () {
window.localStorage.setItem('ArtalkContent', this.getContent().trim())
window.localStorage.setItem('ArtalkContent', this.getContentOriginal().trim())
}
initTextarea () {
@@ -110,15 +118,24 @@ export default class Editor extends ArtalkContext {
// 输入框高度随内容而变化
this.textareaEl.addEventListener('input', (evt) => {
const diff = this.textareaEl.offsetHeight - this.textareaEl.clientHeight
this.textareaEl.style.height = '0px' // it's a magic. 若不加此行,内容减少,高度回不去
this.textareaEl.style.height = `${this.textareaEl.scrollHeight + diff}px`
this.adjustTextareaHeight()
})
}
adjustTextareaHeight () {
const diff = this.textareaEl.offsetHeight - this.textareaEl.clientHeight
this.textareaEl.style.height = '0px' // it's a magic. 若不加此行,内容减少,高度回不去
this.textareaEl.style.height = `${this.textareaEl.scrollHeight + diff}px`
}
openedPlugName: string = null
initEditorPlug () {
this.plugList = {}
let openedPlugName = null
this.plugWrapEl.innerHTML = ''
this.plugWrapEl.style.display = 'none'
this.openedPlugName = null
this.plugSwitcherWrapEl.innerHTML = ''
// 依次实例化 plug
this.LOADABLE_PLUG_LIST.forEach((PlugObj) => {
@@ -132,10 +149,10 @@ export default class Editor extends ArtalkContext {
this.plugSwitcherWrapEl.querySelectorAll('.active').forEach(item => item.classList.remove('active'))
// 若点击已打开的,则收起
if (plug.getName() === openedPlugName) {
if (plug.getName() === this.openedPlugName) {
plug.onHide()
this.plugWrapEl.style.display = 'none'
openedPlugName = null
this.openedPlugName = null
return
}
@@ -159,13 +176,20 @@ export default class Editor extends ArtalkContext {
})
this.plugWrapEl.style.display = ''
openedPlugName = plug.getName()
this.openedPlugName = plug.getName()
btnElem.classList.add('active')
})
})
}
/** 关闭编辑器插件 */
closePlug () {
this.plugWrapEl.innerHTML = ''
this.plugWrapEl.style.display = 'none'
this.openedPlugName = null
}
insertContent (val: string) {
if ((document as any).selection) {
this.textareaEl.focus();
@@ -192,6 +216,7 @@ export default class Editor extends ArtalkContext {
if (!!this.plugList && !!this.plugList.preview) {
this.plugList.preview.updateContent()
}
this.adjustTextareaHeight()
}
clearEditor () {
@@ -200,7 +225,19 @@ export default class Editor extends ArtalkContext {
}
getContent () {
return this.textareaEl.value || ''
let content = this.getContentOriginal()
// 表情包处理
if (this.plugList && this.plugList.emoticons) {
const emoticonsPlug = this.plugList.emoticons as EmoticonsPlug
content = emoticonsPlug.transEmoticonImageText(content)
}
return content
}
getContentOriginal () {
return this.textareaEl.value || '' // Tip: !!"0" === true
}
getContentMarked () {
@@ -262,36 +299,40 @@ export default class Editor extends ArtalkContext {
this.artalk.request('CommentAdd', {
content: this.getContent(),
nick: this.user.nick,
email: this.user.email,
link: this.user.link,
nick: this.user.data.nick,
email: this.user.data.email,
link: this.user.data.link,
rid: this.getReplyComment() === null ? 0 : this.getReplyComment().data.id,
page_key: this.artalk.conf.pageKey,
password: this.user.password,
captcha: Checker.submitCaptchaVal || ''
password: this.user.data.password,
captcha: this.artalk.checker.submitCaptchaVal || ''
}, () => {
this.artalk.ui.showLoading(this.el)
}, () => {
this.artalk.ui.hideLoading(this.el)
}, (msg, data) => {
const newComment = new Comment(this.artalk.list, data.comment)
const newComment = new Comment(this.artalk, this.artalk.list, data.comment)
if (this.getReplyComment() !== null) {
this.getReplyComment().setChild(newComment)
this.getReplyComment().putChild(newComment)
} else {
this.artalk.list.putComment(newComment)
this.artalk.list.putRootComment(newComment)
}
this.clearEditor() // 清空编辑器
this.artalk.ui.scrollIntoView(newComment.getElem())
this.clearEditor()
newComment.playFadeInAnim() // 播放评论渐出动画
this.artalk.list.data.total += 1 // 评论数增加 1
this.artalk.list.refreshUI() // 更新 list 界面
}, (msg, data) => {
if ((typeof data === 'object') && data !== null && typeof data.need_password === 'boolean' && data.need_password === true) {
// 管理员密码验证
Checker.checkAction('管理员', () => {
this.artalk.checker.action('管理员', () => {
this.submit()
})
} else if ((typeof data === 'object') && data !== null && typeof data.need_captcha === 'boolean' && data.need_captcha === true) {
// 验证码验证
Checker.submitCaptchaImgData = data.img_data
Checker.checkAction('验证码', () => {
this.artalk.checker.submitCaptchaImgData = data.img_data
this.artalk.checker.action('验证码', () => {
this.submit()
})
} else {
@@ -303,5 +344,27 @@ export default class Editor extends ArtalkContext {
showNotify (msg: string, type) {
this.artalk.ui.showNotify(msg, type, this.notifyWrapEl)
}
/** 关闭评论 */
closeComment () {
this.closeCommentEl.style.display = ''
if (!this.artalk.user.data.isAdmin) {
this.textareaEl.style.display = 'none'
this.closePlug()
this.bottomEl.style.display = 'none'
} else {
// 管理员一直打开评论
this.textareaEl.style.display = ''
this.bottomEl.style.display = ''
}
}
/** 打开评论 */
openComment () {
this.closeCommentEl.style.display = 'none'
this.textareaEl.style.display = ''
this.bottomEl.style.display = ''
}
}
+23 -16
View File
@@ -1,30 +1,33 @@
import Artalk from '../Artalk'
import ArtalkContext from '../ArtalkContext'
import Utils from '../utils'
export default class Layer extends ArtalkContext {
// TODO: for 一页多评论,该实例请用完及时销毁;待优化
export class Layer extends ArtalkContext {
private el: HTMLElement
private wrapEl: HTMLElement
private maskEl: HTMLElement
private maskClickHideEnable: boolean = true
constructor (private name: string, html?: HTMLElement) {
super()
constructor (artalk: Artalk, private name: string, html?: HTMLElement) {
super(artalk)
this.initWrap()
this.el = this.wrapEl.querySelector(
`[data-layer-name="${name}"]`
)
if (this.el !== null) throw new Error(`layer "${name}" 已存在`)
if (!html) {
this.el = Utils.createElement()
this.el.classList.add('artalk-layer-item')
} else {
this.el = html
this.el = this.wrapEl.querySelector(`[data-layer-name="${name}"]`)
if (this.el === null) {
// 若 layer 元素未创建过
if (!html) {
this.el = Utils.createElement()
this.el.classList.add('artalk-layer-item')
} else {
this.el = html
}
}
this.el.setAttribute('data-layer-name', name)
this.el.style.display = 'none'
// 添加到 layers wrap 中
this.wrapEl.prepend(this.el)
}
@@ -32,7 +35,7 @@ export default class Layer extends ArtalkContext {
this.wrapEl = document.querySelector(`.artalk-layer-wrap`)
if (!this.wrapEl) {
this.wrapEl = Utils.createElement(
'<div class="artalk-layer-wrap" style="display: none;"><div class="artalk-layer-mask"></div></div>'
`<div class="artalk-layer-wrap" style="display: none;"><div class="artalk-layer-mask"></div></div>`
)
document.body.appendChild(this.wrapEl)
}
@@ -52,6 +55,8 @@ export default class Layer extends ArtalkContext {
return this.el
}
private static hideTimeoutList: number[] = []
show () {
Layer.hideTimeoutList.forEach(item => {
clearTimeout(item)
@@ -70,11 +75,10 @@ export default class Layer extends ArtalkContext {
document.body.style.overflow = 'hidden'
}
private static hideTimeoutList: number[] = []
hide () {
Layer.hideTimeoutList.push(setTimeout(() => {
this.wrapEl.style.display = 'none'
document.body.style.overflow = ''
}, 450))
this.wrapEl.classList.add('artalk-fade-out')
@@ -84,7 +88,6 @@ export default class Layer extends ArtalkContext {
}, 200))
this.el.style.display = 'none'
document.body.style.overflow = ''
}
setMaskClickHide (enable: boolean) {
@@ -97,3 +100,7 @@ export default class Layer extends ArtalkContext {
this.el = null
}
}
export default function BuildLayer (artalk: Artalk, name: string, html?: HTMLElement) {
return new Layer(artalk, name, html)
}
+92 -155
View File
@@ -1,44 +1,37 @@
import md5 from 'md5'
import '../css/list.less'
import Comment from './Comment'
import { ListData } from '~/types/artalk-data'
import { ListData, CommentData } from '~/types/artalk-data'
import Artalk from '../Artalk'
import ArtalkContext from '../ArtalkContext'
import Utils from '../utils'
export default class List extends ArtalkContext {
public el: HTMLElement
public commentsWrapEl: HTMLElement
public comments: Comment[] = []
public data: ListData
public reqPageSize: number = 15 // 每次请求获取量
public readMoreEl: HTMLElement
public readMoreLoadingEl: HTMLElement
public readMoreTextEl: HTMLElement
public data: ListData
public reqPageSize: number = 15 // 每次请求获取量
public isLoading: boolean = false
public closeCommentBtnEl: HTMLElement
public openSidebarBtnEl: HTMLElement
constructor () {
super()
constructor (artalk: Artalk) {
super(artalk)
this.el = Utils.createElement(require('../templates/List.ejs')(this))
this.artalk.el.appendChild(this.el)
this.commentsWrapEl = this.el.querySelector('.artalk-list-comments-wrap')
// 侧边栏呼出按钮
this.openSidebarBtnEl = this.el.querySelector('[data-action="open-sidebar"]')
this.openSidebarBtnEl.addEventListener('click', () => {
this.artalk.sidebar.show()
})
// 锚点快速跳转评论
window.addEventListener('hashchange', () => {
this.checkRedirectByUrlHash()
})
// 操作按钮
this.initListActionBtn()
// 查看更多
this.reqPageSize = this.artalk.conf.readMore ? (this.artalk.conf.readMore.pageSize || this.reqPageSize) : this.reqPageSize
@@ -49,15 +42,12 @@ export default class List extends ArtalkContext {
this.readMoreEl.addEventListener('click', () => {
this.readMore()
})
// 请求获取评论
this.reqComments()
}
/** 拉取评论 */
reqComments (offset: Number = 0) {
public reqComments (offset: Number = 0) {
if (offset === 0) {
this.clearComments();
this.artalk.clearComments();
}
this.artalk.request('CommentGet', {
@@ -75,12 +65,12 @@ export default class List extends ArtalkContext {
}, (msg, data: ListData) => {
this.data = { ...data }
this.artalk.ui.setGlobalError(null)
this.importCommentsByReqObj(data.comments)
this.reqImportComments(data.comments)
// 查看更多按钮
if (this.hasMoreComments) this.showReadMoreBtn()
else this.hideReadMoreBtn()
// 锚点跳转
this.checkRedirectByUrlHash()
this.artalk.checkGoToCommentByUrlHash()
// 滚动到底部自动加载
if (offset === 0 && this.hasMoreComments) {
this.initScrollBottomAutoLoad()
@@ -101,169 +91,103 @@ export default class List extends ArtalkContext {
}
/** 导入评论 - 通过请求数据 */
importCommentsByReqObj (rawData: any[]) {
if (!Array.isArray(rawData)) { throw new Error('putCommentsByObj 出错:参数非数组') }
const newCommentList: Comment[] = []
rawData.forEach((commentData) => {
if (commentData.id === 0) {
throw new Error('黑人问号 ??? Comment 的 ID 怎么可能是 0 ?')
}
if (commentData.rid === 0) {
newCommentList.push(new Comment(this, commentData))
}
})
private reqImportComments (rawData: any[]) {
if (!Array.isArray(rawData)) { throw new Error('请求响应参数非数组') }
// 查找并导入所有子评论
const queryChildren = (parentComment: Comment) => {
rawData.forEach((commentData) => {
if (commentData.rid === parentComment.data.id) {
const cComment = new Comment(this, commentData)
parentComment.setChild(cComment)
cComment.playFadeInAnim()
const queryImportChildren = (parentC: Comment) => {
const children = rawData.filter(o => o.rid === parentC.data.id)
if (children.length === 0) return
// 是否存在子评论
const hasChild = (parentId: number) => {
return rawData.find(o => o.rid === parentId) !== null
}
children.forEach((itemData: CommentData) => {
itemData.is_allow_reply = parentC.data.is_allow_reply
const childC = new Comment(this.artalk, this, itemData)
parentC.putChild(childC)
// 继续 递归查找子评论
if (hasChild(cComment.data.id)) {
queryChildren(cComment)
}
}
queryImportChildren(childC); // 递归查找所有子评论
})
}
// 查找并导入子评论
newCommentList.forEach((comment) => {
queryChildren(comment)
// 开始处理 rawData
rawData.filter((o) => o.rid === 0).forEach((rootCommentData: CommentData) => {
if (rootCommentData.is_collapsed) rootCommentData.is_allow_reply = false
const rootComment = new Comment(this.artalk, this, rootCommentData)
this.artalk.comments.push(rootComment) // 将评论导入 comments 总表中
this.commentsWrapEl.appendChild(rootComment.getElem())
rootComment.playFadeInAnim()
queryImportChildren(rootComment)
})
// 将评论导入 this.comments 总表中
this.comments.push(...newCommentList)
// 装载评论元素
newCommentList.forEach((comment) => {
this.commentsWrapEl.appendChild(comment.getElem())
comment.playFadeInAnim()
})
this.refreshUI()
this.refreshUI(true)
}
/** 添加评论项 */
putComment (comment: Comment) {
public putRootComment (comment: Comment) {
this.commentsWrapEl.prepend(comment.getElem())
this.comments.unshift(comment)
comment.playFadeInAnim()
this.refreshUI()
this.artalk.comments.unshift(comment)
}
/** 查找评论项 */
findComment (id: number) {
let comment: Comment|null = null
this.eachComment(this.comments, (item) => {
if (item.data.id === id) {
comment = item
return false
}
return true
private initListActionBtn () {
// 侧边栏呼出按钮
this.openSidebarBtnEl = this.el.querySelector('[data-action="open-sidebar"]')
this.openSidebarBtnEl.addEventListener('click', () => {
this.artalk.sidebar.show()
})
return comment
}
/** 遍历操作 Comment (包括子评论) */
eachComment (commentList: Comment[], action: (comment?: Comment, levelList?: Comment[]) => boolean|void) {
if (commentList.length === 0) return
commentList.every((item) => {
if (action(item, commentList) === false) return false
this.eachComment(item.getChildren(), action)
return true
// 关闭评论按钮
this.closeCommentBtnEl = this.el.querySelector('[data-action="admin-close-comment"]') as HTMLElement
this.closeCommentBtnEl.addEventListener('click', () => {
this.adminSetPage({
is_close_comment: !this.data.page.is_close_comment
})
})
}
/** 删除评论 */
deleteComment (comment: number|Comment) {
let findComment: Comment
if (typeof comment === 'number') {
findComment = this.findComment(comment)
if (!findComment) throw Error(`未找到评论 ${comment}`)
} else findComment = comment
findComment.getElem().remove()
this.eachComment(this.comments, (item, levelList) => {
if (item === findComment) {
levelList.splice(levelList.indexOf(item), 1)
return false
}
return true
})
}
/** 清空所有评论 */
clearComments () {
this.commentsWrapEl.innerHTML = ''
this.comments = []
this.data = undefined
}
/** 刷新界面 */
refreshUI () {
(this.el.querySelector('.artalk-comment-count-num') as HTMLElement).innerText = this.getCommentCount().toString()
public refreshUI (isFirstUse: boolean = false) {
(this.el.querySelector('.artalk-comment-count-num') as HTMLElement).innerText = this.getListCommentCount().toString()
let noCommentElem = this.commentsWrapEl.querySelector('.artalk-no-comment') as HTMLElement
if (this.comments.length <= 0 && !noCommentElem) {
if (this.artalk.comments.length <= 0 && !noCommentElem) {
noCommentElem = Utils.createElement('<div class="artalk-no-comment"></div>')
noCommentElem.innerText = this.artalk.conf.noComment
this.commentsWrapEl.appendChild(noCommentElem)
}
if (this.comments.length > 0 && noCommentElem !== null) {
if (this.artalk.comments.length > 0 && noCommentElem !== null) {
noCommentElem.remove()
}
// 已输入个人信息
if (!!this.artalk.user.nick && !!this.artalk.user.email) {
this.openSidebarBtnEl.style.display = ''
if (!!this.artalk.user.data.nick && !!this.artalk.user.data.email) {
this.openSidebarBtnEl.classList.remove('artalk-hide')
} else {
this.openSidebarBtnEl.style.display = 'none'
this.openSidebarBtnEl.classList.add('artalk-hide')
}
// 是管理员
if (this.artalk.user.isAdmin) {
this.el.querySelectorAll('[data-only-admin-show]').forEach((itemEl: HTMLElement) => {
// 仅管理员显示控制
this.el.querySelectorAll('[data-list-ui-only-admin]').forEach((itemEl: HTMLElement) => {
if (this.artalk.user.data.isAdmin)
itemEl.classList.remove('artalk-hide')
})
} else {
this.el.querySelectorAll('[data-only-admin-show]').forEach((itemEl: HTMLElement) => {
else
itemEl.classList.add('artalk-hide')
})
})
// 关闭评论
if (!!this.data && !!this.data.page && this.data.page.is_close_comment === true) {
this.artalk.editor.closeComment()
this.closeCommentBtnEl.innerHTML = '打开评论'
} else if (!isFirstUse) {
this.artalk.editor.openComment()
this.closeCommentBtnEl.innerHTML = '关闭评论'
}
}
/** 跳到评论项位置 - 根据 `location.hash` */
checkRedirectByUrlHash () {
let commentId: number = Number(Utils.getLocationParmByName('artalk_comment'))
if (!commentId) {
const match = window.location.hash.match(/#artalk-comment-([0-9]+)/)
if (!match || !match[1] || Number.isNaN(Number(match[1]))) return
commentId = Number(match[1])
}
const comment = this.findComment(commentId)
if (!comment && this.hasMoreComments) {
this.readMore()
return
}
if (!comment) { return }
this.artalk.ui.scrollIntoView(comment.getElem(), false)
setTimeout(() => {
comment.getElem().classList.add('artalk-flash-once')
}, 800)
/** 获取评论总数 (包括子评论) */
public getListCommentCount (): number {
if (!this.data || !this.data.total) return 0
return Number(this.data.total || '0')
}
/** 是否还有更多的评论 */
@@ -326,19 +250,32 @@ export default class List extends ArtalkContext {
}
/** 根据请求数据判断 nick 是否为管理员 */
checkNickEmailIsAdmin (nick: string, email: string) {
public checkNickEmailIsAdmin (nick: string, email: string) {
if (!this.data || !this.data.admin_nicks || !this.data.admin_encrypted_emails) return false
return (this.data.admin_nicks.indexOf(nick) !== -1)
&& (this.data.admin_encrypted_emails.find(o => String(o).toLowerCase() === String(md5(email)).toLowerCase()))
}
/** 获取评论总数 (包括子评论) */
getCommentCount (): number {
let count = 0
this.eachComment(this.comments, () => {
count++
/** 管理员设置页面信息 */
public adminSetPage (conf: {
is_close_comment: boolean
}) {
this.artalk.request('SetPage', {
nick: this.artalk.user.data.nick,
email: this.artalk.user.data.email,
page_key: this.artalk.conf.pageKey,
password: this.artalk.user.data.password,
is_close_comment: Number(conf.is_close_comment)
}, () => {
this.artalk.ui.showLoading(this.artalk.editor.el)
}, () => {
this.artalk.ui.hideLoading(this.artalk.editor.el)
}, (msg, data) => {
this.data.page = { ...data }
this.refreshUI()
}, (msg, data) => {
this.artalk.editor.showNotify(`修改页面数据失败:${msg}`, 'e')
})
return count
}
}
+9 -8
View File
@@ -1,20 +1,20 @@
import Comment from './Comment'
import '../css/sidebar.less'
import Artalk from '../Artalk'
import ArtalkContext from '../ArtalkContext'
import Utils from '../utils'
import { CommentData } from '~/types/artalk-data'
import Layer from './Layer'
import BuildLayer, { Layer } from './Layer'
export default class Sidebar extends ArtalkContext {
public el: HTMLElement
public layer: Layer
public contentEl: HTMLElement
constructor () {
super()
constructor (artalk: Artalk) {
super(artalk)
this.el = Utils.createElement(require('../templates/Sidebar.ejs')(this))
this.layer = new Layer('sidebar', this.el)
this.contentEl = this.el.querySelector('.artalk-sidebar-content')
this.el.querySelector('.artalk-sidebar-close').addEventListener('click', () => {
@@ -23,6 +23,7 @@ export default class Sidebar extends ArtalkContext {
}
show () {
this.layer = BuildLayer(this.artalk, 'sidebar', this.el)
this.layer.show()
this.contentEl.scrollTo(0, 0)
setTimeout(() => {
@@ -30,8 +31,8 @@ export default class Sidebar extends ArtalkContext {
}, 20)
this.artalk.request('CommentReplyGet', {
nick: this.artalk.user.nick,
email: this.artalk.user.email
nick: this.artalk.user.data.nick,
email: this.artalk.user.data.email
}, () => {
this.artalk.ui.showLoading(this.contentEl)
}, () => {
@@ -50,11 +51,11 @@ export default class Sidebar extends ArtalkContext {
hide () {
this.el.style.transform = ''
this.layer.hide()
this.layer.dispose() // 用完即销毁
}
putComment (data: CommentData) {
const comment = new Comment(null, data)
const comment = new Comment(this.artalk, null, data)
comment.elem.querySelector('[data-comment-action="reply"]').remove()
comment.elem.style.cursor = 'pointer'
+36
View File
@@ -0,0 +1,36 @@
import Artalk from '../Artalk';
import ArtalkContext from '../ArtalkContext';
export default class User extends ArtalkContext {
public data: {
nick: string|null,
email: string|null,
link: string|null,
password: string|null,
isAdmin: boolean
}
constructor (artalk: Artalk) {
super(artalk)
// 从 localStorage 导入
const localUser = JSON.parse(window.localStorage.getItem('ArtalkUser') || '{}')
this.data = {
nick: localUser.nick || '',
email: localUser.email || '',
link: localUser.link || '',
password: localUser.password || '',
isAdmin: localUser.isAdmin || false
}
}
/** 保存用户到 localStorage 中 */
public save () {
window.localStorage.setItem('ArtalkUser', JSON.stringify(this.data))
}
/** 是否已填写基本用户信息 */
public checkHasBasicUserInfo () {
return !!this.data.nick && !!this.data.email
}
}
+23 -5
View File
@@ -3,16 +3,23 @@ import Editor from '../Editor'
import ArtalkContext from '~/src/ArtalkContext'
import Utils from '~/src/utils'
type EmoticonListType = {
[grpName: string]: {
inputType: 'emoticon'|'image'
container: { [name: string]: string }
}
}
export default class EmoticonsPlug extends ArtalkContext {
public elem: HTMLElement
public emoticons: any
public emoticons: EmoticonListType
public listWrapElem: HTMLElement
public typesElem: HTMLElement
constructor (public editor: Editor) {
super()
super(editor.artalk)
this.emoticons = this.artalk.conf.emoticons
this.initElem()
@@ -46,7 +53,7 @@ export default class EmoticonsPlug extends ArtalkContext {
const title = elem.getAttribute('title')
const content = elem.getAttribute('data-content')
if (inputType === 'image') {
this.editor.insertContent(`![${title}](${content})`)
this.editor.insertContent(`:[${title}]`)
} else {
this.editor.insertContent(content)
}
@@ -82,8 +89,8 @@ export default class EmoticonsPlug extends ArtalkContext {
}
changeListHeight () {
const listWrapHeight = Utils.getHeight(this.listWrapElem)
this.editor.plugWrapEl.style.height = `${listWrapHeight > 150 ? listWrapHeight : 150}px`
/* const listWrapHeight = Utils.getHeight(this.listWrapElem)
this.editor.plugWrapEl.style.height = `${listWrapHeight > 150 ? listWrapHeight : 150}px` */
}
onShow () {
@@ -96,4 +103,15 @@ export default class EmoticonsPlug extends ArtalkContext {
onHide () {
this.elem.parentElement.style.height = ''
}
public transEmoticonImageText (text: string) {
Object.entries(this.emoticons).forEach(([grpName, grp]) => {
if (grp.inputType !== 'image') return
Object.entries(grp.container).forEach(([name, imgSrc]) => {
text = text.split(`:[${name}]`).join(`![${name}](${imgSrc}) `) // replaceAll(...)
})
})
return text
}
}
@@ -7,7 +7,7 @@ export default class PreviewPlug extends ArtalkContext {
public elem: HTMLElement
constructor (public editor: Editor) {
super()
super(editor.artalk)
this.initElem()
}
+1 -1
View File
@@ -8,7 +8,7 @@ export default class PreviewPlug extends ArtalkContext {
public binded: boolean = false
constructor (public editor: Editor) {
super()
super(editor.artalk)
this.initElem()
}
@@ -3,9 +3,10 @@
width: 100%;
.artalk-emoticons-list-wrap {
/* overflow-y: scroll;
.artalk-slim-scrollbar();
overflow-y: scroll;
overflow-x: hidden;
height: calc(100% - 30px); */
height: 100%; // calc(100% - 30px);
width: 100%;
.artalk-emoticons-list {
@@ -25,7 +26,7 @@
}
&:hover {
background: #F4F4F4;
background: @color-bg-grey;
}
}
}
@@ -51,7 +52,7 @@
&:hover,
&.active {
background: #F4F4F4;
background: @color-bg-grey;
}
}
}
+19
View File
@@ -0,0 +1,19 @@
.artalk-slim-scrollbar() {
&::-webkit-scrollbar {
width: 4px;
background: transparent;
}
&::-webkit-scrollbar-thumb,
&::-webkit-scrollbar-thumb:window-inactive {
background: rgba(86,86,86,.3);
}
&::-webkit-scrollbar-thumb:vertical:hover {
background: rgba(65,74,82,.77);
}
&::-webkit-scrollbar-thumb:vertical:active {
background: rgba(41,47,53,.77);
}
}
+3
View File
@@ -4,13 +4,16 @@
@color-meta: #757e91;
@color-border: #eceff2;
@color-bg: #FFF;
@color-bg-grey: #F4F4F4;
@color-main: #0083ff;
@color-red: #ff5652;
@color-yellow: #ffa928;
@font-family: "Helvetica Neue", "PingFangSC-Light", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
@font-size: 14px;
@line-height: 1.6em;
@block-margin: 15px;
@block-margin-mobile: 7px;
@block-padding: 10px;
@block-radius: 4px;
+83 -30
View File
@@ -1,7 +1,6 @@
.artalk-comment-wrap {
overflow: hidden;
position: relative;
margin-bottom: 10px;
border-bottom: 1px solid @color-border;
&.artalk-flash-once {
@@ -38,7 +37,7 @@
}
}
& > .artalk-content {
& > .artalk-comment-main {
display: block;
flex: auto;
@@ -53,7 +52,7 @@
align-items: end;
.badge() {
background: #F4F4F4;
background: @color-bg-grey;
padding: 0 5px;
border-radius: 3px;
}
@@ -93,17 +92,67 @@
overflow: hidden;
position: relative;
& > *:first-child {
margin-top: 0;
}
& > *:last-child {
margin-bottom: 0;
}
img {
max-width: 100%;
}
& > .artalk-content {
&.artalk-type-collapsed {
border: 3px solid @color-bg-grey;
border-bottom: 0;
padding: 5px 10px;
border-radius: @block-radius @block-radius 0 0;
margin-bottom: -5px;
}
&.artalk-hide {
display: none;
}
& > *:first-child {
margin-top: 0;
}
& > *:last-child {
margin-bottom: 0;
}
}
& > .artalk-collapsed {
margin: 3px 0;
font-size: 13px;
padding: 10px 18px;
display: block;
background: @color-bg-grey;
border-radius: @block-radius;
& > .artalk-text {
color: @color-meta;
}
& > .artalk-show-btn {
color: @color-main;
cursor: pointer;
user-select: none;
margin-left: 3px;
&:hover {
color: @color-main;
}
}
}
&.artalk-read-more-mask:after {
pointer-events: none;
position: absolute;
z-index: 1;
display: block;
overflow: hidden;
width: 100%;
content: " ";
bottom: 0;
height: 41px;
background: linear-gradient(180deg, hsla(0,0%,100%,0), hsla(0,0%,100%,1));
}
}
& > .artalk-footer {
@@ -141,29 +190,33 @@
}
}
}
}
}
& > .artalk-comment-children {
margin-top: 10px;
border-radius: 3px;
border-left: 1px dashed hsla(0,0%,93%,.5);
.comment-small-size() {
border-bottom-color: transparent;
&:not(:last-child) {
margin-bottom: 5px;
}
.artalk-comment-wrap {
border-bottom-color: transparent;
&:not(:last-child) {
margin-bottom: 5px;
}
& > .artalk-comment {
padding-top: 4px;
padding-bottom: 4px;
& > .artalk-comment {
padding-top: 4px;
padding-bottom: 4px;
& > .artalk-avatar img {
width: 36px;
height: 36px;
}
}
}
& > .artalk-avatar img {
width: 36px;
height: 36px;
}
}
}
.artalk-comment-children {
.artalk-comment-wrap {
margin-top: 10px;
border-radius: 3px;
border-left: 1px dashed hsla(0,0%,93%,.5);
.comment-small-size()
}
}
}
+34 -1
View File
@@ -4,6 +4,13 @@
margin-bottom: @block-margin;
overflow: hidden;
position: relative;
background: #FFF;
@media only screen and (max-width: 768px) {
& > {
margin-bottom: @block-margin-mobile;
}
}
& > .artalk-editor-header {
display: flex;
@@ -41,6 +48,32 @@
border: 2px solid transparent;
border-radius: @block-radius/2;
outline: none;
overflow: hidden;
}
.artalk-close-comment {
display: flex;
user-select: none;
// background: repeating-linear-gradient(145deg, #f2f2f2, #f2f2f2 15px, #fff 0, #fff 30px);
padding: 0;
position: relative;
text-align: center;
min-height: 30px;
border-top: 1px solid @color-border;
margin-top: 4px;
font-weight: 100;
font-size: 12px;
place-items: center;
justify-content: center;
place-content: flex-end;
flex-direction: row;
background: #FFF;
& > span {
color: @color-meta;
border-radius: 2px;
margin-right: 14px;
}
}
.artalk-send-reply {
@@ -84,7 +117,7 @@
& > .artalk-editor-plug-wrap {
position: relative;
height: 150px;
height: 180px;
width: 100%;
overflow: hidden;
border-top: 1px solid @color-border;
+15 -1
View File
@@ -26,9 +26,23 @@
& > span {
font-size: 14px;
color: @color-main;
color: @color-meta;
cursor: pointer;
user-select: none;
&.artalk-on {
color: @color-main;
}
&.artalk-hide {
display: none;
}
&:not(:last-child):not(.artalk-hide) {
margin-right: 10px;
padding-right: 10px;
border-right: 1px solid @color-border;
}
}
}
}
+15 -13
View File
@@ -11,6 +11,7 @@
color: @color-default;
font-family: @font-family;
word-wrap: break-word;
word-break: break-all;
}
pre {
@@ -48,6 +49,10 @@
* {
box-sizing: border-box;
}
img {
max-width: 100%;
}
}
.full-layer {
@@ -123,21 +128,18 @@
@keyframes artalkLoadingIconRotate {
0% { transform: rotate(0) }
50% { transform: rotate(180deg) }
100% { transform: rotate(360deg) }
}
.artalk-loading-icon {
border-radius: 100%;
margin: 0;
border: 1px solid @color-main;
border-bottom-color: transparent;
height: 15px;
width: 15px;
background: 0 0 !important;
display: inline-block;
animation: artalkLoadingIconRotate .75s 0s linear infinite;
vertical-align: middle;
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 1px transparent;
border-top-color: #29d;
border-left-color: #29d;
border-radius: 50%;
animation: artalkLoadingIconRotate .4s linear infinite;
}
/* Animations */
@@ -174,12 +176,12 @@
}
.artalk-layer-dialog-wrap {
@formInputHeight: 25px;
@formInputHeight: 30px;
&:extend(.full-layer);
background-color: rgba(255, 255, 255, 0.94);
& > .artalk-layer-dialog {
width: 30%;
width: 25%;
& > .artalk-layer-dialog-content {
.artalk-captcha-img {
+6
View File
@@ -10,6 +10,12 @@
transition: transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
transform: translate(@width, 0px);
@media only screen and (max-width: @width) {
& {
width: 100%;
}
}
.artalk-sidebar-inner {
position: relative;
+19 -6
View File
@@ -5,23 +5,36 @@
<img src="<%= getGravatarUrl() %>">
</a>
</div>
<div class="artalk-content">
<div class="artalk-comment-main">
<div class="artalk-header">
<span class="artalk-nick">
<a target="_blank" <% if(!!data.link){ %>href="<%= data.link %>"<% } %>><%= data.nick %></a>
</span>
<% if (!!data.badge) { %>
<span class="artalk-badge"><%= data.badge %></span>
<span class="artalk-badge" <% if(!!data.badge.color){ %>style="background-color: <%= data.badge.color %>"<% } %>>
<%= data.badge.name || '管理员' %>
</span>
<% } %>
<span class="artalk-date"><%= timeAgo(new Date(data.date)) %></span>
<span class="artalk-date"><%= getDateFormatted() %></span>
<span class="artalk-ua"><%= getUserUaBrowser() %></span>
<span class="artalk-ua"><%= getUserUaOS() %></span>
</div>
<div class="artalk-body"><%- getContentMarked() %></div>
<div class="artalk-body">
<div class="artalk-content<%= !!data.is_collapsed ? ' artalk-hide artalk-type-collapsed' : '' %>"><%- getContentMarked() %></div>
<% if(!!data.is_collapsed) { %>
<div class="artalk-collapsed">
<span class="artalk-text">该评论已被系统或管理员折叠</span>
<span class="artalk-show-btn">查看内容</span>
</div>
<% } %>
</div>
<div class="artalk-footer">
<div class="artalk-comment-actions">
<span data-comment-action="reply">回复</span>
<span data-comment-action="delete" class="artalk-hide" data-only-admin-show>删除</span>
<% if(data.is_allow_reply !== false) { %><span data-comment-action="reply">回复</span><% } %>
<span data-comment-action="collapse" class="artalk-hide" data-list-ui-only-admin>
<%= !!data.is_collapsed ? '取消折叠' : '折叠' %>
</span>
<span data-comment-action="delete" class="artalk-hide" data-list-ui-only-admin>删除</span>
</div>
</div>
</div>
+2 -1
View File
@@ -5,13 +5,14 @@
<input name="link" placeholder="网址 (https://)" class="artalk-link" type="url">
</div>
<div class="artalk-editor-textarea-wrap">
<div class="artalk-close-comment" style="display: none;"><span>仅管理员可评论</span></div>
<textarea id="artalk-editor-textarea" class="artalk-editor-textarea" placeholder="<%= artalk.conf.placeholder %>"></textarea>
</div>
<div class="artalk-editor-plug-wrap" style="display: none;"></div>
<div class="artalk-editor-bottom">
<div class="artalk-editor-bottom-part artalk-left artalk-editor-plug-switcher-wrap"></div>
<div class="artalk-editor-bottom-part artalk-right">
<button type="button" class="artalk-send-btn">发送评论</button>
<button type="button" class="artalk-send-btn"><%= artalk.conf.sendBtn %></button>
</div>
</div>
<div class="artalk-editor-notify-wrap"></div>
+2 -1
View File
@@ -5,7 +5,8 @@
条评论
</div>
<div class="artalk-right-action">
<span data-action="open-sidebar" style="display: none;">通知中心</span>
<span data-action="admin-close-comment" class="artalk-hide" data-list-ui-only-admin>关闭评论</span>
<span data-action="open-sidebar" class="artalk-hide artalk-on">通知中心</span>
</div>
</div>
<div class="artalk-list-body">
+56
View File
@@ -37,4 +37,60 @@ export default class Utils {
left: rect.left + window.scrollX
}
}
public static timeAgo (date: Date) {
try {
const oldTime = date.getTime()
const currTime = new Date().getTime()
const diffValue = currTime - oldTime
const days = Math.floor(diffValue / (24 * 3600 * 1000))
if (days === 0) {
// 计算相差小时数
const leave1 = diffValue % (24 * 3600 * 1000) // 计算天数后剩余的毫秒数
const hours = Math.floor(leave1 / (3600 * 1000))
if (hours === 0) {
// 计算相差分钟数
const leave2 = leave1 % (3600 * 1000) // 计算小时数后剩余的毫秒数
const minutes = Math.floor(leave2 / (60 * 1000))
if (minutes === 0) {
// 计算相差秒数
const leave3 = leave2 % (60 * 1000) // 计算分钟数后剩余的毫秒数
const seconds = Math.round(leave3 / 1000)
return `${seconds} 秒前`
}
return `${minutes} 分钟前`
}
return `${hours} 小时前`
}
if (days < 0) return '刚刚'
if (days < 8) {
return `${days} 天前`
}
return this.dateFormat(date)
} catch (error) {
console.error(error)
return ' - '
}
}
public static padWithZeros (vNumber: number, width: number) {
let numAsString = vNumber.toString()
while (numAsString.length < width) {
numAsString = `0${numAsString}`
}
return numAsString
}
public static dateFormat (date: Date) {
const vDay = this.padWithZeros(date.getDate(), 2)
const vMonth = this.padWithZeros(date.getMonth() + 1, 2)
const vYear = this.padWithZeros(date.getFullYear(), 2)
// var vHour = padWithZeros(date.getHours(), 2);
// var vMinute = padWithZeros(date.getMinutes(), 2);
// var vSecond = padWithZeros(date.getSeconds(), 2);
return `${vYear}-${vMonth}-${vDay}`
}
}
+24 -2
View File
@@ -1,11 +1,12 @@
import Artalk from '../Artalk'
import ArtalkContext from '../ArtalkContext'
import Utils from '.'
export default class Ui extends ArtalkContext {
public el: HTMLElement
constructor () {
super()
constructor (artalk: Artalk) {
super(artalk)
this.el = this.artalk.el
}
@@ -152,4 +153,25 @@ export default class Ui extends ArtalkContext {
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop))
}
playFadeAnim (elem: HTMLElement, after?: () => void, type: 'in'|'out' = 'in') {
elem.classList.add(`artalk-fade-${type}`)
// 动画结束清除 class
const onAnimEnded = () => {
elem.classList.remove(`artalk-fade-${type}`)
elem.removeEventListener('animationend', onAnimEnded)
if (after) after()
}
elem.addEventListener('animationend', onAnimEnded)
}
/** 播放渐入动画 */
playFadeInAnim (elem: HTMLElement, after?: () => void) {
this.playFadeAnim(elem, after, 'in')
}
/** 播放渐出动画 */
playFadeOutAnim (elem: HTMLElement, after?: () => void) {
this.playFadeAnim(elem, after, 'out')
}
}
+5
View File
@@ -14,6 +14,11 @@ export interface ArtalkConfig {
*/
noComment?: string
/**
* 发送按钮文字
*/
sendBtn?: string
/**
* 默认头像(URL or Gravatar Type)
*/
+22
View File
@@ -25,6 +25,21 @@ export interface CommentData {
/** 评论日期 */
date: string
/** 是否折叠 */
is_collapsed?: boolean
/** 是否为管理员 */
is_admin?: boolean
/** 徽章 */
badge?: {
name?: string
color?: string
}
/** 是否允许回复 */
is_allow_reply?: boolean
}
export interface ListData {
@@ -48,4 +63,11 @@ export interface ListData {
/** 管理员加密后的邮箱 */
admin_encrypted_emails: string[]
/** 页面信息 */
page: {
id: number
page_key: string
is_close_comment: boolean
}
}
+10 -6
View File
@@ -1,6 +1,7 @@
/* eslint-disable global-require */
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
@@ -34,8 +35,9 @@ module.exports = (env, argv) => {
filename: '[name].js',
library: '[name]',
libraryTarget: 'umd',
libraryExport: 'default',
umdNamedDefine: true
libraryExport: '',
umdNamedDefine: true,
globalObject: 'this'
},
plugins: [
new HtmlWebpackPlugin({
@@ -43,8 +45,10 @@ module.exports = (env, argv) => {
filename: `${ROOT_PATH}/index.html`,
template: `${ROOT_PATH}/index-tpl.ejs`,
inject: 'head',
hash: true
hash: true,
alwaysWriteToDisk: true
}),
new HtmlWebpackHarddiskPlugin(),
new webpack.DefinePlugin({
ARTALK_VERSION: `"${VERSION}"`,
'process.env.NODE_ENV': JSON.stringify(NODE_ENV)
@@ -108,7 +112,7 @@ module.exports = (env, argv) => {
lessOptions: {
paths: [SRC_PATH]
},
prependData: '@import "css/_variables.less";'
prependData: '@import "css/_variables.less";@import "css/_extend.less";'
}
}
],
@@ -142,8 +146,8 @@ module.exports = (env, argv) => {
open: true,
hot: true,
inline: true,
publicPath: '/dist/',
compress: true,
publicPath: '/dist/', // 打包文件生成路径(内存中)
contentBase: path.join(__dirname, '/'),
stats: 'errors-only',
overlay: {
errors: true,
+7
View File
@@ -3719,6 +3719,13 @@ html-minifier@^3.2.3:
relateurl "0.2.x"
uglify-js "3.4.x"
html-webpack-harddisk-plugin@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/html-webpack-harddisk-plugin/-/html-webpack-harddisk-plugin-1.0.1.tgz#4fc034d3440ec2a223dbe8fa1a5a2eb905e86fa5"
integrity sha512-GWfutTqfxOe53qEKocqAFrrLVP/EJbmJZDAS5jBBmALkfyc2aakoQkSgo3fitYJORWcN0Fi8ekuySxfffhhRYw==
dependencies:
mkdirp "^0.5.1"
html-webpack-plugin@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b"