├── docs ├── CNAME └── index.html ├── .npmignore ├── .gitignore ├── .travis.yml ├── renovate.json ├── package.json ├── LICENSE ├── gulpfile.js ├── dist ├── disqusjs.css └── disqus.js ├── src ├── disqusjs.css └── disqus.js └── README.md /docs/CNAME: -------------------------------------------------------------------------------- 1 | disqusjs.skk.moe -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/index.html 2 | 3 | dist/index.html 4 | 5 | docs/ 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | 4 | *.log 5 | 6 | node_modules 7 | 8 | .idea 9 | 10 | .directory 11 | 12 | _build 13 | 14 | public 15 | 16 | src/index.html 17 | 18 | dist/index.html 19 | 20 | package-lock.json 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "9" 4 | before_install: 5 | - yarn global add gulp-cli 6 | install: 7 | - yarn 8 | script: 9 | - gulp build 10 | cache: 11 | yarn: true 12 | directories: 13 | - node_modules -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "assignees": [ 3 | "SukkaW" 4 | ], 5 | "extends": [ 6 | "config:base" 7 | ], 8 | "vulnerabilityAlerts": { 9 | "labels": [ 10 | "security" 11 | ], 12 | "assignees": [ 13 | "SukkaW" 14 | ] 15 | }, 16 | "reviewers": [ 17 | "SukkaW" 18 | ], 19 | "commitMessageAction": "update", 20 | "semanticCommits": true, 21 | "semanticCommitType": "chore" 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "disqusjs", 3 | "version": "1.3.0", 4 | "description": "Alternative DISQUS - Render comments components from Disqus API", 5 | "main": "dist/disqus.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/SukkaW/DisqusJS.git" 12 | }, 13 | "keywords": [ 14 | "Disqus", 15 | "DisqusJS", 16 | "SukkaW", 17 | "API", 18 | "XHR", 19 | "JSON" 20 | ], 21 | "author": "Sukka", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/SukkaW/DisqusJS/issues" 25 | }, 26 | "homepage": "https://github.com/SukkaW/DisqusJS#readme", 27 | "dependencies": {}, 28 | "devDependencies": { 29 | "@babel/core": "^7.10.1", 30 | "@babel/preset-env": "^7.10.1", 31 | "gulp": "^4.0.2", 32 | "gulp-autoprefixer": "^7.0.1", 33 | "gulp-babel": "^8.0.0", 34 | "gulp-clean-css": "^4.3.0", 35 | "gulp-header": "^2.0.9", 36 | "gulp-uglify": "^3.0.2" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sukka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const uglify = require('gulp-uglify'); 3 | const babel = require('gulp-babel'); 4 | const autoprefixer = require('gulp-autoprefixer'); 5 | const cleanCSS = require('gulp-clean-css'); 6 | const header = require('gulp-header'); 7 | const pkg = require('./package.json'); 8 | 9 | 10 | const jsBanner = ['/*! DisqusJS v<%= pkg.version %>', 11 | 'Sukka (https://skk.moe)', 12 | 'https://disqusjs.skk.moe', 13 | '<%= pkg.license %> License */', 14 | ].join(' | '); 15 | 16 | const cssBanner = ['/*! DisqusJS - Default Theme | v<%= pkg.version %>', 17 | 'Sukka (https://skk.moe)', 18 | 'https://disqusjs.skk.moe', 19 | '<%= pkg.license %> License */' 20 | ].join(' | '); 21 | 22 | 23 | const browserslist = [ 24 | 'last 3 versions', 25 | 'since 2016', 26 | '> 1%', 27 | 'Chrome >= 49', 28 | 'Firefox >= 50', 29 | 'ie >= 11', 30 | 'Safari >= 9', 31 | ] 32 | 33 | const configs = { 34 | autoprefixer: { 35 | overrideBrowserslist: browserslist 36 | }, 37 | cleanCSS: { 38 | compatibility: 'ie11' 39 | }, 40 | }; 41 | 42 | gulp.task('minify-js', () => gulp.src('src/**/*.js') 43 | .pipe(babel({ 44 | 'presets': [ 45 | ['@babel/env', { 46 | 'targets': browserslist, 47 | 'loose': true 48 | }] 49 | ] 50 | })) 51 | .pipe(uglify({ 52 | keep_fnames: false 53 | })) 54 | .pipe(header(jsBanner, { pkg })) 55 | .pipe(gulp.dest('dist'))); 56 | 57 | gulp.task('minify-css', () => gulp.src('src/**/*.css') 58 | .pipe(autoprefixer(configs.autoprefixer)) 59 | .pipe(cleanCSS(configs.cleanCSS)) 60 | .pipe(header(cssBanner, { pkg })) 61 | .pipe(gulp.dest('dist'))); 62 | 63 | gulp.task('build', gulp.parallel('minify-js', 'minify-css')); 64 | 65 | gulp.task('default', gulp.parallel('build')); -------------------------------------------------------------------------------- /dist/disqusjs.css: -------------------------------------------------------------------------------- 1 | /*! DisqusJS - Default Theme | v1.3.0 | Sukka (https://skk.moe) | https://disqusjs.skk.moe | MIT License */#dsqjs *{margin:0;padding:0}#dsqjs a{text-decoration:none;color:#076dd0}#dsqjs .dsqjs-hide{display:none!important}#dsqjs .dsqjs-disabled{cursor:not-allowed;opacity:.5}#dsqjs #dsqjs-msg{text-align:center;margin-top:4px;margin-bottom:4px;font-size:14px}#dsqjs #dsqjs-msg .dsqjs-msg-btn{cursor:pointer}#dsqjs .dsqjs-bullet{line-height:1.4;margin:0 2px}#dsqjs .dsqjs-bullet::after{color:#c2c6cc;content:"·";font-weight:700}#dsqjs .dsqjs-clearfix:after,#dsqjs .dsqjs-clearfix:before{display:table;content:"";line-height:0;clear:both}#dsqjs .dsqjs-nav{position:relative;margin:0 0 20px;border-bottom:2px solid #e7e9ee}#dsqjs ol,#dsqjs ul{list-style:none;list-style-type:none}#dsqjs .dsqjs-no-comment{text-align:center;font-size:16px;line-height:1.5;word-wrap:break-word;overflow:hidden;color:#2a2e2e;margin-bottom:6px}#dsqjs .dsqjs-nav-tab{float:left;text-transform:capitalize;font-size:15px;padding:12px 8px;color:#656c7a;display:block;margin:0 15px 0 0;font-weight:700;line-height:1;position:relative;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}#dsqjs .dsqjs-nav-tab:last-child{margin:0}#dsqjs .dsqjs-tab-active{color:#2a2e2e}#dsqjs .dsqjs-tab-active>span:after{content:" ";display:block;height:2px;background-color:#076dd0!important;position:absolute;bottom:-5px;left:0;right:0}#dsqjs .dsqjs-post-list .dsqjs-post-item{position:relative;margin-bottom:16px}#dsqjs .dsqjs-post-list .dsqjs-post-avatar{float:left;margin-right:10px;position:relative;background:#dbdfe4;padding:0;display:block;border-radius:4px}#dsqjs .dsqjs-post-list .dsqjs-post-avatar img{width:44px;height:44px;display:block;border-radius:4px}#dsqjs .dsqjs-post-list .dsqjs-post-header{line-height:1;font-size:14px;margin-bottom:3px}#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-post-author{color:#656c7a;font-weight:700}#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-admin-badge{color:#fff;background:#687a86;padding:1px 3px;margin-left:4px;font-size:12px;line-height:1;font-weight:700;border-radius:3px;display:inline-block;position:relative;top:-1px;left:1px}#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-meta{display:inline-block;font-size:12px;color:#656c7a}#dsqjs .dsqjs-post-body{font-size:15px;line-height:1.5;word-wrap:break-word;overflow:hidden;color:#2a2e2e}#dsqjs .dsqjs-post-body code{padding:.2em .4em;margin:0;font-size:85%;background:#f5f5f5;color:inherit;border-radius:3px}#dsqjs .dsqjs-post-body pre{padding:.5em;overflow:auto;font-size:85%;line-height:1.45;border-radius:3px;background:#f5f5f5;margin:.5em 0}#dsqjs .dsqjs-post-body blockquote{padding:0 .8em;margin:.5em 0;color:#6a737d;border-left:.25em solid #dfe2e5}#dsqjs .dsqjs-post-body p:last-child{margin:0}#dsqjs .dsqjs-post-list.dsqjs-children>li{margin-left:30px}@media (min-width:768px){#dsqjs .dsqjs-post-list.dsqjs-children>li{margin-left:48px}#dsqjs .dsqjs-post-list .dsqjs-post-avatar{margin-right:12px}#dsqjs .dsqjs-post-list .dsqjs-post-item{margin-bottom:20px}}@media (min-width:1024px){#dsqjs .dsqjs-post-list.dsqjs-children>li{margin-left:60px}}#dsqjs .dsqjs-post-list.dsqjs-children .dsqjs-post-avatar img{width:38px;height:38px}#dsqjs .dsqjs-load-more{font-size:14px;font-weight:400;display:block;text-align:center;padding:11px 14px;margin:0 0 24px;background:#687a86;color:#fff;cursor:pointer}#dsqjs .dsqjs-load-more:hover{opacity:.8}#dsqjs footer{text-align:right;line-height:1.5;padding-top:10px;padding-right:10px;border-top:2px solid #e7e9ee;margin-top:12px;font-weight:700;font-size:16px;color:#555}#dsqjs .dsqjs-disqus-logo{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 438 80'%3e%3cpath fill='%23575B5D' d='M30.2 1.6H1v76h28.9C57 77.6 73 61.3 73 39.4v-.2c0-22-15.7-37.6-42.9-37.6zm21.3 38.1c0 12.3-8.4 19.3-21 19.3H22V20.3h8.5c12.6 0 21 7 21 19.2v.2zm35.6 38h21.2V1.5H87.1v76zm70-47.4c-10.4-2.4-13-4-13-7.4v-.2c0-2.7 2.4-5 7.6-5 6.7 0 14.3 2.7 21.2 7.6l10.6-14.9A47.9 47.9 0 0 0 152.2.3c-18.3 0-29.4 10.2-29.4 24.3v.2c0 15.7 12.4 20.3 28.6 24 10.4 2.3 12.9 4 12.9 7.2v.2c0 3.3-3 5.2-8.7 5.2-8.8 0-17.2-3.1-24.7-9l-11.7 14a53.1 53.1 0 0 0 35.6 12.5c18.5 0 30.7-9.2 30.7-24.7V54c0-14.3-10.8-20-28.3-23.7zm120.7 9.3v-.2A39.5 39.5 0 0 0 236.9.1c-23.4 0-41 17.7-41 39.5v.2a39.5 39.5 0 0 0 40.8 39.4c8.7 0 16.6-2.5 23.1-6.8l8.4 7.5L279 68.1l-7.9-6.6a38 38 0 0 0 6.8-21.9zm-21.4.5c0 2.6-.5 5-1.3 7.3l-10.4-9.3-10.6 12 10.5 9a21.7 21.7 0 0 1-7.7 1.4c-11.6 0-19.4-9.7-19.4-20.7v-.2c0-11 7.7-20.5 19.2-20.5 11.7 0 19.7 9.7 19.7 20.7v.3zm83.5 4.3c0 10.6-5.5 15.6-14 15.6s-14-5.2-14-16.1V1.6h-21.4v42.7C290.5 68 304 79 325.7 79s35.6-10.8 35.6-35.3V1.5h-21.4v42.8zm68.9-14.1c-10.6-2.4-13.2-4-13.2-7.4v-.2c0-2.7 2.5-5 7.6-5 6.8 0 14.4 2.7 21.3 7.6l10.6-14.9A47.9 47.9 0 0 0 403.8.3c-18.3 0-29.5 10.2-29.5 24.3v.2c0 15.7 12.5 20.3 28.7 24 10.3 2.3 12.8 4 12.8 7.2v.2c0 3.3-3 5.3-8.7 5.3-8.8 0-17.1-3.2-24.6-9.2l-11.7 14A53.1 53.1 0 0 0 406.4 79c18.5 0 30.7-9.2 30.7-24.7V54c0-14.3-10.8-20-28.3-23.7z'/%3e%3c/svg%3e");background-position:50% 50%;background-repeat:no-repeat;display:inline-block;height:12px;width:65.7px}#dsqjs .dsqjs-order{display:-webkit-box;display:-ms-flexbox;display:flex;float:right;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:10px;margin-bottom:12px}#dsqjs .dsqjs-order-radio{display:none}#dsqjs .dsqjs-order-radio:checked+.dsqjs-order-label{color:#fff;background-color:#888}#dsqjs .dsqjs-order-label{display:block;height:20px;line-height:20px;margin-right:10px;font-size:12px;border-radius:2px;padding:0 5px;background-color:#dcdcdc;cursor:pointer}#dsqjs p.dsqjs-has-more{margin-bottom:24px;margin-left:48px;font-size:13px;line-height:15px}#dsqjs p.dsqjs-has-more a.dsqjs-has-more-btn{color:#656c7a;text-decoration:underline;cursor:pointer} -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 |Disqus 替代方案 - 使用 Disqus API 渲染评论列表127 |
DisqusJS 提供一个纯前端的、不依赖后端服务器的 Disqus 评论基础模式。
128 | 132 |评论列表加载中...
正在切换排序方式...
',r.classList.add("dsqjs-hide"),t()},e=""===s?"":"&cursor="+s;r.classList.add("dsqjs-disabled");function c(s){var e=s.createdAt;return Date.parse(new Date(e))}function l(s,e){return s.parent&&e.parent?c(s)-c(e):0}var u=M.config.api+"3.0/threads/listPostsThreaded?forum="+encodeURIComponent(M.config.shortname)+"&thread="+encodeURIComponent(M.page.id)+encodeURIComponent(e)+"&api_key="+encodeURIComponent(k())+"&order="+encodeURIComponent(M.sortType);b(u).then(function(s){var e,t=s.data;if(0===t.code&&0切换至 完整 Disqus 模式 显示更多回复
'),t+='切换至 完整 Disqus 模式 显示更多回复
':"";r+='评论列表加载中...
正在切换排序方式...
'; 326 | // 把 加载更多评论 隐藏起来 327 | $loadMoreBtn.classList.add('dsqjs-hide'); 328 | getComment(); 329 | } 330 | 331 | // 处理传入的 cursor 332 | const cursorParam = (cursor === '') ? '' : `&cursor=${cursor}`; 333 | 334 | // 在发起请求前禁用 加载更多评论 按钮防止重复调用 335 | $loadMoreBtn.classList.add('dsqjs-disabled'); 336 | /* 337 | * 获取评论列表 338 | * 339 | * API Docs: https://disqus.com/api/docs/posts/list/ 340 | * API URI: /3.0/posts/list.json?forum=[shortname]&thread=[thread id]&api_key=[apikey] 341 | * 342 | * https://github.com/SukkaW/DisqusJS/issues/6 343 | * 可以使用 include=deleted 来获得已被删除评论列表 344 | * 345 | * https://blog.fooleap.org/disqus-api-comments-order-by-desc.html 346 | * 处理评论嵌套问题,使用了一个隐藏 API /threads/listPostsThreaded 347 | * 用法和 /threads/listPosts 相似,和 /threads/post 的区别也只有 include 字段不同 348 | * 这个能够返回已删除评论,所以也不需要 include=deleted 了 349 | * sort 字段提供三个取值: 350 | * - desc (降序) 351 | * - asc (升序) 352 | * - popular(最热) 353 | * 这个 API 的问题在于被嵌套的评论总是降序,看起来很不习惯 354 | * 355 | * 每次加载翻页评论的时候合并数据并进行重排序 356 | * 用户切换排序方式的时候直接取出进行重新渲染 357 | */ 358 | const sortCommentParseDate = ({ createdAt }) => Date.parse(new Date(createdAt)); 359 | const sortCommentparentAsc = (a, b) => { 360 | if (a.parent && b.parent) { 361 | return sortCommentParseDate(a) - sortCommentParseDate(b); 362 | } else { 363 | return 0; 364 | } 365 | }; 366 | 367 | const url = `${disqusjs.config.api}3.0/threads/listPostsThreaded?forum=${encodeURIComponent(disqusjs.config.shortname)}&thread=${encodeURIComponent(disqusjs.page.id)}${encodeURIComponent(cursorParam)}&api_key=${encodeURIComponent(apikey())}&order=${encodeURIComponent(disqusjs.sortType)}`; 368 | 369 | _get(url).then(({ data }) => { 370 | if (data.code === 0 && data.response.length > 0) { 371 | // 解禁 加载更多评论 372 | $loadMoreBtn.classList.remove('dsqjs-disabled'); 373 | 374 | // 将获得的评论数据和当前页面已有的评论数据合并 375 | disqusjs.page.comment.push(...data.response) 376 | 377 | // 将所有的子评论进行降序排列 378 | disqusjs.page.comment.sort(sortCommentparentAsc); 379 | 380 | // 用当前页面的所有评论数据进行渲染 381 | renderComment(disqusjs.page.comment); 382 | 383 | // 为排序按钮们委托事件 384 | Array.prototype.slice.call($orderRadio).forEach(i => i.addEventListener('change', switchSortType)); 385 | Array.prototype.slice.call($loadHideCommentInDisqus).forEach(i => i.addEventListener(CLICK, checkDisqus)); 386 | 387 | if (data.cursor.hasNext) { 388 | // 将 cursor.next 存入 disqusjs 变量中供不能传参的不匿名函数使用 389 | disqusjs.page.next = data.cursor.next; 390 | // 确保 加载更多评论按钮 文字正常 391 | $loadMoreBtn.innerHTML = '加载更多评论' 392 | // 显示 加载更多评论 按钮 393 | $loadMoreBtn.classList.remove('dsqjs-hide'); 394 | $loadMoreBtn.addEventListener(CLICK, getMoreComment); 395 | } else { 396 | // 没有更多评论了,确保按钮隐藏 397 | $loadMoreBtn.classList.add('dsqjs-hide'); 398 | } 399 | } else if (data.code === 0 && data.response.length === 0) { 400 | // 当前没有评论,显示提示信息 401 | msg(`你可能无法访问 Disqus,已启用评论基础模式。${HTML_TPL_EL_ASK_FOR_FULL}`) 402 | $$('dsqjs-post-container').innerHTML = `${disqusjs.config.nocomment}
` 403 | 404 | assignClickEventForAskForFulButton(); 405 | 406 | $$('dsqjs-force-disqus').addEventListener(CLICK, useDsqjs); 407 | } else { 408 | throw new Error; 409 | } 410 | }).catch(getCommentError); 411 | } 412 | 413 | /** 414 | * parseCommentData(data) - 解析评论列表 415 | * 416 | * @param {Array} data - 评论列表 JSON 417 | * @return {Array} - 解析后的评论列表数据 418 | */ 419 | const parseCommentData = (data) => { 420 | let topLevelComments = []; 421 | let childComments = []; 422 | 423 | const commentJSON = (comment) => ({ 424 | comment, 425 | author: comment.author.name, 426 | 427 | // 如果不设置 admin 会返回 undefined,所以需要嘴一个判断 428 | isPrimary: (disqusjs.config.admin ? (comment.author.username === disqusjs.config.admin) : false), 429 | 430 | children: getChildren(+comment.id), 431 | 432 | /* 433 | * Disqus 改变了 Private API 的行为 434 | * https://github.com/fooleap/disqus-php-api/issues/44 435 | * 默认隐藏更多的评论,通过 hasMore 字段判断 436 | */ 437 | // 将 hasMore 字段提升到上层字段中 438 | hasMore: comment.hasMore 439 | }); 440 | 441 | const getChildren = (id) => { 442 | // 如果没有子评论,就不需要解析子评论了 443 | if (childComments.length === 0) return null; 444 | 445 | const list = []; 446 | childComments.forEach(comment => { 447 | if (comment.parent === id) { 448 | list.unshift(commentJSON(comment)); 449 | } 450 | }) 451 | 452 | return list.length ? list : null 453 | } 454 | 455 | data.forEach(comment => { 456 | // 如果没有 comment.parent 说明是第一级评论 457 | const c = comment.parent ? childComments : topLevelComments; 458 | c.push(comment); 459 | }); 460 | return topLevelComments.map(commentJSON); 461 | } 462 | 463 | /* 464 | * renderCommentData(data) - 渲染评论列表 465 | * 466 | * @param {Array} data - 从 getComment() 获取到的 JSON 467 | */ 468 | const renderComment = (data) => { 469 | /* 470 | * processData(data) - 处理评论列表 471 | * 472 | * @param {Array} data - 解析后的评论列表 JSON 473 | */ 474 | const processData = (data) => { 475 | // 处理 Disqus Profile URL 476 | if (data.comment.author.profileUrl) { 477 | /* 478 | Avatar Element 479 | 480 |切换至 完整 Disqus 模式 显示更多回复
` 569 | : ''; 570 | 571 | html += `切换至 完整 Disqus 模式 显示更多回复
` 596 | } 597 | 598 | html += `
演示
136 |如果您当前网络支持访问 Disqus,DisqusJS 将会自动加载完整 DISQUS 模式。如果需要预览 DisqusJS 你可以 点击这里 来强制使用 DisqusJS 的评论基础模式
137 |