From 8e6028fd768fa5c2ac7eaeafb6241020bc1a1970 Mon Sep 17 00:00:00 2001 From: xCss Date: Mon, 1 Apr 2019 20:42:36 +0800 Subject: [PATCH] some update 2019-04-01 20:42 --- dist/Valine.Pure.min.js | 2 +- dist/Valine.min.js | 2 +- index.js | 1008 +------------------------- index.scss | 531 -------------- src/index.js | 1489 ++++++++++++++++++++++++--------------- src/index.scss | 442 ++++++++---- 6 files changed, 1194 insertions(+), 2280 deletions(-) delete mode 100644 index.scss diff --git a/dist/Valine.Pure.min.js b/dist/Valine.Pure.min.js index 56f61f7c..9b44ceba 100644 --- a/dist/Valine.Pure.min.js +++ b/dist/Valine.Pure.min.js @@ -2,7 +2,7 @@ * Valine v1.3.6 * (c) 2017-2019 xCss * Released under the GPL-2.0 License. - * Last Update: 2019-04-01 14:38:00 + * Last Update: 2019-4-1 20:42:15 */ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Valine=t():e.Valine=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=1)}([function(e,t,n){var r,i,o;/*! autosize 4.0.2 diff --git a/dist/Valine.min.js b/dist/Valine.min.js index c060b26b..9447dff6 100644 --- a/dist/Valine.min.js +++ b/dist/Valine.min.js @@ -2,7 +2,7 @@ * Valine v1.3.6 * (c) 2017-2019 xCss * Released under the GPL-2.0 License. - * Last Update: 2019-04-01 14:38:00 + * Last Update: 2019-4-1 20:42:15 */ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Valine=t():e.Valine=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=17)}([function(e,t,n){var r,i,o;/*! autosize 4.0.2 diff --git a/index.js b/index.js index a87cab7d..bc2d6843 100644 --- a/index.js +++ b/index.js @@ -1,1006 +1,6 @@ -const VERSION = require('../package.json').version; -const md5 = require('blueimp-md5'); -const marked = require('marked'); -const autosize = require('autosize'); -const timeAgo = require('./utils/timeago'); -const detect = require('./utils/detect'); -const Utils = require('./utils/htmlUtils'); -const Emoji = require('./plugins/emojis'); -const hanabi = require('hanabi'); -const LINKREG = /^https?\:\/\//; +'use strict' -const defaultComment = { - comment: '', - nick: 'Anonymous', - mail: '', - link: '', - ua: navigator.userAgent, - url: '' -}; -const locales = { - 'zh-cn': { - head: { - nick: '昵称', - mail: '邮箱', - link: '网址(http://)', - }, - tips: { - comments: '评论', - sofa: '快来做第一个评论的人吧~', - busy: '还在提交中,请稍候...', - again: '这么简单也能错,也是没谁了.' - }, - ctrl: { - reply: '回复', - ok: '好的', - sure: '确认', - cancel: '取消', - confirm: '确认', - continue: '继续', - more: '查看更多...', - try: '再试试?', - preview: '预览', - emoji: '表情' - }, - error: { - 99: '初始化失败,请检查init中的`el`元素.', - 100: '初始化失败,请检查你的AppId和AppKey.', - 401: '未经授权的操作,请检查你的AppId和AppKey.', - 403: '访问被api域名白名单拒绝,请检查你的安全域名设置.', - }, - timeago: { - seconds: '秒前', - minutes: '分钟前', - hours: '小时前', - days: '天前', - now: '刚刚' - } - }, - en: { - head: { - nick: 'NickName', - mail: 'E-Mail', - link: 'Website(http://)', - }, - tips: { - comments: 'Comments', - sofa: 'No comments yet.', - busy: 'Submit is busy, please wait...', - again: 'Sorry, this is a wrong calculation.' - }, - ctrl: { - reply: 'Reply', - ok: 'Ok', - sure: 'Sure', - cancel: 'Cancel', - confirm: 'Confirm', - continue: 'Continue', - more: 'Load More...', - try: 'Once More?', - preview: 'Preview', - emoji: 'Emoji' - }, - error: { - 99: 'Initialization failed, Please check the `el` element in the init method.', - 100: 'Initialization failed, Please check your appId and appKey.', - 401: 'Unauthorized operation, Please check your appId and appKey.', - 403: 'Access denied by api domain white list, Please check your security domain.', - }, - timeago: { - seconds: 'seconds ago', - minutes: 'minutes ago', - hours: 'hours ago', - days: 'days ago', - now: 'just now' - } - } -} +var Valine = require('./dist/Valine.min.js') -let _avatarSetting = { - cdn: 'https://gravatar.loli.net/avatar/', - ds: ['mp', 'identicon', 'monsterid', 'wavatar', 'robohash', 'retro', ''], - params: '', - hide: false - }, - META = ['nick', 'mail', 'link'], - _store = Storage && localStorage && localStorage instanceof Storage && localStorage, - _path = location.pathname.replace(/index\.html?$/, ''); - -function ValineFactory(option) { - let root = this - // Valine init - !!option && root.init(option); - return root; -} - -/** - * Valine Init - * @param {Object} option - */ -ValineFactory.prototype.init = function (option) { - if (typeof document === 'undefined') { - console && console.warn('Sorry, Valine does not support Server-side rendering.') - return; - } - let root = this; - try { - let { - lang, - langMode, - avatar, - avatarForce, - avatar_cdn, - notify, - verify, - visitor, - pageSize, - recordIP - } = option; - let ds = _avatarSetting['ds']; - let force = avatarForce ? '&q=' + Math.random().toString(32).substring(2) : ''; - - lang && langMode && root.installLocale(lang, langMode); - root.locale = root.locale || locales[lang || 'zh-cn']; - root.notify = notify || false; - root.verify = verify || false; - - if (recordIP) { - let ipScript = Utils.create('script', 'src', '//api.ip.sb/jsonip?callback=getIP'); - let s = document.getElementsByTagName("script")[0]; - s.parentNode.insertBefore(ipScript, s); - // 获取IP - window.getIP = function (json) { - defaultComment['ip'] = json.ip; - } - } - - _avatarSetting['params'] = `?d=${(ds.indexOf(avatar) > -1 ? avatar : 'mp')}&v=${VERSION}${force}`; - _avatarSetting['hide'] = avatar === 'hide' ? true : false; - _avatarSetting['cdn'] = LINKREG.test(avatar_cdn) ? avatar_cdn : _avatarSetting['cdn'] - - _path = option.path || _path; - - let size = Number(pageSize || 10); - option.pageSize = !isNaN(size) ? (size < 1 ? 10 : size) : 10; - - marked.setOptions({ - renderer: new marked.Renderer(), - highlight: option.highlight === false ? null : hanabi, - gfm: true, - tables: true, - breaks: true, - pedantic: false, - sanitize: false, - smartLists: true, - smartypants: true - }); - - if (!AV) { - setTimeout(() => { - root.init(option) - }, 20) - return; - } - let id = option.app_id || option.appId; - let key = option.app_key || option.appKey; - if (!id || !key) throw 99; - AV.applicationId && delete AV._config.applicationId || (AV.applicationId = null); - AV.applicationKey && delete AV._config.applicationKey || (AV.applicationKey = null); - AV.init({ - appId: id, - appKey: key - }); - - // get comment count - let els = Utils.findAll(document, '.valine-comment-count'); - for (let i = 0, len = els.length; i < len; i++) { - let el = els[i]; - if (el) { - let k = Utils.attr(el, 'data-xid'); - if (k) { - root.Q(k).count().then(n => { - el.innerText = n - }).catch(ex => { - el.innerText = 0 - }) - } - } - } - - // Counter - visitor && CounterFactory.add(AV.Object.extend('Counter')); - - let el = option.el || null; - let _el = Utils.findAll(document, el); - el = el instanceof HTMLElement ? el : (_el[_el.length - 1] || null); - if (!el) return; - root.el = el; - root.el.classList.add('v'); - - _avatarSetting['hide'] && root.el.classList.add('hide-avatar'); - option.meta = (option.guest_info || option.meta || META).filter(item => META.indexOf(item) > -1); - let inputEl = (option.meta.length == 0 ? META : option.meta).map(item => { - let _t = item == 'mail' ? 'email' : 'text'; - return META.indexOf(item) > -1 ? `` : '' - }); - root.placeholder = option.placeholder || 'Just Go Go'; - - root.el.innerHTML = `
${inputEl.join('')}
${root.locale['ctrl']['emoji']} | ${root.locale['ctrl']['preview']}
Powered By Valine
v${VERSION}
`; - - // Empty Data - let vempty = Utils.find(root.el, '.vempty'); - root.nodata = { - show(txt) { - vempty.innerHTML = txt || root.locale['tips']['sofa']; - Utils.attr(vempty, 'style', 'display:block;'); - return root; - }, - hide() { - Utils.attr(vempty, 'style', 'display:none;'); - return root; - } - } - - // loading - let _spinner = Utils.create('div', 'class', 'vloading'); - // loading control - let _vlist = Utils.find(root.el, '.vlist'); - root.loading = { - show(mt) { - let _vlis = Utils.findAll(_vlist, '.vcard'); - if (mt) _vlist.insertBefore(_spinner, _vlis[0]); - else _vlist.appendChild(_spinner); - root.nodata.hide(); - return root; - }, - hide() { - let _loading = Utils.find(_vlist, '.vloading'); - if (_loading) Utils.remove(_loading); - Utils.findAll(_vlist, '.vcard').length === 0 && root.nodata.show() - return root; - } - }; - // alert - let _mark = Utils.find(root.el, '.vmark'); - root.alert = { - /** - * { - * type:0/1, - * text:'', - * ctxt:'', - * otxt:'', - * cb:fn - * } - * - * @param {Object} o - */ - show(o) { - _mark.innerHTML = `
${o && o.text || 1}
`; - let _vbtns = Utils.find(_mark, '.vbtns'); - let _cBtn = ``; - let _oBtn = ``; - _vbtns.innerHTML = `${_cBtn}${o && o.type && _oBtn}`; - Utils.on('click', Utils.find(_mark, '.vcancel'), (e) => { - root.alert.hide(); - }) - Utils.attr(_mark, 'style', 'display:block;'); - if (o && o.type) { - let _ok = Utils.find(_mark, '.vsure'); - Utils.on('click', _ok, (e) => { - root.alert.hide(); - o.cb && o.cb(); - }); - } - return root; - }, - hide() { - Utils.attr(_mark, 'style', 'display:none;'); - return root; - } - } - - // Bind Event - root.bind(option); - - } catch (ex) { - root.ErrorHandler(ex) - } - return root; -} - -// 新建Counter对象 -let createCounter = function (Counter, o) { - let newCounter = new Counter(); - let acl = new AV.ACL(); - acl.setPublicReadAccess(true); - acl.setPublicWriteAccess(true); - newCounter.setACL(acl); - newCounter.set('url', o.url) - newCounter.set('xid', o.xid) - newCounter.set('title', o.title) - newCounter.set('time', 1) - newCounter.save().then(ret => { - Utils.find(o.el, '.leancloud-visitors-count').innerText = 1 - }).catch(ex => { - console.log(ex) - }); -} -let CounterFactory = { - add(Counter) { - let lvs = Utils.findAll(document, '.leancloud_visitors,.leancloud-visitors'); - if (lvs.length) { - let lv = lvs[0]; - let url = Utils.attr(lv, 'id'); - let title = Utils.attr(lv, 'data-flag-title'); - let xid = encodeURI(url); - let o = { - el: lv, - url: url, - xid: xid, - title: title - } - // 判断是否需要+1 - if (decodeURI(url) === decodeURI(_path)) { - let query = new AV.Query(Counter); - query.equalTo('url', url); - query.find().then(ret => { - if (ret.length > 0) { - let v = ret[0]; - v.increment("time"); - v.save().then(rt => { - Utils.find(lv, '.leancloud-visitors-count').innerText = rt.get('time') - }).catch(ex => { - console.log(ex) - }); - } else { - createCounter(Counter, o) - } - }).catch(ex => { - ex.code == 101 && createCounter(Counter, o) - }) - } else CounterFactory.show(Counter, lvs) - } - }, - show(Counter, lvs) { - let COUNT_CONTAINER_REF = '.leancloud-visitors-count'; - - // 重置所有计数 - Utils.each(lvs, function (idx, el) { - let cel = Utils.find(el, COUNT_CONTAINER_REF); - if (cel) cel.innerText = 0 - }) - let urls = []; - for (let i in lvs) { - if (lvs.hasOwnProperty(i)) urls.push(Utils.attr(lvs[i], 'id')) - } - if (urls.length) { - let query = new AV.Query(Counter); - query.containedIn('url', urls); - query.find().then(ret => { - if (ret.length > 0) { - Utils.each(ret, function (idx, item) { - let url = item.get('url'); - let time = item.get('time'); - let el = Utils.find(document, `.leancloud_visitors[id="${url}"]`) || Utils.find(document, `.leancloud-visitors[id="${url}"]`); - let cel = Utils.find(el, COUNT_CONTAINER_REF); - if (cel) cel.innerText = time - }); - } - }).catch(ex => { - console.error(ex) - }) - } - } -} - -/** - * LeanCloud SDK Query Util - * @param {String} url - * @param {String} id - */ -ValineFactory.prototype.Q = function (k) { - let len = arguments.length - if (len == 1) { - let notExist = new AV.Query('Comment'); - notExist.doesNotExist('rid'); - let isEmpty = new AV.Query('Comment'); - isEmpty.equalTo('rid', ''); - let q = AV.Query.or(notExist, isEmpty); - q.equalTo('url', decodeURI(k)); - q.addDescending('createdAt'); - q.addDescending('insertedAt'); - return q; - } else { - let ids = JSON.stringify(arguments[1]).replace(/(\[|\])/g, ''); - let cql = `select * from Comment where rid in (${ids}) order by -createdAt,-createdAt`; - return AV.Query.doCloudQuery(cql) - } -} - -ValineFactory.prototype.ErrorHandler = function (ex) { - // console.log(ex.code,ex.message) - let root = this; - root.el && root.loading.hide().nodata.hide() - if (({}).toString.call(ex) === "[object Error]") { - let code = ex.code || '', - t = root.locale['error'][code], - msg = t || ex.message || ex.error || ''; - if (code == 101) { - root.nodata.show() - } else root.el && root.nodata.show(`
Code ${code}: ${msg}
`) || - console && console.error(`Code ${code}: ${msg}`) - } else { - root.el && root.nodata.show(`
${JSON.stringify(ex)}
`) || - console && console.error(JSON.stringify(ex)) - } - return; -} - -/** - * install Multi language support - * @param {String} locale langName - * @param {Object} mode langSource - */ -ValineFactory.prototype.installLocale = function (locale, mode) { - let root = this; - mode = mode || {}; - if (locale) { - // locales[locale] = JSON.stringify(Object.keys(locales['zh-cn']))==JSON.stringify(Object.keys(mode)) ? mode : undefined; - locales[locale] = mode; - root.locale = locales[locale] || locales['zh-cn']; - } - return root; -} - -/** - * - * @param {String} path - */ -ValineFactory.prototype.setPath = function (path) { - _path = path || _path; - return this -} - -/** - * Bind Event - */ -ValineFactory.prototype.bind = function (option) { - let root = this; - - // load emojis - let _vemojis = Utils.find(root.el, '.vemojis'); - let _vpreview = Utils.find(root.el, '.vpreview'); - // emoji 操作 - let _emojiCtrl = Utils.find(root.el, '.vemoji-btn'); - // 评论内容预览 - let _vpreviewCtrl = Utils.find(root.el, `.vpreview-btn`); - let emojiData = Emoji.data; - for (let key in emojiData) { - if (emojiData.hasOwnProperty(key)) { - (function (name, val) { - let _i = Utils.create('i', { - 'name': name, - 'title': name - }); - _i.innerHTML = val; - _vemojis.appendChild(_i); - Utils.on('click', _i, (e) => { - let _veditor = Utils.find(root.el, '.veditor'); - _insertAtCaret(_veditor, val) - syncContentEvt(_veditor) - }); - })(key, emojiData[key]) - } - } - root.emoji = { - show() { - root.preview.hide(); - Utils.attr(_emojiCtrl, 'v', 1); - Utils.removeAttr(_vpreviewCtrl, 'v'); - Utils.attr(_vemojis, 'style', 'display:block'); - return root.emoji - }, - hide() { - Utils.removeAttr(_emojiCtrl, 'v'); - Utils.attr(_vemojis, 'style', 'display:hide'); - return root.emoji - } - } - root.preview = { - show() { - if (defaultComment['comment']) { - root.emoji.hide(); - Utils.attr(_vpreviewCtrl, 'v', 1); - Utils.removeAttr(_emojiCtrl, 'v'); - _vpreview.innerHTML = defaultComment['comment']; - Utils.attr(_vpreview, 'style', 'display:block'); - _activeOtherFn() - } - return root.preview - }, - hide() { - Utils.removeAttr(_vpreviewCtrl, 'v'); - Utils.attr(_vpreview, 'style', 'display:none'); - return root.preview - }, - empty() { - _vpreview.innerHtml = ''; - return root.preview - } - } - - /** - * XSS filter - * @param {String} content Html String - */ - let xssFilter = (content) => { - let vNode = Utils.create('div'); - vNode.insertAdjacentHTML('afterbegin', content); - let ns = Utils.findAll(vNode, "*"); - let rejectNodes = ['INPUT', 'STYLE', 'SCRIPT', 'IFRAME', 'FRAME', 'AUDIO', 'VIDEO', 'EMBED', 'META', 'TITLE', 'LINK']; - let __replaceVal = (node, attr) => { - let val = Utils.attr(node, attr); - val && Utils.attr(node, attr, val.replace(/(javascript|eval)/ig, '')); - } - Utils.each(ns, (idx, n) => { - if (n.nodeType !== 1) return; - if (rejectNodes.indexOf(n.nodeName) > -1) { - // console.log(n.nodeName) - if (n.nodeName === 'INPUT' && Utils.attr(n, 'type') === 'checkbox') Utils.attr(n, 'disabled', 'disabled'); - else Utils.remove(n); - } - if (n.nodeName === 'A') __replaceVal(n, 'href') - Utils.clearAttr(n) - }) - - return vNode.innerHTML - } - - /** - * 评论框内容变化事件 - * @param {HTMLElement} el - */ - let syncContentEvt = (_el) => { - let _v = 'comment'; - let _val = (_el.value || ''); - _val = Emoji.parse(_val); - _el.value = _val; - let ret = xssFilter(marked(_val)); - defaultComment[_v] = ret; - _vpreview.innerHTML = ret; - if (_val) autosize(_el); - else autosize.destroy(_el) - } - - // 显示/隐藏 Emojis - Utils.on('click', _emojiCtrl, (e) => { - let _vi = Utils.attr(_emojiCtrl, 'v'); - if (_vi) { - root.emoji.hide() - } else { - root.emoji.show(); - } - }); - - Utils.on('click', _vpreviewCtrl, function (e) { - let _vi = Utils.attr(_vpreviewCtrl, 'v'); - if (_vi) { - root.preview.hide(); - } else { - root.preview.show(); - } - }); - - let meta = option.meta; - let inputs = {}; - - // 同步操作 - let mapping = { - veditor: "comment" - } - for (let i = 0, len = meta.length; i < len; i++) { - mapping[`v${meta[i]}`] = meta[i]; - } - for (let i in mapping) { - if (mapping.hasOwnProperty(i)) { - let _v = mapping[i]; - let _el = Utils.find(root.el, `.${i}`); - inputs[_v] = _el; - _el && Utils.on('input change blur', _el, (e) => { - if (_v === 'comment') { - syncContentEvt(_el) - } else { - defaultComment[_v] = Utils.escape(_el.value.replace(/(^\s*)|(\s*$)/g, "")); - } - }); - } - } - - let _insertAtCaret = (field, val) => { - if (document.selection) { - //For browsers like Internet Explorer - field.focus(); - let sel = document.selection.createRange(); - sel.text = val; - field.focus(); - } else if (field.selectionStart || field.selectionStart == '0') { - //For browsers like Firefox and Webkit based - let startPos = field.selectionStart; - let endPos = field.selectionEnd; - let scrollTop = field.scrollTop; - field.value = field.value.substring(0, startPos) + val + field.value.substring(endPos, field.value.length); - field.focus(); - field.selectionStart = startPos + val.length; - field.selectionEnd = startPos + val.length; - field.scrollTop = scrollTop; - } else { - field.focus(); - field.value += val; - } - } - let createVquote = id => { - let vcontent = Utils.find(root.el, ".vh[rootid='" + id + "']"); - let vquote = Utils.find(vcontent, '.vquote'); - if (!vquote) { - vquote = Utils.create('div', 'class', 'vquote'); - vcontent.appendChild(vquote); - } - return vquote - } - - let query = (no = 1) => { - let size = option.pageSize; - let count = Number(Utils.find(root.el, '.vnum').innerText); - root.loading.show(); - let cq = root.Q(_path); - cq.limit(size); - cq.skip((no - 1) * size); - cq.find().then(rets => { - let len = rets.length; - let rids = [] - for (let i = 0; i < len; i++) { - let ret = rets[i]; - rids.push(ret.id) - insertDom(ret, Utils.find(root.el, '.vlist'), !0) - } - // load children comment - root.Q(_path, rids).then(ret => { - let childs = ret && ret.results || [] - for (let k = 0; k < childs.length; k++) { - let child = childs[k]; - insertDom(child, createVquote(child.get('rid'))) - } - }) - let _vpage = Utils.find(root.el, '.vpage'); - _vpage.innerHTML = size * no < count ? `` : ''; - let _vmore = Utils.find(_vpage, '.vmore'); - if (_vmore) { - Utils.on('click', _vmore, (e) => { - _vpage.innerHTML = ''; - query(++no); - }) - } - root.loading.hide(); - }).catch(ex => { - root.loading.hide().ErrorHandler(ex) - }) - } - - root.Q(_path).count().then(num => { - if (num > 0) { - Utils.attr(Utils.find(root.el, '.vinfo'), 'style', 'display:block;'); - Utils.find(root.el, '.vcount').innerHTML = `${num} ${root.locale['tips']['comments']}`; - query(); - } else { - root.loading.hide(); - } - }).catch(ex => { - root.ErrorHandler(ex) - }); - - let insertDom = (rt, node, mt) => { - - let _vcard = Utils.create('div', { - 'class': 'vcard', - 'id': rt.id - }); - let _img = _avatarSetting['hide'] ? '' : ``; - let ua = rt.get('ua') || ''; - let uaMeta = ''; - if (ua) { - ua = detect(ua); - let browser = `${ua.browser} ${ua.version}`; - let os = `${ua.os} ${ua.osVersion}`; - uaMeta = `${browser} ${os}`; - } - let _nick = ''; - let _t = rt.get('link') || ''; - _nick = _t ? `${rt.get("nick")}` : `${rt.get('nick')}`; - _vcard.innerHTML = `${_img} -
-
${_nick} ${uaMeta}
-
- ${timeAgo(rt.get('insertedAt') || rt.createdAt,root.locale)} - ${root.locale['ctrl']['reply']} -
-
- ${xssFilter(rt.get("comment"))} -
-
`; - let _vat = Utils.find(_vcard, '.vat'); - let _as = Utils.findAll(_vcard, 'a'); - for (let i = 0, len = _as.length; i < len; i++) { - let _a = _as[i]; - if (_a && (Utils.attr(_a, 'class') || '').indexOf('at') == -1) { - Utils.attr(_a, { - 'target': '_blank', - 'rel': 'nofollow' - }); - } - } - let _vlis = Utils.findAll(node, '.vcard'); - if (mt) node.appendChild(_vcard); - else node.insertBefore(_vcard, _vlis[0]); - let _vcontent = Utils.find(_vcard, '.vcontent'); - if (_vcontent) expandEvt(_vcontent); - if (_vat) bindAtEvt(_vat, rt); - _activeOtherFn() - } - - - let _activeOtherFn = () => { - setTimeout(function () { - try { - let MathJax = MathJax || ''; - MathJax && MathJax.Hub.Queue(["Typeset", MathJax.Hub]); - $('pre code').each(function (i, block) { - hljs.highlightBlock(block); - }) - $('code.hljs').each(function (i, block) { - hljs.lineNumbersBlock(block); - }); - } catch (error) { - - } - }, 20) - } - - let _activeHLJS = () => {} - - // expand event - let expandEvt = (el) => { - setTimeout(function () { - if (el.offsetHeight > 180) { - el.classList.add('expand'); - Utils.on('click', el, (e) => { - Utils.attr(el, 'class', 'vcontent'); - }) - } - }) - } - - let atData = {} - // at event - let bindAtEvt = (el, rt) => { - Utils.on('click', el, (e) => { - let at = `@${Utils.escape(rt.get('nick'))}`; - atData = { - 'at': Utils.escape(at) + ' ', - 'rid': rt.get('rid') || rt.id, - 'pid': rt.id, - 'rmail': rt.get('mail'), - } - // console.log(atData) - Utils.attr(inputs['comment'], 'placeholder', at); - inputs['comment'].focus(); - }) - } - - // cache - let getCache = () => { - let s = _store && _store.ValineCache; - if (s) { - s = JSON.parse(s); - let m = meta; - for (let i in m) { - let k = m[i]; - Utils.find(root.el, `.v${k}`).value = Utils.unescape(s[k]); - defaultComment[k] = s[k]; - } - } - } - getCache(); - // reset form - let reset = () => { - defaultComment['comment'] = ""; - inputs['comment'].value = ""; - syncContentEvt(inputs['comment']) - Utils.attr(inputs['comment'], 'placeholder', root.placeholder); - atData = {}; - root.preview.empty().hide(); - } - - // submitsubmit - let submitBtn = Utils.find(root.el, '.vsubmit'); - let submitEvt = (e) => { - if (Utils.attr(submitBtn, 'disabled')) { - root.alert.show({ - type: 0, - text: `${root.locale['tips']['busy']}ヾ(๑╹◡╹)ノ"`, - ctxt: root.locale['ctrl']['ok'] - }) - return; - } - if (defaultComment['comment'] == '') { - inputs['comment'].focus(); - return; - } - defaultComment['nick'] = defaultComment['nick'] || 'Anonymous'; - - // return; - if (root.notify || root.verify) { - verifyEvt(commitEvt) - } else { - commitEvt(); - } - } - - // setting access - let getAcl = () => { - let acl = new AV.ACL(); - acl.setPublicReadAccess(!0); - acl.setPublicWriteAccess(false); - return acl; - } - - let commitEvt = () => { - Utils.attr(submitBtn, 'disabled', !0); - root.loading.show(!0); - // 声明类型 - let Ct = AV.Object.extend('Comment'); - // 新建对象 - let comment = new Ct(); - defaultComment['url'] = decodeURI(_path); - defaultComment['insertedAt'] = new Date(); - if (atData['rid']) { - let pid = atData['pid'] || atData['rid']; - comment.set('rid', atData['rid']); - comment.set('pid', pid); - defaultComment['comment'] = defaultComment['comment'].replace('

', `

${atData['at']} , `); - } - for (let i in defaultComment) { - if (defaultComment.hasOwnProperty(i)) { - let _v = defaultComment[i]; - comment.set(i, _v); - } - } - comment.setACL(getAcl()); - comment.save().then(ret => { - defaultComment['nick'] != 'Anonymous' && _store && _store.setItem('ValineCache', JSON.stringify({ - nick: defaultComment['nick'], - link: defaultComment['link'], - mail: defaultComment['mail'] - })); - let _count = Utils.find(root.el, '.vnum'); - let num = 1; - try { - if (atData['rid']) { - let vquote = Utils.find(root.el, '.vquote[rid="' + atData['rid'] + '"]') || createVquote(atData['rid']); - insertDom(ret, vquote, !0) - } else { - if (_count) { - num = Number(_count.innerText) + 1; - _count.innerText = num; - } else { - Utils.find(root.el, '.vcount').innerHTML = '1 ' + root.locale['tips']['comments'] - } - insertDom(ret, Utils.find(root.el, '.vlist')); - } - - defaultComment['mail'] && signUp({ - username: defaultComment['nick'], - mail: defaultComment['mail'] - }); - - atData['at'] && atData['rmail'] && root.notify && mailEvt({ - username: atData['at'].replace('@', ''), - mail: atData['rmail'] - }); - Utils.removeAttr(submitBtn, 'disabled'); - root.loading.hide(); - reset(); - } catch (ex) { - root.ErrorHandler(ex); - } - }).catch(ex => { - root.ErrorHandler(ex); - }) - } - - let verifyEvt = (fn) => { - let x = Math.floor((Math.random() * 10) + 1); - let y = Math.floor((Math.random() * 10) + 1); - let z = Math.floor((Math.random() * 10) + 1); - let opt = ['+', '-', 'x']; - let o1 = opt[Math.floor(Math.random() * 3)]; - let o2 = opt[Math.floor(Math.random() * 3)]; - let expre = `${x}${o1}${y}${o2}${z}`; - let subject = `${expre} = `; - root.alert.show({ - type: 1, - text: subject, - ctxt: root.locale['ctrl']['cancel'], - otxt: root.locale['ctrl']['ok'], - cb() { - let code = +Utils.find(root.el, '.vcode').value; - let ret = (new Function(`return ${expre.replace(/x/g, '*')}`))(); - if (ret === code) { - fn && fn(); - } else { - root.alert.show({ - type: 1, - text: `(T_T)${root.locale['tips']['again']}`, - ctxt: root.locale['ctrl']['cancel'], - otxt: root.locale['ctrl']['try'], - cb() { - verifyEvt(fn); - return; - } - }) - } - } - }) - } - - let signUp = (o) => { - let u = new AV.User(); - u.setUsername(o.username); - u.setPassword(o.mail); - u.setEmail(o.mail); - u.setACL(getAcl()); - return u.signUp(); - } - - let mailEvt = (o) => { - AV.User.requestPasswordReset(o.mail).then(ret => {}).catch(e => { - if (e.code == 1) { - root.alert.show({ - type: 0, - text: `ヾ(o・ω・)ノ At太频繁啦,提醒功能暂时宕机。
${e.error}`, - ctxt: root.locale['ctrl']['ok'] - }) - } else { - signUp(o).then(ret => { - mailEvt(o); - }).catch(x => { - //err(x) - }) - } - }) - } - - Utils.on('click', submitBtn, submitEvt); - Utils.on('keydown', document, function (e) { - e = event || e; - let keyCode = e.keyCode || e.which || e.charCode; - let ctrlKey = e.ctrlKey || e.metaKey; - // Shortcut key - ctrlKey && keyCode === 13 && submitEvt() - // tab key - if (keyCode === 9) { - let focus = document.activeElement.id || '' - if (focus == 'veditor') { - e.preventDefault(); - let _veditor = Utils.find(root.el, '.veditor'); - _insertAtCaret(_veditor, ' '); - } - } - }) -} - -function Valine(options) { - return new ValineFactory(options) -} - -module.exports = Valine; -module.exports.default = Valine; \ No newline at end of file +module.exports = Valine +module.exports.default = module.exports \ No newline at end of file diff --git a/index.scss b/index.scss deleted file mode 100644 index 22256db9..00000000 --- a/index.scss +++ /dev/null @@ -1,531 +0,0 @@ -/** - * @Valine - * Author: xCss - * Github: https://github.com/xCss/Valine - * Website: https://valine.js.org - */ - -.v { - * { - box-sizing: border-box; //font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", 微软雅黑, "helvetica neue", helvetica, ubuntu, roboto, noto, "segoe ui", Arial, sans-serif; - //font-size: 16px; - line-height:2; - color: #555; - transition: all .3s ease; - } - - hr{ - margin: .825rem 0; - border-color:#f6f6f6; - border-style: dashed; - } - &.hide-avatar{ - .vimg{display:none;} - } - a{ - position:relative; - cursor: pointer; - color: #1abc9c; - text-decoration:none; - display:inline-block; - &::before{ - content:''; - position:absolute; - width:0; - right:0; - bottom:0; - height:1px; - background:#1abc9c; - transition: width .3s ease; - } - &:hover { - color: #D7191A; - &::before{ - width:100%; - left:0; - right:auto; - } - } - } - pre,code{ - background-color: #f6f6f6; - color: #555; - padding: 0.2em 0.4em; - border-radius: 3px; - font-size: 85%; - 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; - } - pre{ - padding: 10px; - overflow: auto; - line-height: 1.45; - code{ - padding:0; - background:transparent; - white-space: pre-wrap; - word-break: keep-all; - } - } - blockquote{ - color: #666; - margin: .5rem 0; - padding: 0 0 0 1rem ; - border-left: 8px solid rgba(238, 238, 238, 0.5); - } - .vinput { - border: none; - resize: none; - outline: none; - padding: 10px 5px; - max-width: 100%; - font-size: .775rem; - } - input[type='checkbox'],input[type='radio']{ - display:inline-block; - vertical-align: middle; - margin-top:-2px; - } - .vwrap { - border: 1px solid #f0f0f0; - border-radius: 4px; - margin-bottom: 10px; - overflow: hidden; - position: relative; - padding: 10px; - input { - background: transparent; - } - .vedit { - position: relative; - padding-top:10px; - } - .vedit .vctrl { - text-align: right; - font-size: 12px; - } - .vedit .vctrl span { - padding: 10px; - display: inline-block; - vertical-align: middle; - cursor: pointer; - } - .vedit .vemojis { - display: none; - font-size: 18px; - text-align: justify; - max-height: 145px; - overflow: auto; - margin-bottom: 10px; - box-shadow: 0px 0 1px #f0f0f0; - } - .vedit .vemojis i { - font-style: normal; - padding: 7px 0; - width: 38px; - cursor: pointer; - text-align: center; - display: inline-block; - vertical-align: middle; - } - .vedit .vpreview { - padding: 7px; - box-shadow: 0px 0 1px #f0f0f0; - img,frame,iframe{ - max-width: 100%; - border:none; - } - } - .vheader { - .vinput { - width: 33.33%; - border-bottom: 1px #dedede dashed; - } - &.item2 .vinput { - width: 50%; - } - &.item1 .vinput { - width: 100%; - } - .vinput:focus { - border-bottom-color: #eb5055; - } - @media screen and (max-width:520px) { - .vinput { - width: 100%; - } - &.item2 .vinput { - width: 100%; - } - } - } - .vcontrol { - //background: #fdfdfd; - font-size: 0; - padding-top: 15px; - .col { - display: inline-block; - font-size: .725rem; - vertical-align: middle; - color: #ccc; - &.text-right { - text-align: right; - } - svg { - margin-right: 2px; - overflow: hidden; - fill: currentColor; - vertical-align: middle; - } - &.col-20 { - width: 20%; - } - &.col-40 { - width: 40%; - } - &.col-60 { - width: 60%; - } - &.col-80 { - width: 80%; - } - &.split { - width: 50%; - } - } - } - .vmark { - position: absolute; - background: rgba(0, 0, 0, .65); - width: 100%; - height: 100%; - left: 0; - top: 0; - .valert { - padding-top: 3rem; - .vtext { - color: #fff; - padding: 1rem 0; - } - .vcode { - width: 4.6875rem; - border-radius: .3125rem; - padding: .5rem; - background: #dedede; - &:focus { - border-color: #3090e4; - background-color: #fff; - } - } - } - @media screen and(max-width:720px) { - .valert { - padding-top: 5.5rem; - .vtext { - color: #fff; - padding: 1rem 0; - } - } - } - } - } - .power { - color: #999; - font-size: .75rem; - padding:.5rem 0; - a { - font-size: .75rem; - } - } - .vinfo { - font-size: 0; - padding: 5px; - .col { - font-size: .875rem; - display: inline-block; - width: 50%; - vertical-align: middle; - } - .vcount { - .vnum { - font-weight: 600; - font-size: 1.25rem; - } - } - } - a { - text-decoration: none; - color: #555; - // outline: none; - &:hover { - color: #222; - } - } - ul,ol { - padding: 0; - margin-left: 1.25rem; - } - .txt-center { - text-align: center; - } - .txt-right { - text-align: right; - } - .pd5 { - padding: 5px; - } - .pd10 { - padding: 10px; - } - .veditor { - width: 100%; - min-height: 8.75rem; - font-size: .875rem; - background: transparent; - resize: vertical; - transition:all .25s ease; - } - .vbtn { - transition-duration: .4s; - text-align: center; - color: #313131; - border: 1px solid #ededed; - border-radius: .3rem; - display: inline-block; - background: #ededed; - margin-bottom: 0; - font-weight: 400; - vertical-align: middle; - touch-action: manipulation; - cursor: pointer; - white-space: nowrap; - padding: .5rem 1.25rem; - font-size: .875rem; - line-height: 1.42857143; - user-select: none; - outline: none; - &+.vbtn { - margin-left: 1.25rem; - } - } - .vbtn:active, - .vbtn:hover { - color: #3090e4; - border-color: #3090e4; - background-color: #fff; - } - .vempty { - padding: 1.25rem; - text-align: center; - color: #999; - } - .vlist { - width: 100%; - .vcard { - padding-top: 1.5rem; - position: relative; - display: block; - &:after { - content: ''; - clear: both; - display: block; - } - .vimg { - width: 3.125rem; - height: 3.125rem; - float: left; - border-radius: 50%; - margin-right: .7525rem; - border:1px solid #f5f5f5; - padding:.125rem; - } - - @media screen and (max-width:720px) { - .vimg { - width: 2.5rem; - height: 2.5rem; - } - } - .vhead { - line-height: 1.5; - margin-top: 0; - .vnick { - position:relative; - font-size: .875rem; - font-weight: 500; - margin-right: .875rem; - cursor: pointer; - color: #1abc9c; - text-decoration:none; - display:inline-block; - &::before{ - content:''; - position:absolute; - width:0; - right:0; - bottom:0; - height:1px; - background:#1abc9c; - transition: width .3s ease; - } - &:hover { - color: #D7191A; - &::before{ - width:100%; - left:0; - right:auto; - } - } - } - .vsys { - display: inline-block; - padding: .2rem .5rem; - background: #ededed; - color: #b3b1b1; - font-size: .75rem; - border-radius: .2rem; - margin-right: .3rem; - } - @media screen and (max-width:520px) { - .vsys { - display: none; - } - } - } - - .vh { - overflow: hidden; - padding-bottom: .5rem; - border-bottom: 1px dashed #f5f5f5; - .vtime { - color: #b3b3b3; - font-size: .75rem; - margin-right: .875rem; - } - .vmeta { - line-height:1; - position: relative; - .vat { - font-size: .8125rem; - color: #ef2f11; - cursor: pointer; - float:right; - } - } - } - &:last-child{ - .vh{ - border-bottom:none; - } - } - .vcontent { - word-wrap: break-word; - word-break: break-all; - text-align: justify; - color: #4A4A4A; - font-size: .875rem; - line-height: 2; - position: relative; - margin-bottom: .75rem; - padding-top: .625rem; - img,iframe,frame { - max-width: 100%; - border:none; - } - &.expand { - cursor: pointer; - max-height: 11.25rem; - overflow: hidden; - &:before { - display: block; - content: ""; - position: absolute; - width: 100%; - left: 0; - top: 0; - bottom: 3.15rem; - pointer-events: none; - background: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, .9)); - } - &:after { - display: block; - content: "Click on expand"; - text-align: center; - color: #828586; - position: absolute; - width: 100%; - height: 3.15rem; - line-height: 3.15rem; - left: 0; - bottom: 0; - pointer-events: none; - background: rgba(255, 255, 255, .9); - } - } - } - - .vquote { - color: #666; - margin-top: 1rem; - padding-left: 1rem; - border-left: 1px dashed rgba(238, 238, 238, 0.5); - .vimg{ - width: 2.225rem; - height: 2.225rem; - } - } - - } - } - .vpage { - .vmore{ - margin:1rem 0; - } - } - .clear { - content: ''; - display: block; - clear: both; - } - /************ Loading ************/ - $base-line-height: 40px; - $white: rgb(220, 220, 220); - $off-white: rgb(160, 160, 160); - $spin-duration: 1s; - $pulse-duration: 750ms; - @keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } - } - @keyframes pulse { - 50% { - background: $white; - } - } - .vloading { - position: relative; - padding: 20px; - display: block; - height: 80px; - } - .vloading::before { - box-sizing: border-box; - content: ''; - position: absolute; - display: inline-block; - top: 20px; - left: 50%; - margin-left: -20px; - width: $base-line-height; - height: $base-line-height; - border: 6px double $off-white; - border-top-color: transparent; - border-bottom-color: transparent; - border-radius: 50%; - animation: spin $spin-duration infinite linear; - } -} \ No newline at end of file diff --git a/src/index.js b/src/index.js index 39959f20..a3234bd1 100644 --- a/src/index.js +++ b/src/index.js @@ -1,144 +1,268 @@ -/** - * @Valine - * Author: xCss - * Github: https://github.com/xCss/Valine - * Website: https://valine.js.org - */ -import md5 from 'blueimp-md5'; -import marked from 'marked'; -const gravatar = { - cdn: 'https://gravatar.cat.net/avatar/', - ds: ['mm', 'identicon', 'monsterid', 'wavatar', 'retro', ''], - params: '?s=40', - hide: !1 -}; +const VERSION = require('../package.json').version; +const md5 = require('blueimp-md5'); +const marked = require('marked'); +const autosize = require('autosize'); +const timeAgo = require('./utils/timeago'); +const detect = require('./utils/detect'); +const Utils = require('./utils/htmlUtils'); +const Emoji = require('./plugins/emojis'); +const hanabi = require('hanabi'); +const LINKREG = /^https?\:\/\//; + const defaultComment = { comment: '', - rid: '', - nick: 'Guest', + nick: 'Anonymous', mail: '', link: '', ua: navigator.userAgent, - url: '', - pin: 0, - like: 0 + url: '' }; -const GUEST_INFO = ['nick', 'mail', 'link']; +const locales = { + 'zh-cn': { + head: { + nick: '昵称', + mail: '邮箱', + link: '网址(http://)', + }, + tips: { + comments: '评论', + sofa: '快来做第一个评论的人吧~', + busy: '还在提交中,请稍候...', + again: '这么简单也能错,也是没谁了.' + }, + ctrl: { + reply: '回复', + ok: '好的', + sure: '确认', + cancel: '取消', + confirm: '确认', + continue: '继续', + more: '查看更多...', + try: '再试试?', + preview: '预览', + emoji: '表情' + }, + error: { + 99: '初始化失败,请检查init中的`el`元素.', + 100: '初始化失败,请检查你的AppId和AppKey.', + 401: '未经授权的操作,请检查你的AppId和AppKey.', + 403: '访问被api域名白名单拒绝,请检查你的安全域名设置.', + }, + timeago: { + seconds: '秒前', + minutes: '分钟前', + hours: '小时前', + days: '天前', + now: '刚刚' + } + }, + en: { + head: { + nick: 'NickName', + mail: 'E-Mail', + link: 'Website(http://)', + }, + tips: { + comments: 'Comments', + sofa: 'No comments yet.', + busy: 'Submit is busy, please wait...', + again: 'Sorry, this is a wrong calculation.' + }, + ctrl: { + reply: 'Reply', + ok: 'Ok', + sure: 'Sure', + cancel: 'Cancel', + confirm: 'Confirm', + continue: 'Continue', + more: 'Load More...', + try: 'Once More?', + preview: 'Preview', + emoji: 'Emoji' + }, + error: { + 99: 'Initialization failed, Please check the `el` element in the init method.', + 100: 'Initialization failed, Please check your appId and appKey.', + 401: 'Unauthorized operation, Please check your appId and appKey.', + 403: 'Access denied by api domain white list, Please check your security domain.', + }, + timeago: { + seconds: 'seconds ago', + minutes: 'minutes ago', + hours: 'hours ago', + days: 'days ago', + now: 'just now' + } + } +} -const store = localStorage; -class Valine { - /** - * Valine constructor function - * @param {Object} option - * @constructor - */ - constructor(option) { - let _root = this; - // version - _root.version = '1.1.8-beta'; +let _avatarSetting = { + cdn: 'https://gravatar.loli.net/avatar/', + ds: ['mp', 'identicon', 'monsterid', 'wavatar', 'robohash', 'retro', ''], + params: '', + hide: false + }, + META = ['nick', 'mail', 'link'], + _store = Storage && localStorage && localStorage instanceof Storage && localStorage, + _path = location.pathname.replace(/index\.html?$/, ''); - _root.md5 = md5; +function ValineFactory(option) { + let root = this // Valine init - !!option && _root.init(option); - } + !!option && root.init(option); + return root; +} - /** - * Valine Init - * @param {Object} option - */ - init(option) { - let _root = this; - try { - let el = ({}).toString.call(option.el) === "[object HTMLDivElement]" ? option.el : document.querySelectorAll(option.el)[0]; - if (({}).toString.call(el) != '[object HTMLDivElement]') { - throw `The target element was not found.`; +/** + * Valine Init + * @param {Object} option + */ +ValineFactory.prototype.init = function (option) { + if (typeof document === 'undefined') { + console && console.warn('Sorry, Valine does not support Server-side rendering.') + return; + } + let root = this; + try { + let { + lang, + langMode, + avatar, + avatarForce, + avatar_cdn, + notify, + verify, + visitor, + pageSize, + recordIP + } = option; + let ds = _avatarSetting['ds']; + let force = avatarForce ? '&q=' + Math.random().toString(32).substring(2) : ''; + + lang && langMode && root.installLocale(lang, langMode); + root.locale = root.locale || locales[lang || 'zh-cn']; + root.notify = notify || false; + root.verify = verify || false; + + if (recordIP) { + let ipScript = Utils.create('script', 'src', '//api.ip.sb/jsonip?callback=getIP'); + let s = document.getElementsByTagName("script")[0]; + s.parentNode.insertBefore(ipScript, s); + // 获取IP + window.getIP = function (json) { + defaultComment['ip'] = json.ip; } - _root.el = el; - _root.el.classList.add('valine'); - - const guest_info = option.guest_info || GUEST_INFO; - const inputEl = guest_info.map(item => { - switch (item) { - case 'nick': - return ''; - break; - case 'mail': - return ''; - break; - case 'link': - return ''; - break; - default: - return ''; - break; - } - }); + } - let placeholder = option.placeholder || ''; - let eleHTML = `

${inputEl.join('')}
MarkDown is Support
`; - _root.el.innerHTML = eleHTML; - - // Empty Data - let vempty = _root.el.querySelector('.vempty'); - _root.nodata = { - show(txt) { - vempty.innerHTML = txt || `还没有评论哦,快来抢沙发吧!`; - vempty.setAttribute('style', 'display:block;'); - }, - hide() { - vempty.setAttribute('style', 'display:none;'); + _avatarSetting['params'] = `?d=${(ds.indexOf(avatar) > -1 ? avatar : 'mp')}&v=${VERSION}${force}`; + _avatarSetting['hide'] = avatar === 'hide' ? true : false; + _avatarSetting['cdn'] = LINKREG.test(avatar_cdn) ? avatar_cdn : _avatarSetting['cdn'] + + _path = option.path || _path; + + let size = Number(pageSize || 10); + option.pageSize = !isNaN(size) ? (size < 1 ? 10 : size) : 10; + + marked.setOptions({ + renderer: new marked.Renderer(), + highlight: option.highlight === false ? null : hanabi, + gfm: true, + tables: true, + breaks: true, + pedantic: false, + sanitize: false, + smartLists: true, + smartypants: true + }); + + if (!AV) { + setTimeout(() => { + root.init(option) + }, 20) + return; + } + let id = option.app_id || option.appId; + let key = option.app_key || option.appKey; + if (!id || !key) throw 99; + AV.applicationId && delete AV._config.applicationId || (AV.applicationId = null); + AV.applicationKey && delete AV._config.applicationKey || (AV.applicationKey = null); + AV.init({ + appId: id, + appKey: key + }); + + // get comment count + let els = Utils.findAll(document, '.valine-comment-count'); + for (let i = 0, len = els.length; i < len; i++) { + let el = els[i]; + if (el) { + let k = Utils.attr(el, 'data-xid'); + if (k) { + root.Q(k).count().then(n => { + el.innerText = n + }).catch(ex => { + el.innerText = 0 + }) } } + } - // loading - let _spinner = `
`; - let vloading = _root.el.querySelector('.vloading'); - vloading.innerHTML = _spinner; - // loading control - _root.loading = { - show() { - vloading.setAttribute('style', 'display:block;'); - _root.nodata.hide(); - }, - hide() { - vloading.setAttribute('style', 'display:none;'); - _root.el.querySelectorAll('.vcard').length === 0 && _root.nodata.show(); - } - }; - //_root.nodata.show(); - - _root.notify = option.notify || !1; - _root.verify = option.verify || !1; - - gravatar['params'] = '?d=' + (gravatar['ds'].indexOf(option.avatar) > -1 ? option.avatar : 'mm'); - gravatar['hide'] = option.avatar === 'hide' ? !0 : !1; - - let av = option.av || AV; - let appId = option.app_id || option.appId; - let appKey = option.app_key || option.appKey; - if (!appId || !appKey) { - _root.loading.hide(); - throw '初始化失败,请检查你的appid或者appkey.'; - return; + // Counter + visitor && CounterFactory.add(AV.Object.extend('Counter')); + + let el = option.el || null; + let _el = Utils.findAll(document, el); + el = el instanceof HTMLElement ? el : (_el[_el.length - 1] || null); + if (!el) return; + root.el = el; + root.el.classList.add('v'); + + _avatarSetting['hide'] && root.el.classList.add('hide-avatar'); + option.meta = (option.guest_info || option.meta || META).filter(item => META.indexOf(item) > -1); + let inputEl = (option.meta.length == 0 ? META : option.meta).map(item => { + let _t = item == 'mail' ? 'email' : 'text'; + return META.indexOf(item) > -1 ? `` : '' + }); + root.placeholder = option.placeholder || 'Just Go Go'; + + root.el.innerHTML = `
${inputEl.join('')}
${root.locale['ctrl']['emoji']} | ${root.locale['ctrl']['preview']}
Powered By Valine
v${VERSION}
`; + + // Empty Data + let vempty = Utils.find(root.el, '.vempty'); + root.nodata = { + show(txt) { + vempty.innerHTML = txt || root.locale['tips']['sofa']; + Utils.attr(vempty, 'style', 'display:block;'); + return root; + }, + hide() { + Utils.attr(vempty, 'style', 'display:none;'); + return root; } - av.applicationId = null; - av.init({ - appId: appId, - appKey: appKey - }); - _root.v = av; - defaultComment.url = (option.path || location.pathname).replace(/index\.(html|htm)/, ''); - - } catch (ex) { - let issue = 'https://github.com/xCss/Valine/issues'; - if (_root.el) _root.nodata.show(`
${ex}
Valine:${_root.version}
反馈:${issue}
`); - else console && console.log(`%c${ex}\n%cValine%c${_root.version} ${issue}`, 'color:red;', 'background:#000;padding:5px;line-height:30px;color:#fff;', 'background:#456;line-height:30px;padding:5px;color:#fff;'); - return; } - let _mark = _root.el.querySelector('.vmark'); + // loading + let _spinner = Utils.create('div', 'class', 'vloading'); + // loading control + let _vlist = Utils.find(root.el, '.vlist'); + root.loading = { + show(mt) { + let _vlis = Utils.findAll(_vlist, '.vcard'); + if (mt) _vlist.insertBefore(_spinner, _vlis[0]); + else _vlist.appendChild(_spinner); + root.nodata.hide(); + return root; + }, + hide() { + let _loading = Utils.find(_vlist, '.vloading'); + if (_loading) Utils.remove(_loading); + Utils.findAll(_vlist, '.vcard').length === 0 && root.nodata.show() + return root; + } + }; // alert - _root.alert = { + let _mark = Utils.find(root.el, '.vmark'); + root.alert = { /** * { * type:0/1, @@ -151,541 +275,732 @@ class Valine { * @param {Object} o */ show(o) { - _mark.innerHTML = `
${o.text}
`; - let _vbtns = _mark.querySelector('.vbtns'); - let _cBtn = ``; - let _oBtn = ``; - _vbtns.innerHTML = `${_cBtn}${o.type && _oBtn}`; - _mark.querySelector('.vcancel').addEventListener('click', function (e) { - _root.alert.hide(); - }); - _mark.setAttribute('style', 'display:block;'); + _mark.innerHTML = `
${o && o.text || 1}
`; + let _vbtns = Utils.find(_mark, '.vbtns'); + let _cBtn = ``; + let _oBtn = ``; + _vbtns.innerHTML = `${_cBtn}${o && o.type && _oBtn}`; + Utils.on('click', Utils.find(_mark, '.vcancel'), (e) => { + root.alert.hide(); + }) + Utils.attr(_mark, 'style', 'display:block;'); if (o && o.type) { - let _ok = _mark.querySelector('.vsure'); - Event.on('click', _ok, (e) => { - _root.alert.hide(); + let _ok = Utils.find(_mark, '.vsure'); + Utils.on('click', _ok, (e) => { + root.alert.hide(); o.cb && o.cb(); }); } + return root; }, hide() { - _mark.setAttribute('style', 'display:none;'); + Utils.attr(_mark, 'style', 'display:none;'); + return root; } } // Bind Event - _root.bind(option); - } + root.bind(option); - /** - * Bind Event - */ - bind(option) { - let _root = this; - let guest_info = (option.guest_info || GUEST_INFO).filter(item => GUEST_INFO.indexOf(item) > -1); + } catch (ex) { + root.ErrorHandler(ex) + } + return root; +} - let expandEvt = (el) => { - if (el.offsetHeight > 180) { - el.classList.add('expand'); - Event.on('click', el, (e) => { - el.setAttribute('class', 'vcontent'); - }) +// 新建Counter对象 +let createCounter = function (Counter, o) { + let newCounter = new Counter(); + let acl = new AV.ACL(); + acl.setPublicReadAccess(true); + acl.setPublicWriteAccess(true); + newCounter.setACL(acl); + newCounter.set('url', o.url) + newCounter.set('xid', o.xid) + newCounter.set('title', o.title) + newCounter.set('time', 1) + newCounter.save().then(ret => { + Utils.find(o.el, '.leancloud-visitors-count').innerText = 1 + }).catch(ex => { + console.log(ex) + }); +} +let CounterFactory = { + add(Counter) { + let lvs = Utils.findAll(document, '.leancloud_visitors,.leancloud-visitors'); + if (lvs.length) { + let lv = lvs[0]; + let url = Utils.attr(lv, 'id'); + let title = Utils.attr(lv, 'data-flag-title'); + let xid = encodeURI(url); + let o = { + el: lv, + url: url, + xid: xid, + title: title } + // 判断是否需要+1 + if (decodeURI(url) === decodeURI(_path)) { + let query = new AV.Query(Counter); + query.equalTo('url', url); + query.find().then(ret => { + if (ret.length > 0) { + let v = ret[0]; + v.increment("time"); + v.save().then(rt => { + Utils.find(lv, '.leancloud-visitors-count').innerText = rt.get('time') + }).catch(ex => { + console.log(ex) + }); + } else { + createCounter(Counter, o) + } + }).catch(ex => { + ex.code == 101 && createCounter(Counter, o) + }) + } else CounterFactory.show(Counter, lvs) } - - let commonQuery = (cb) => { - let query = new _root.v.Query('Comment'); - query.equalTo('url', defaultComment['url']); - query.descending('createdAt'); - return query; + }, + show(Counter, lvs) { + let COUNT_CONTAINER_REF = '.leancloud-visitors-count'; + + // 重置所有计数 + Utils.each(lvs, function (idx, el) { + let cel = Utils.find(el, COUNT_CONTAINER_REF); + if (cel) cel.innerText = 0 + }) + let urls = []; + for (let i in lvs) { + if (lvs.hasOwnProperty(i)) urls.push(Utils.attr(lvs[i], 'id')) } - // let initPages = (cb) => { - // commonQuery().count().then(count => { - // if (count > 0) { - // let _vpage = _root.el.querySelector('.vpage'); - // _root.el.querySelector('.count').innerHTML = `评论(${count})`; - // } - // }).catch(ex => { - // console.log(ex); - // }) - // } - let query = (pageNo = 1) => { - _root.loading.show(); - let cq = commonQuery(); - cq.limit('1000'); - cq.find().then(rets => { - let len = rets.length; - if (len) { - _root.el.querySelector('.vlist').innerHTML = ''; - for (let i = 0; i < len; i++) { - insertDom(rets[i], !0) - } - _root.el.querySelector('.count').innerHTML = `评论(${len})`; + if (urls.length) { + let query = new AV.Query(Counter); + query.containedIn('url', urls); + query.find().then(ret => { + if (ret.length > 0) { + Utils.each(ret, function (idx, item) { + let url = item.get('url'); + let time = item.get('time'); + let el = Utils.find(document, `.leancloud_visitors[id="${url}"]`) || Utils.find(document, `.leancloud-visitors[id="${url}"]`); + let cel = Utils.find(el, COUNT_CONTAINER_REF); + if (cel) cel.innerText = time + }); } - _root.loading.hide(); }).catch(ex => { - //err(ex) - _root.loading.hide(); + console.error(ex) }) } - query(); - - let insertDom = (ret, mt) => { - - let _vcard = document.createElement('li'); - _vcard.setAttribute('class', 'vcard'); - _vcard.setAttribute('id', ret.id); - let _img = gravatar['hide'] ? '' : ``; - _vcard.innerHTML = `${_img}
${ret.get("comment")}
${timeAgo(ret.get("createdAt"))}回复
`; - let _vlist = _root.el.querySelector('.vlist'); - let _vlis = _vlist.querySelectorAll('li'); - let _vat = _vcard.querySelector('.vat'); - let _as = _vcard.querySelectorAll('a'); - for (let i = 0, len = _as.length; i < len; i++) { - let item = _as[i]; - if (item && item.getAttribute('class') != 'at') { - item.setAttribute('target', '_blank'); - item.setAttribute('rel', 'nofollow'); - } - } - if (mt) _vlist.appendChild(_vcard); - else _vlist.insertBefore(_vcard, _vlis[0]); - let _vcontent = _vcard.querySelector('.vcontent'); - expandEvt(_vcontent); - bindAtEvt(_vat); + } +} - } +/** + * LeanCloud SDK Query Util + * @param {String} url + * @param {String} id + */ +ValineFactory.prototype.Q = function (k) { + let len = arguments.length + if (len == 1) { + let notExist = new AV.Query('Comment'); + notExist.doesNotExist('rid'); + let isEmpty = new AV.Query('Comment'); + isEmpty.equalTo('rid', ''); + let q = AV.Query.or(notExist, isEmpty); + q.equalTo('url', decodeURI(k)); + q.addDescending('createdAt'); + q.addDescending('insertedAt'); + return q; + } else { + let ids = JSON.stringify(arguments[1]).replace(/(\[|\])/g, ''); + let cql = `select * from Comment where rid in (${ids}) order by -createdAt,-createdAt`; + return AV.Query.doCloudQuery(cql) + } +} - let mapping = { - veditor: "comment" - } - for (let i = 0, length = guest_info.length; i < length; i++) { - mapping[`v${guest_info[i]}`] = guest_info[i]; - } +ValineFactory.prototype.ErrorHandler = function (ex) { + // console.log(ex.code,ex.message) + let root = this; + root.el && root.loading.hide().nodata.hide() + if (({}).toString.call(ex) === "[object Error]") { + let code = ex.code || '', + t = root.locale['error'][code], + msg = t || ex.message || ex.error || ''; + if (code == 101) { + root.nodata.show() + } else root.el && root.nodata.show(`
Code ${code}: ${msg}
`) || + console && console.error(`Code ${code}: ${msg}`) + } else { + root.el && root.nodata.show(`
${JSON.stringify(ex)}
`) || + console && console.error(JSON.stringify(ex)) + } + return; +} + +/** + * install Multi language support + * @param {String} locale langName + * @param {Object} mode langSource + */ +ValineFactory.prototype.installLocale = function (locale, mode) { + let root = this; + mode = mode || {}; + if (locale) { + // locales[locale] = JSON.stringify(Object.keys(locales['zh-cn']))==JSON.stringify(Object.keys(mode)) ? mode : undefined; + locales[locale] = mode; + root.locale = locales[locale] || locales['zh-cn']; + } + return root; +} + +/** + * + * @param {String} path + */ +ValineFactory.prototype.setPath = function (path) { + _path = path || _path; + return this +} - let inputs = {}; - for (let i in mapping) { - if (mapping.hasOwnProperty(i)) { - let _v = mapping[i]; - let _el = _root.el.querySelector(`.${i}`); - inputs[_v] = _el; - Event.on('input', _el, (e) => { - defaultComment[_v] = _v === 'comment' ? marked(_el.value, { sanitize: !0 }) : HtmlUtil.encode(_el.value); +/** + * Bind Event + */ +ValineFactory.prototype.bind = function (option) { + let root = this; + + // load emojis + let _vemojis = Utils.find(root.el, '.vemojis'); + let _vpreview = Utils.find(root.el, '.vpreview'); + // emoji 操作 + let _emojiCtrl = Utils.find(root.el, '.vemoji-btn'); + // 评论内容预览 + let _vpreviewCtrl = Utils.find(root.el, `.vpreview-btn`); + let emojiData = Emoji.data; + for (let key in emojiData) { + if (emojiData.hasOwnProperty(key)) { + (function (name, val) { + let _i = Utils.create('i', { + 'name': name, + 'title': name + }); + _i.innerHTML = val; + _vemojis.appendChild(_i); + Utils.on('click', _i, (e) => { + let _veditor = Utils.find(root.el, '.veditor'); + _insertAtCaret(_veditor, val) + syncContentEvt(_veditor) }); + })(key, emojiData[key]) + } + } + root.emoji = { + show() { + root.preview.hide(); + Utils.attr(_emojiCtrl, 'v', 1); + Utils.removeAttr(_vpreviewCtrl, 'v'); + Utils.attr(_vemojis, 'style', 'display:block'); + return root.emoji + }, + hide() { + Utils.removeAttr(_emojiCtrl, 'v'); + Utils.attr(_vemojis, 'style', 'display:hide'); + return root.emoji + } + } + root.preview = { + show() { + if (defaultComment['comment']) { + root.emoji.hide(); + Utils.attr(_vpreviewCtrl, 'v', 1); + Utils.removeAttr(_emojiCtrl, 'v'); + _vpreview.innerHTML = defaultComment['comment']; + Utils.attr(_vpreview, 'style', 'display:block'); + _activeOtherFn() } + return root.preview + }, + hide() { + Utils.removeAttr(_vpreviewCtrl, 'v'); + Utils.attr(_vpreview, 'style', 'display:none'); + return root.preview + }, + empty() { + _vpreview.innerHtml = ''; + return root.preview } + } - // cache - let getCache = () => { - let s = store && store.ValineCache; - if (s) { - s = JSON.parse(s); - let m = guest_info; - for (let i in m) { - let k = m[i]; - _root.el.querySelector(`.v${k}`).value = s[k]; - defaultComment[k] = s[k]; - } - } + /** + * XSS filter + * @param {String} content Html String + */ + let xssFilter = (content) => { + let vNode = Utils.create('div'); + vNode.insertAdjacentHTML('afterbegin', content); + let ns = Utils.findAll(vNode, "*"); + let rejectNodes = ['INPUT', 'STYLE', 'SCRIPT', 'IFRAME', 'FRAME', 'AUDIO', 'VIDEO', 'EMBED', 'META', 'TITLE', 'LINK']; + let __replaceVal = (node, attr) => { + let val = Utils.attr(node, attr); + val && Utils.attr(node, attr, val.replace(/(javascript|eval)/ig, '')); } - getCache(); + Utils.each(ns, (idx, n) => { + if (n.nodeType !== 1) return; + if (rejectNodes.indexOf(n.nodeName) > -1) { + // console.log(n.nodeName) + if (n.nodeName === 'INPUT' && Utils.attr(n, 'type') === 'checkbox') Utils.attr(n, 'disabled', 'disabled'); + else Utils.remove(n); + } + if (n.nodeName === 'A') __replaceVal(n, 'href') + Utils.clearAttr(n) + }) + return vNode.innerHTML + } + /** + * 评论框内容变化事件 + * @param {HTMLElement} el + */ + let syncContentEvt = (_el) => { + let _v = 'comment'; + let _val = (_el.value || ''); + _val = Emoji.parse(_val); + _el.value = _val; + let ret = xssFilter(marked(_val)); + defaultComment[_v] = ret; + _vpreview.innerHTML = ret; + if (_val) autosize(_el); + else autosize.destroy(_el) + } - let atData = { - rmail: '', - at: '' + // 显示/隐藏 Emojis + Utils.on('click', _emojiCtrl, (e) => { + let _vi = Utils.attr(_emojiCtrl, 'v'); + if (_vi) { + root.emoji.hide() + } else { + root.emoji.show(); } + }); - // reset form - let reset = () => { - for (let i in mapping) { - if (mapping.hasOwnProperty(i)) { - let _v = mapping[i]; - let _el = _root.el.querySelector(`.${i}`); - _el.value = ""; - defaultComment[_v] = ""; - } - } - atData['at'] = ''; - atData['rmail'] = ''; - defaultComment['rid'] = ''; - defaultComment['nick'] = 'Guest'; - getCache(); + Utils.on('click', _vpreviewCtrl, function (e) { + let _vi = Utils.attr(_vpreviewCtrl, 'v'); + if (_vi) { + root.preview.hide(); + } else { + root.preview.show(); } + }); - // submit - let submitBtn = _root.el.querySelector('.vsubmit'); - let submitEvt = (e) => { - // console.log(defaultComment) - // return; - if (submitBtn.getAttribute('disabled')) { - _root.alert.show({ - type: 0, - text: '再等等,评论正在提交中ヾ(๑╹◡╹)ノ"', - ctxt: '好的' - }) - return; - } - if (defaultComment.comment == '') { - inputs['comment'].focus(); - return; - } - if (defaultComment.nick == '') { - defaultComment['nick'] = '小调皮'; - } - let idx = defaultComment.comment.indexOf(atData.at); - if (idx > -1 && atData.at != '') { - let at = `${atData.at}`; - defaultComment.comment = defaultComment.comment.replace(atData.at, at); - } - // veirfy - let mailRet = check.mail(defaultComment.mail); - let linkRet = check.link(defaultComment.link); - defaultComment['mail'] = mailRet.k ? mailRet.v : ''; - defaultComment['link'] = linkRet.k ? linkRet.v : ''; - if (!mailRet.k && !linkRet.k && guest_info.indexOf('mail') > -1 && guest_info.indexOf('link') > -1) { - _root.alert.show({ - type: 1, - text: '您的网址和邮箱格式不正确, 是否继续提交?', - cb() { - if (_root.notify || _root.verify) { - verifyEvt(commitEvt) - } else { - commitEvt(); - } - } - }) - } else if (!mailRet.k && guest_info.indexOf('mail') > -1) { - _root.alert.show({ - type: 1, - text: '您的邮箱格式不正确, 是否继续提交?', - cb() { - if (_root.notify || _root.verify) { - verifyEvt(commitEvt) - } else { - commitEvt(); - } - } - }) - } else if (!linkRet.k && guest_info.indexOf('link') > -1) { - _root.alert.show({ - type: 1, - text: '您的网址格式不正确, 是否继续提交?', - cb() { - if (_root.notify || _root.verify) { - verifyEvt(commitEvt) - } else { - commitEvt(); - } - } - }) - } else { - if (_root.notify || _root.verify) { - verifyEvt(commitEvt) + let meta = option.meta; + let inputs = {}; + + // 同步操作 + let mapping = { + veditor: "comment" + } + for (let i = 0, len = meta.length; i < len; i++) { + mapping[`v${meta[i]}`] = meta[i]; + } + for (let i in mapping) { + if (mapping.hasOwnProperty(i)) { + let _v = mapping[i]; + let _el = Utils.find(root.el, `.${i}`); + inputs[_v] = _el; + _el && Utils.on('input change blur', _el, (e) => { + if (_v === 'comment') { + syncContentEvt(_el) } else { - commitEvt(); + defaultComment[_v] = Utils.escape(_el.value.replace(/(^\s*)|(\s*$)/g, "")); } - } + }); } + } - // setting access - let getAcl = () => { - let acl = new _root.v.ACL(); - acl.setPublicReadAccess(!0); - acl.setPublicWriteAccess(!1); - return acl; + let _insertAtCaret = (field, val) => { + if (document.selection) { + //For browsers like Internet Explorer + field.focus(); + let sel = document.selection.createRange(); + sel.text = val; + field.focus(); + } else if (field.selectionStart || field.selectionStart == '0') { + //For browsers like Firefox and Webkit based + let startPos = field.selectionStart; + let endPos = field.selectionEnd; + let scrollTop = field.scrollTop; + field.value = field.value.substring(0, startPos) + val + field.value.substring(endPos, field.value.length); + field.focus(); + field.selectionStart = startPos + val.length; + field.selectionEnd = startPos + val.length; + field.scrollTop = scrollTop; + } else { + field.focus(); + field.value += val; } - - let commitEvt = () => { - submitBtn.setAttribute('disabled', !0); - _root.loading.show(); - // 声明类型 - let Ct = _root.v.Object.extend('Comment'); - // 新建对象 - let comment = new Ct(); - for (let i in defaultComment) { - if (defaultComment.hasOwnProperty(i)) { - let _v = defaultComment[i]; - comment.set(i, _v); - } - } - comment.setACL(getAcl()); - comment.save().then((ret) => { - defaultComment['nick'] != 'Guest' && store && store.setItem('ValineCache', JSON.stringify({ - nick: defaultComment['nick'], - link: defaultComment['link'], - mail: defaultComment['mail'] - })); - let _count = _root.el.querySelector('.num'); - let num = 1; - try { - - if (_count) { - num = Number(_count.innerText) + 1; - _count.innerText = num; - } else { - _root.el.querySelector('.count').innerHTML = '评论(1)' - } - insertDom(ret); - - defaultComment['mail'] && signUp({ - username: defaultComment['nick'], - mail: defaultComment['mail'] - }); - - atData['at'] && atData['rmail'] && _root.notify && mailEvt({ - username: atData['at'].replace('@', ''), - mail: atData['rmail'] - }); - submitBtn.removeAttribute('disabled'); - _root.loading.hide(); - reset(); - } catch (error) { - console.log(error) - } - }).catch(ex => { - _root.loading.hide(); - }) + } + let createVquote = id => { + let vcontent = Utils.find(root.el, ".vh[rootid='" + id + "']"); + let vquote = Utils.find(vcontent, '.vquote'); + if (!vquote) { + vquote = Utils.create('div', 'class', 'vquote'); + vcontent.appendChild(vquote); } + return vquote + } - let verifyEvt = (fn) => { - let x = Math.floor((Math.random() * 10) + 1); - let y = Math.floor((Math.random() * 10) + 1); - let z = Math.floor((Math.random() * 10) + 1); - let opt = ['+', '-', 'x']; - let o1 = opt[Math.floor(Math.random() * 3)]; - let o2 = opt[Math.floor(Math.random() * 3)]; - let expre = `${x}${o1}${y}${o2}${z}`; - let subject = `${expre} = `; - _root.alert.show({ - type: 1, - text: subject, - ctxt: '取消', - otxt: '确认', - cb() { - let code = +_root.el.querySelector('.vcode').value; - let ret = (new Function(`return ${expre.replace(/x/g, '*')}`))(); - if (ret === code) { - fn && fn(); - } else { - _root.alert.show({ - type: 1, - text: '(T_T)这么简单都算错,也是没谁了', - ctxt: '伤心了,不回了', - otxt: '再试试?', - cb() { - verifyEvt(fn); - return; - } - }) - } + let query = (no = 1) => { + let size = option.pageSize; + let count = Number(Utils.find(root.el, '.vnum').innerText); + root.loading.show(); + let cq = root.Q(_path); + cq.limit(size); + cq.skip((no - 1) * size); + cq.find().then(rets => { + let len = rets.length; + let rids = [] + for (let i = 0; i < len; i++) { + let ret = rets[i]; + rids.push(ret.id) + insertDom(ret, Utils.find(root.el, '.vlist'), !0) + } + // load children comment + root.Q(_path, rids).then(ret => { + let childs = ret && ret.results || [] + for (let k = 0; k < childs.length; k++) { + let child = childs[k]; + insertDom(child, createVquote(child.get('rid'))) } }) - } + let _vpage = Utils.find(root.el, '.vpage'); + _vpage.innerHTML = size * no < count ? `` : ''; + let _vmore = Utils.find(_vpage, '.vmore'); + if (_vmore) { + Utils.on('click', _vmore, (e) => { + _vpage.innerHTML = ''; + query(++no); + }) + } + root.loading.hide(); + }).catch(ex => { + root.loading.hide().ErrorHandler(ex) + }) + } - let signUp = (o) => { - let u = new _root.v.User(); - u.setUsername(o.username); - u.setPassword(o.mail); - u.setEmail(o.mail); - u.setACL(getAcl()); - return u.signUp(); + root.Q(_path).count().then(num => { + if (num > 0) { + Utils.attr(Utils.find(root.el, '.vinfo'), 'style', 'display:block;'); + Utils.find(root.el, '.vcount').innerHTML = `${num} ${root.locale['tips']['comments']}`; + query(); + } else { + root.loading.hide(); } - - let mailEvt = (o) => { - _root.v.User.requestPasswordReset(o.mail).then(ret => { }).catch(e => { - if (e.code == 1) { - _root.alert.show({ - type: 0, - text: `ヾ(o・ω・)ノ At太频繁啦,提醒功能暂时宕机。
${e.error}`, - ctxt: '好的' - }) - } else { - signUp(o).then(ret => { - mailEvt(o); - }).catch(x => { - //err(x) - }) - } - }) + }).catch(ex => { + root.ErrorHandler(ex) + }); + + let insertDom = (rt, node, mt) => { + + let _vcard = Utils.create('div', { + 'class': 'vcard', + 'id': rt.id + }); + let _img = _avatarSetting['hide'] ? '' : ``; + let ua = rt.get('ua') || ''; + let uaMeta = ''; + if (ua) { + ua = detect(ua); + let browser = `${ua.browser} ${ua.version}`; + let os = `${ua.os} ${ua.osVersion}`; + uaMeta = `${browser} ${os}`; } - - // at event - let bindAtEvt = (el) => { - Event.on('click', el, (e) => { - let at = el.getAttribute('at'); - let rid = el.getAttribute('rid'); - let rmail = el.getAttribute('mail'); - atData['at'] = at; - atData['rmail'] = rmail; - defaultComment['rid'] = rid; - inputs['comment'].value = `${at} ,`; - inputs['comment'].focus(); - }) + let _nick = ''; + let _t = rt.get('link') || ''; + _nick = _t ? `${rt.get("nick")}` : `${rt.get('nick')}`; + _vcard.innerHTML = `${_img} +
+
${_nick} ${uaMeta}
+
+ ${timeAgo(rt.get('insertedAt') || rt.createdAt,root.locale)} + ${root.locale['ctrl']['reply']} +
+
+ ${xssFilter(rt.get("comment"))} +
+
`; + let _vat = Utils.find(_vcard, '.vat'); + let _as = Utils.findAll(_vcard, 'a'); + for (let i = 0, len = _as.length; i < len; i++) { + let _a = _as[i]; + if (_a && (Utils.attr(_a, 'class') || '').indexOf('at') == -1) { + Utils.attr(_a, { + 'target': '_blank', + 'rel': 'nofollow' + }); + } } + let _vlis = Utils.findAll(node, '.vcard'); + if (mt) node.appendChild(_vcard); + else node.insertBefore(_vcard, _vlis[0]); + let _vcontent = Utils.find(_vcard, '.vcontent'); + if (_vcontent) expandEvt(_vcontent); + if (_vat) bindAtEvt(_vat, rt); + _activeOtherFn() + } - Event.off('click', submitBtn, submitEvt); - Event.on('click', submitBtn, submitEvt); + let _activeOtherFn = () => { + setTimeout(function () { + try { + let MathJax = MathJax || ''; + MathJax && MathJax.Hub.Queue(["Typeset", MathJax.Hub]); + $('pre code').each(function (i, block) { + hljs.highlightBlock(block); + }) + $('code.hljs').each(function (i, block) { + hljs.lineNumbersBlock(block); + }); + } catch (error) { + } + }, 20) } -} -// const loadAV = (cb) => { -// let avjs = document.createElement('script');    -// let _doc = document.querySelector('head');  -// avjs.type = 'text/javascript';     -// avjs.async = 'async';     -// avjs.src = '//cdn1.lncld.net/static/js/3.0.4/av-min.js';     -// _doc.appendChild(avjs);     -// if (avjs.readyState) { //IE       -// avjs.onreadystatechange = function() {         -// if (avjs.readyState == 'complete' || avjs.readyState == 'loaded') {           -// avjs.onreadystatechange = null;           -// cb && cb();         -// }       -// }     -// } else { //非IE       -// avjs.onload = function() { cb && cb(); }     -// } -// } - -const Event = { - on(type, el, handler, capture) { - if (el.addEventListener) el.addEventListener(type, handler, capture || false); - else if (el.attachEvent) el.attachEvent(`on${type}`, handler); - else el[`on${type}`] = handler; - }, - off(type, el, handler, capture) { - if (el.removeEventListener) el.removeEventListener(type, handler, capture || false); - else if (el.detachEvent) el.detachEvent(`on${type}`, handler); - else el[`on${type}`] = null; - }, - // getEvent(e) { - // return e || window.event; - // }, - // getTarget(e) { - // return e.target || e.srcElement; - // }, - // preventDefault(e) { - // e = e || window.event; - // e.preventDefault && e.preventDefault() || (e.returnValue = false); - // }, - // stopPropagation(e) { - // e = e || window.event; - // e.stopPropagation && e.stopPropagation() || (e.cancelBubble = !0); - // } -} + let _activeHLJS = () => {} + // expand event + let expandEvt = (el) => { + setTimeout(function () { + if (el.offsetHeight > 180) { + el.classList.add('expand'); + Utils.on('click', el, (e) => { + Utils.attr(el, 'class', 'vcontent'); + }) + } + }) + } + let atData = {} + // at event + let bindAtEvt = (el, rt) => { + Utils.on('click', el, (e) => { + let at = `@${Utils.escape(rt.get('nick'))}`; + atData = { + 'at': Utils.escape(at) + ' ', + 'rid': rt.get('rid') || rt.id, + 'pid': rt.id, + 'rmail': rt.get('mail'), + } + // console.log(atData) + Utils.attr(inputs['comment'], 'placeholder', at); + inputs['comment'].focus(); + }) + } + // cache + let getCache = () => { + let s = _store && _store.ValineCache; + if (s) { + s = JSON.parse(s); + let m = meta; + for (let i in m) { + let k = m[i]; + Utils.find(root.el, `.v${k}`).value = Utils.unescape(s[k]); + defaultComment[k] = s[k]; + } + } + } + getCache(); + // reset form + let reset = () => { + defaultComment['comment'] = ""; + inputs['comment'].value = ""; + syncContentEvt(inputs['comment']) + Utils.attr(inputs['comment'], 'placeholder', root.placeholder); + atData = {}; + root.preview.empty().hide(); + } -const getLink = (target) => { - return target.link || (target.mail && `mailto:${target.mail}`) || 'javascript:void(0);'; -} + // submitsubmit + let submitBtn = Utils.find(root.el, '.vsubmit'); + let submitEvt = (e) => { + if (Utils.attr(submitBtn, 'disabled')) { + root.alert.show({ + type: 0, + text: `${root.locale['tips']['busy']}ヾ(๑╹◡╹)ノ"`, + ctxt: root.locale['ctrl']['ok'] + }) + return; + } + if (defaultComment['comment'] == '') { + inputs['comment'].focus(); + return; + } + defaultComment['nick'] = defaultComment['nick'] || 'Anonymous'; -const check = { - mail(m) { - return { - k: /[\w-\.]+@([\w-]+\.)+[a-z]{2,3}/.test(m), - v: m - }; - }, - link(l) { - l = l.length > 0 && (/^(http|https)/.test(l) ? l : `http://${l}`); - return { - k: /(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/.test(l), - v: l - }; + // return; + if (root.notify || root.verify) { + verifyEvt(commitEvt) + } else { + commitEvt(); + } } -} -const HtmlUtil = { - - // /** - // * - // * 将str中的链接转换成a标签形式 - // * @param {String} str - // * @returns - // */ - // transUrl(str) { - // let reg = /(http:\/\/|https:\/\/)((\w|=|\?|\.|\/|&|-)+)/g; - // return str.replace(reg, '$1$2'); - // }, - /** - * HTML转码 - * @param {String} str - * @return {String} result - */ - encode(str) { - return !!str ? str.replace(/&/g, "&").replace(//g, ">").replace(/ /g, " ").replace(/\'/g, "'").replace(/\"/g, """) : ''; - }, - /** - * HTML解码 - * @param {String} str - * @return {String} result - */ - decode(str) { - return !!str ? str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/ /g, " ").replace(/'/g, "\'").replace(/"/g, "\"") : ''; + // setting access + let getAcl = () => { + let acl = new AV.ACL(); + acl.setPublicReadAccess(!0); + acl.setPublicWriteAccess(false); + return acl; } -}; -const dateFormat = (date) => { - var vDay = padWithZeros(date.getDate(), 2); - var vMonth = padWithZeros(date.getMonth() + 1, 2); - var vYear = 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}`; -} + let commitEvt = () => { + Utils.attr(submitBtn, 'disabled', !0); + root.loading.show(!0); + // 声明类型 + let Ct = AV.Object.extend('Comment'); + // 新建对象 + let comment = new Ct(); + defaultComment['url'] = decodeURI(_path); + defaultComment['insertedAt'] = new Date(); + if (atData['rid']) { + let pid = atData['pid'] || atData['rid']; + comment.set('rid', atData['rid']); + comment.set('pid', pid); + defaultComment['comment'] = defaultComment['comment'].replace('

', `

${atData['at']} , `); + } + for (let i in defaultComment) { + if (defaultComment.hasOwnProperty(i)) { + let _v = defaultComment[i]; + comment.set(i, _v); + } + } + comment.setACL(getAcl()); + comment.save().then(ret => { + defaultComment['nick'] != 'Anonymous' && _store && _store.setItem('ValineCache', JSON.stringify({ + nick: defaultComment['nick'], + link: defaultComment['link'], + mail: defaultComment['mail'] + })); + let _count = Utils.find(root.el, '.vnum'); + let num = 1; + try { + if (atData['rid']) { + let vquote = Utils.find(root.el, '.vquote[rid="' + atData['rid'] + '"]') || createVquote(atData['rid']); + insertDom(ret, vquote, !0) + } else { + if (_count) { + num = Number(_count.innerText) + 1; + _count.innerText = num; + } else { + Utils.find(root.el, '.vcount').innerHTML = '1 ' + root.locale['tips']['comments'] + } + insertDom(ret, Utils.find(root.el, '.vlist')); + } -const timeAgo = (date) => { - try { - var oldTime = date.getTime(); - var currTime = new Date().getTime(); - var diffValue = currTime - oldTime; - - var days = Math.floor(diffValue / (24 * 3600 * 1000)); - if (days === 0) { - //计算相差小时数 - var leave1 = diffValue % (24 * 3600 * 1000); //计算天数后剩余的毫秒数 - var hours = Math.floor(leave1 / (3600 * 1000)); - if (hours === 0) { - //计算相差分钟数 - var leave2 = leave1 % (3600 * 1000); //计算小时数后剩余的毫秒数 - var minutes = Math.floor(leave2 / (60 * 1000)); - if (minutes === 0) { - //计算相差秒数 - var leave3 = leave2 % (60 * 1000); //计算分钟数后剩余的毫秒数 - var seconds = Math.round(leave3 / 1000); - return seconds + ' 秒前'; + defaultComment['mail'] && signUp({ + username: defaultComment['nick'], + mail: defaultComment['mail'] + }); + + atData['at'] && atData['rmail'] && root.notify && mailEvt({ + username: atData['at'].replace('@', ''), + mail: atData['rmail'] + }); + Utils.removeAttr(submitBtn, 'disabled'); + root.loading.hide(); + reset(); + } catch (ex) { + root.ErrorHandler(ex); + } + }).catch(ex => { + root.ErrorHandler(ex); + }) + } + + let verifyEvt = (fn) => { + let x = Math.floor((Math.random() * 10) + 1); + let y = Math.floor((Math.random() * 10) + 1); + let z = Math.floor((Math.random() * 10) + 1); + let opt = ['+', '-', 'x']; + let o1 = opt[Math.floor(Math.random() * 3)]; + let o2 = opt[Math.floor(Math.random() * 3)]; + let expre = `${x}${o1}${y}${o2}${z}`; + let subject = `${expre} = `; + root.alert.show({ + type: 1, + text: subject, + ctxt: root.locale['ctrl']['cancel'], + otxt: root.locale['ctrl']['ok'], + cb() { + let code = +Utils.find(root.el, '.vcode').value; + let ret = (new Function(`return ${expre.replace(/x/g, '*')}`))(); + if (ret === code) { + fn && fn(); + } else { + root.alert.show({ + type: 1, + text: `(T_T)${root.locale['tips']['again']}`, + ctxt: root.locale['ctrl']['cancel'], + otxt: root.locale['ctrl']['try'], + cb() { + verifyEvt(fn); + return; + } + }) } - return minutes + ' 分钟前'; } - return hours + ' 小时前'; - } - if (days < 0) return '刚刚'; + }) + } - if (days < 8) { - return days + ' 天前'; - } else { - return dateFormat(date) - } - } catch (error) { - console.log(error) + let signUp = (o) => { + let u = new AV.User(); + u.setUsername(o.username); + u.setPassword(o.mail); + u.setEmail(o.mail); + u.setACL(getAcl()); + return u.signUp(); } + let mailEvt = (o) => { + AV.User.requestPasswordReset(o.mail).then(ret => {}).catch(e => { + if (e.code == 1) { + root.alert.show({ + type: 0, + text: `ヾ(o・ω・)ノ At太频繁啦,提醒功能暂时宕机。
${e.error}`, + ctxt: root.locale['ctrl']['ok'] + }) + } else { + signUp(o).then(ret => { + mailEvt(o); + }).catch(x => { + //err(x) + }) + } + }) + } + Utils.on('click', submitBtn, submitEvt); + Utils.on('keydown', document, function (e) { + e = event || e; + let keyCode = e.keyCode || e.which || e.charCode; + let ctrlKey = e.ctrlKey || e.metaKey; + // Shortcut key + ctrlKey && keyCode === 13 && submitEvt() + // tab key + if (keyCode === 9) { + let focus = document.activeElement.id || '' + if (focus == 'veditor') { + e.preventDefault(); + let _veditor = Utils.find(root.el, '.veditor'); + _insertAtCaret(_veditor, ' '); + } + } + }) } -const padWithZeros = (vNumber, width) => { - var numAsString = vNumber.toString(); - while (numAsString.length < width) { - numAsString = '0' + numAsString; - } - return numAsString; +function Valine(options) { + return new ValineFactory(options) } module.exports = Valine; +module.exports.default = Valine; \ No newline at end of file diff --git a/src/index.scss b/src/index.scss index 4088f27e..22256db9 100644 --- a/src/index.scss +++ b/src/index.scss @@ -4,22 +4,87 @@ * Github: https://github.com/xCss/Valine * Website: https://valine.js.org */ -.valine { + +.v { * { - box-sizing: border-box; - //font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", 微软雅黑, "helvetica neue", helvetica, ubuntu, roboto, noto, "segoe ui", Arial, sans-serif; + box-sizing: border-box; //font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", 微软雅黑, "helvetica neue", helvetica, ubuntu, roboto, noto, "segoe ui", Arial, sans-serif; //font-size: 16px; - line-height: 1.42857143; + line-height:2; color: #555; transition: all .3s ease; } + + hr{ + margin: .825rem 0; + border-color:#f6f6f6; + border-style: dashed; + } + &.hide-avatar{ + .vimg{display:none;} + } + a{ + position:relative; + cursor: pointer; + color: #1abc9c; + text-decoration:none; + display:inline-block; + &::before{ + content:''; + position:absolute; + width:0; + right:0; + bottom:0; + height:1px; + background:#1abc9c; + transition: width .3s ease; + } + &:hover { + color: #D7191A; + &::before{ + width:100%; + left:0; + right:auto; + } + } + } + pre,code{ + background-color: #f6f6f6; + color: #555; + padding: 0.2em 0.4em; + border-radius: 3px; + font-size: 85%; + 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; + } + pre{ + padding: 10px; + overflow: auto; + line-height: 1.45; + code{ + padding:0; + background:transparent; + white-space: pre-wrap; + word-break: keep-all; + } + } + blockquote{ + color: #666; + margin: .5rem 0; + padding: 0 0 0 1rem ; + border-left: 8px solid rgba(238, 238, 238, 0.5); + } .vinput { border: none; resize: none; outline: none; - padding: 10px 0; + padding: 10px 5px; max-width: 100%; - font-size:.775rem; + font-size: .775rem; + } + input[type='checkbox'],input[type='radio']{ + display:inline-block; + vertical-align: middle; + margin-top:-2px; } .vwrap { border: 1px solid #f0f0f0; @@ -27,58 +92,105 @@ margin-bottom: 10px; overflow: hidden; position: relative; - padding:10px; + padding: 10px; input { background: transparent; } - .vheader{ - .vinput{ - width:33.33%; - border-bottom:1px #dedede dashed; + .vedit { + position: relative; + padding-top:10px; + } + .vedit .vctrl { + text-align: right; + font-size: 12px; + } + .vedit .vctrl span { + padding: 10px; + display: inline-block; + vertical-align: middle; + cursor: pointer; + } + .vedit .vemojis { + display: none; + font-size: 18px; + text-align: justify; + max-height: 145px; + overflow: auto; + margin-bottom: 10px; + box-shadow: 0px 0 1px #f0f0f0; + } + .vedit .vemojis i { + font-style: normal; + padding: 7px 0; + width: 38px; + cursor: pointer; + text-align: center; + display: inline-block; + vertical-align: middle; + } + .vedit .vpreview { + padding: 7px; + box-shadow: 0px 0 1px #f0f0f0; + img,frame,iframe{ + max-width: 100%; + border:none; + } + } + .vheader { + .vinput { + width: 33.33%; + border-bottom: 1px #dedede dashed; } &.item2 .vinput { - width: 50%; + width: 50%; } &.item1 .vinput { - width: 100%; + width: 100%; } - .vinput:focus{ - border-bottom-color:#eb5055; + .vinput:focus { + border-bottom-color: #eb5055; } @media screen and (max-width:520px) { .vinput { width: 100%; } &.item2 .vinput { - width: 100%; + width: 100%; } } } .vcontrol { //background: #fdfdfd; font-size: 0; - .col{ - display:inline-block; - font-size:.725rem; + padding-top: 15px; + .col { + display: inline-block; + font-size: .725rem; vertical-align: middle; - color:#ccc; - &.text-right{ - text-align:right; + color: #ccc; + &.text-right { + text-align: right; } - svg{ + svg { margin-right: 2px; overflow: hidden; fill: currentColor; vertical-align: middle; } - &.col-40{ - width:40%; + &.col-20 { + width: 20%; + } + &.col-40 { + width: 40%; } - &.col-60{ - width:60%; + &.col-60 { + width: 60%; } - &.split{ - width:50%; + &.col-80 { + width: 80%; + } + &.split { + width: 50%; } } } @@ -98,7 +210,7 @@ .vcode { width: 4.6875rem; border-radius: .3125rem; - padding:.5rem; + padding: .5rem; background: #dedede; &:focus { border-color: #3090e4; @@ -120,11 +232,12 @@ .power { color: #999; font-size: .75rem; + padding:.5rem 0; a { font-size: .75rem; } } - .info { + .vinfo { font-size: 0; padding: 5px; .col { @@ -132,10 +245,9 @@ display: inline-block; width: 50%; vertical-align: middle; - } - .count { - .num { + .vcount { + .vnum { font-weight: 600; font-size: 1.25rem; } @@ -144,15 +256,14 @@ a { text-decoration: none; color: #555; + // outline: none; &:hover { color: #222; } } - ul, - li { - list-style: none; - margin: 0 auto; + ul,ol { padding: 0; + margin-left: 1.25rem; } .txt-center { text-align: center; @@ -168,17 +279,18 @@ } .veditor { width: 100%; - height: 8.75rem; - font-size:.875rem; - background:transparent; + min-height: 8.75rem; + font-size: .875rem; + background: transparent; + resize: vertical; + transition:all .25s ease; } .vbtn { transition-duration: .4s; text-align: center; color: #313131; - border: 1px solid #f7f7f7; - border-radius: 1.9rem; - background-color: #f7f7f7; + border: 1px solid #ededed; + border-radius: .3rem; display: inline-block; background: #ededed; margin-bottom: 0; @@ -203,60 +315,109 @@ background-color: #fff; } .vempty { - padding: 1.25px; + padding: 1.25rem; text-align: center; color: #999; } .vlist { - width:100%; + width: 100%; .vcard { padding-top: 1.5rem; position: relative; - display:block; - &:after{ - content:''; - clear:both; - display:block; + display: block; + &:after { + content: ''; + clear: both; + display: block; } .vimg { - width:2.5rem; - height:2.5rem; + width: 3.125rem; + height: 3.125rem; float: left; border-radius: 50%; margin-right: .7525rem; + border:1px solid #f5f5f5; + padding:.125rem; + } + + @media screen and (max-width:720px) { + .vimg { + width: 2.5rem; + height: 2.5rem; + } } .vhead { - line-height: 1; - margin-bottom: .625rem; + line-height: 1.5; margin-top: 0; - a { - font-size: .8135rem; - font-weight: 700; - color: rgb(44, 32, 32); - margin-right:.875rem; + .vnick { + position:relative; + font-size: .875rem; + font-weight: 500; + margin-right: .875rem; + cursor: pointer; + color: #1abc9c; + text-decoration:none; + display:inline-block; + &::before{ + content:''; + position:absolute; + width:0; + right:0; + bottom:0; + height:1px; + background:#1abc9c; + transition: width .3s ease; + } &:hover { color: #D7191A; + &::before{ + width:100%; + left:0; + right:auto; + } + } + } + .vsys { + display: inline-block; + padding: .2rem .5rem; + background: #ededed; + color: #b3b1b1; + font-size: .75rem; + border-radius: .2rem; + margin-right: .3rem; + } + @media screen and (max-width:520px) { + .vsys { + display: none; } } } - section { + + .vh { overflow: hidden; - padding-bottom:1.5rem; - border-bottom:1px solid #f5f5f5; - .vfooter{ - position:relative; - .vtime { - color: #b3b3b3; - font-size: .75rem; - margin-right:.875rem; - } + padding-bottom: .5rem; + border-bottom: 1px dashed #f5f5f5; + .vtime { + color: #b3b3b3; + font-size: .75rem; + margin-right: .875rem; + } + .vmeta { + line-height:1; + position: relative; .vat { font-size: .8125rem; color: #ef2f11; cursor: pointer; + float:right; } } } + &:last-child{ + .vh{ + border-bottom:none; + } + } .vcontent { word-wrap: break-word; word-break: break-all; @@ -265,26 +426,11 @@ font-size: .875rem; line-height: 2; position: relative; - margin-bottom:.75rem; - a { - font-size: .875rem; - color:#708090; - text-decoration: double; - &:hover{ - color:#D7191A; - } - } - pre, - .code, - code { - overflow: auto; - padding: 2px 6px; - word-wrap: break-word; - color: #555; - background: #f5f2f2; - border-radius: 3px; - font-size: .875rem; - margin: 5px 0; + margin-bottom: .75rem; + padding-top: .625rem; + img,iframe,frame { + max-width: 100%; + border:none; } &.expand { cursor: pointer; @@ -317,25 +463,23 @@ } } } + + .vquote { + color: #666; + margin-top: 1rem; + padding-left: 1rem; + border-left: 1px dashed rgba(238, 238, 238, 0.5); + .vimg{ + width: 2.225rem; + height: 2.225rem; + } + } + } } .vpage { - padding: .35rem; - i { - display: inline-block; - padding: .05rem .65rem; - font-size: .785rem; - border: 1px solid #f0f0f0; - font-style: normal; - cursor: pointer; - &+i { - margin-left: .35rem; - } - &.active { - border: none; - color: #ccc; - cursor: default; - } + .vmore{ + margin:1rem 0; } } .clear { @@ -344,58 +488,44 @@ clear: both; } /************ Loading ************/ - .spinner { - margin: 0.625rem auto; - width: 3.15rem; - height: 1.875rem; - text-align: center; - font-size: 10px; - } - .spinner>div { - background-color: #9c9c9c; - height: 100%; - width: .375rem; - margin-right: 0.19rem; - display: inline-block; - -webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out; - animation: sk-stretchdelay 1.2s infinite ease-in-out; - } - .spinner .r2 { - -webkit-animation-delay: -1.1s; - animation-delay: -1.1s; - } - .spinner .r3 { - -webkit-animation-delay: -1.0s; - animation-delay: -1.0s; - } - .spinner .r4 { - -webkit-animation-delay: -0.9s; - animation-delay: -0.9s; - } - .spinner .r5 { - -webkit-animation-delay: -0.8s; - animation-delay: -0.8s; - } - @-webkit-keyframes sk-stretchdelay { - 0%, - 40%, - 100% { - -webkit-transform: scaleY(0.4) - } - 20% { - -webkit-transform: scaleY(1.0) + $base-line-height: 40px; + $white: rgb(220, 220, 220); + $off-white: rgb(160, 160, 160); + $spin-duration: 1s; + $pulse-duration: 750ms; + @keyframes spin { + 0% { + transform: rotate(0deg); } - } - @keyframes sk-stretchdelay { - 0%, - 40%, 100% { - transform: scaleY(0.4); - -webkit-transform: scaleY(0.4); + transform: rotate(360deg); } - 20% { - transform: scaleY(1.0); - -webkit-transform: scaleY(1.0); + } + @keyframes pulse { + 50% { + background: $white; } } -} + .vloading { + position: relative; + padding: 20px; + display: block; + height: 80px; + } + .vloading::before { + box-sizing: border-box; + content: ''; + position: absolute; + display: inline-block; + top: 20px; + left: 50%; + margin-left: -20px; + width: $base-line-height; + height: $base-line-height; + border: 6px double $off-white; + border-top-color: transparent; + border-bottom-color: transparent; + border-radius: 50%; + animation: spin $spin-duration infinite linear; + } +} \ No newline at end of file