├── .gitignore ├── app ├── popup │ ├── popup.js │ └── popup.html ├── content-scripts │ ├── zoom │ │ └── zoom.js │ ├── uploadImg │ │ ├── sass │ │ │ └── uploadImg.scss │ │ └── uploadImg.js │ ├── collection │ │ └── collection.js │ ├── checkConversation │ │ ├── sass │ │ │ └── checkConversation.scss │ │ └── checkConversation.js │ ├── checkReply │ │ ├── jump.js │ │ └── insertCheckBtn.js │ └── signin │ │ └── signin.js ├── option │ ├── option.html │ └── option.js ├── settings │ └── settings.js └── background-scripts │ ├── background.js │ ├── uploadImg.js │ ├── setNotifications.js │ └── checkConversation.js ├── extension ├── icons │ ├── icon_128.png │ ├── icon_16.png │ ├── icon_19.png │ ├── icon_38.png │ └── icon_48.png ├── popup │ ├── popup.html │ └── popup.js ├── option │ ├── option.html │ └── option.js ├── content-scripts │ ├── collection │ │ └── collection.js │ ├── checkReply │ │ ├── jump.js │ │ └── insertCheckBtn.js │ ├── signin │ │ └── signin.js │ ├── zoom │ │ └── zoom.js │ ├── uploadImg │ │ └── uploadImg.js │ └── checkConversation │ │ └── checkConversation.js ├── manifest.json ├── manifest_with_comments.json └── background-scripts │ └── background.js ├── .babelrc ├── README.md ├── package.json ├── CHANGELOG.md ├── LICENSE ├── INTRO.md └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /app/popup/popup.js: -------------------------------------------------------------------------------- 1 | // popup.js -------------------------------------------------------------------------------- /extension/icons/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lessfish/v2ex-helper/HEAD/extension/icons/icon_128.png -------------------------------------------------------------------------------- /extension/icons/icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lessfish/v2ex-helper/HEAD/extension/icons/icon_16.png -------------------------------------------------------------------------------- /extension/icons/icon_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lessfish/v2ex-helper/HEAD/extension/icons/icon_19.png -------------------------------------------------------------------------------- /extension/icons/icon_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lessfish/v2ex-helper/HEAD/extension/icons/icon_38.png -------------------------------------------------------------------------------- /extension/icons/icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lessfish/v2ex-helper/HEAD/extension/icons/icon_48.png -------------------------------------------------------------------------------- /app/popup/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | this is popup html 11 | 12 | 13 | -------------------------------------------------------------------------------- /extension/popup/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | this is popup html 11 | 12 | 13 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", { 5 | "useBuiltIns": true, 6 | // "modules": false, 7 | "targets": { 8 | // "browsers": ["ie >= 7"] 9 | "browsers": ["chrome >= 60"] 10 | } 11 | } 12 | ] 13 | ] 14 | } -------------------------------------------------------------------------------- /app/content-scripts/zoom/zoom.js: -------------------------------------------------------------------------------- 1 | import mediumZoom from 'medium-zoom' 2 | import {getSettingsAsync} from '../../settings/settings.js' 3 | // import 'babel-polyfill' 4 | 5 | (async function() { 6 | let cfg = await getSettingsAsync() 7 | 8 | if (!cfg.cfg_zoom) return 9 | 10 | mediumZoom(document.querySelectorAll('.topic_content img')) 11 | })() -------------------------------------------------------------------------------- /app/content-scripts/uploadImg/sass/uploadImg.scss: -------------------------------------------------------------------------------- 1 | /* www.v2ex.com/new */ 2 | /* 发帖添加图片 */ 3 | #uploadImgBtn { 4 | float: right; 5 | display: none; 6 | } 7 | 8 | #uploadTriggerBtn { 9 | padding-left: 10px; 10 | 11 | &:hover { 12 | cursor: pointer; 13 | opacity: .7; 14 | } 15 | } 16 | 17 | .not-allow { 18 | pointer-events: none; 19 | } -------------------------------------------------------------------------------- /app/content-scripts/collection/collection.js: -------------------------------------------------------------------------------- 1 | document.querySelector('#Main > div:nth-child(2) > div.topic_buttons > a:nth-child(2)').onclick = () => { 2 | fetch(location.href, {credentials: "include"}) 3 | .then(res => res.text()) 4 | .then(data => { 5 | let p = /\/(un)?favorite\/topic\/[0-9]+\?t=[a-z]+/ 6 | let apiUrl = data.match(p)[0] 7 | location.href = apiUrl 8 | }) 9 | 10 | return false 11 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # V2EX HELPER 2 | 3 | ## Intro 4 | 5 | :rocket: [使用说明和功能介绍](INTRO.md) 6 | 7 | ## Usage 8 | 9 | [Chrome 网上应用商店](https://chrome.google.com/webstore/detail/v2ex-helper/ceciedfhiofnohkddfiibjieahonddjm) 10 | 11 | ## Edge Version's Usage 12 | 13 | 1. `git clone git@github.com:hanzichi/v2ex-helper.git` 14 | 2. 打开 chrome 浏览器,地址栏输入 `chrome://extensions/` 15 | 3. 勾选上「开发者模式」选项(右上角) 16 | 4. 点击「加载已解压的扩展程序」(左上角),选择第一步 clone 到本地的文件夹中的 **子文件夹 extension** 进行导入 17 | 18 | ## License 19 | 20 | MIT -------------------------------------------------------------------------------- /extension/popup/popup.js: -------------------------------------------------------------------------------- 1 | !function(t){var r={};function e(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,e),o.l=!0,o.exports}e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,r){return Object.prototype.hasOwnProperty.call(t,r)},e.p="",e(e.s=25)}({25:function(t,r,e){"use strict"}}); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@babel/preset-env": "^7.0.0-beta.4", 4 | "babel-core": "^6.26.0", 5 | "babel-loader": "^7.1.2", 6 | "babel-preset-env": "^1.6.1", 7 | "css-loader": "^0.28.7", 8 | "node-sass": "^4.5.3", 9 | "sass-loader": "^6.0.6", 10 | "style-loader": "^0.19.0", 11 | "uglifyjs-webpack-plugin": "^1.0.0-rc.0", 12 | "webpack": "^3.8.1" 13 | }, 14 | "dependencies": { 15 | "babel-polyfill": "^6.26.0", 16 | "medium-zoom": "^0.2.0", 17 | "pure-css-loader": "^3.0.1", 18 | "tingle.js": "^0.12.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/option/option.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CONFIGURATION OF V2EX HELPER 5 | 6 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /extension/option/option.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CONFIGURATION OF V2EX HELPER 5 | 6 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 2017-04-24 4 | 5 | 加入/取消收藏按钮无需多次点击(原因是因为每个页面 csrf token 都会变化,只能取最后一个页面。解决办法有两种,其一是每个页面访问时更新 token,点击加入/收藏 按钮,动态替换 url,使用当前保存的最新 token;其二也是目前暂时的方案,每次点击加入/收藏 按钮后,再动态去获取一下当前页面,也是为了获取最新能用的 token) 6 | 7 | ## 2017-11-06 8 | 9 | Remove jQuery dependency 10 | ## 2017-10-30 11 | 12 | 使用 Webpack 进行开发 13 | 14 | ## 2017-10-28 15 | 16 | 新增消息提醒 17 | 18 | ## 2017-10-27 19 | 20 | 新增设置(选项)页 21 | 22 | ## 2017-10-27 23 | 24 | 自动签到 25 | 26 | ## 2017-10-24 27 | 28 | 主题帖回复中增加对话详情功能 29 | 30 | ## 2017-10-18 31 | 32 | 增加发主题帖添加图片功能(微博图床,需登录微博) 33 | 34 | ## 2017-08-18 35 | 36 | 增加主题帖中图片点击放大功能 37 | 38 | ## 2017-08-13 39 | 40 | [通知页面](https://www.v2ex.com/notifications) 点击「查看」可跳转到该回复具体楼层 -------------------------------------------------------------------------------- /extension/content-scripts/collection/collection.js: -------------------------------------------------------------------------------- 1 | !function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=20)}({20:function(t,e,n){"use strict";document.querySelector("#Main > div:nth-child(2) > div.topic_buttons > a:nth-child(2)").onclick=(()=>(fetch(location.href,{credentials:"include"}).then(t=>t.text()).then(t=>{let e=t.match(/\/(un)?favorite\/topic\/[0-9]+\?t=[a-z]+/)[0];location.href=e}),!1))}}); -------------------------------------------------------------------------------- /app/content-scripts/checkConversation/sass/checkConversation.scss: -------------------------------------------------------------------------------- 1 | /* www.v2ex.com/t/ */ 2 | /* 对话详情 */ 3 | #checkConversationBtn { 4 | padding-left: 10px; 5 | color: #ccc; 6 | font-size: 12px; 7 | &:hover { 8 | opacity: .7; 9 | } 10 | > i { 11 | padding-right: 4px; 12 | } 13 | } 14 | 15 | .tingle-modal-box__content { 16 | max-width: 800px; 17 | padding: 10px !important; 18 | line-height: 1.6 !important; 19 | word-break: break-all; 20 | word-wrap: break-word; 21 | > .cell { 22 | overflow: hidden; 23 | > .left { 24 | float: left; 25 | img { 26 | border-radius: 4px; 27 | } 28 | } 29 | > .right { 30 | margin-left: 60px; 31 | > .author { 32 | color: gray; 33 | margin-bottom: 10px; 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/settings/settings.js: -------------------------------------------------------------------------------- 1 | const options = [ 2 | '回复通知增强', 3 | '主题贴图片点击放大', 4 | '主题贴发图', 5 | '对话详情', 6 | '自动签到', 7 | '新消息提醒(弹窗模式)', 8 | '新消息提醒(图标提醒)', 9 | ] 10 | 11 | let keys = [ 12 | 'checkReply', 13 | 'zoom', 14 | 'uploadImg', 15 | 'checkConversation', 16 | 'signin', 17 | 'notificationsPopup', 18 | 'notificationsIconShowNum', 19 | ] 20 | 21 | const prefix = 'cfg_' 22 | 23 | keys = keys.map(item => prefix + item) 24 | 25 | // get settings 26 | function getSettingsAsync() { 27 | return new Promise(resolve => { 28 | chrome.storage.sync.get(keys, function(cfg) { 29 | keys.forEach(item => { 30 | cfg[item] = cfg[item] === undefined || cfg[item] ? true : false 31 | }) 32 | 33 | resolve(cfg) 34 | }) 35 | }) 36 | } 37 | 38 | export { 39 | options, keys, getSettingsAsync 40 | } -------------------------------------------------------------------------------- /app/option/option.js: -------------------------------------------------------------------------------- 1 | import {options, keys, getSettingsAsync} from '../settings/settings.js' 2 | 3 | (async function(){ 4 | let cfg = await getSettingsAsync() 5 | let html = '' 6 | 7 | options.forEach((item, index) => { 8 | let key = keys[index] 9 | let value = cfg[key] === undefined || cfg[key] ? true : false 10 | 11 | html += ` 12 |
13 | ${item}: 14 | 15 |
16 | ` 17 | }) 18 | 19 | document.querySelector('.container').innerHTML = html 20 | 21 | document.querySelector('.container').addEventListener('click', e => { 22 | if (e.target.type !== 'checkbox') return 23 | 24 | let [k, v] = [e.target.getAttribute('key'), e.target.checked] 25 | let setting = {} 26 | setting[k] = v 27 | chrome.storage.sync.set(setting, function() {}) 28 | }, false) 29 | })() -------------------------------------------------------------------------------- /extension/content-scripts/checkReply/jump.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=7)}({7:function(e,t,r){"use strict";!function(){let e=~~location.hash.replace(/#reply/,""),t=[].slice.call(document.querySelectorAll("#Main > div:nth-child(4) > .cell[id]")),r=1+~~(e/100);if(1+~~(t[0].querySelector("table > tbody > tr > td:nth-child(3) > div.fr > span").innerHTML/100)!==r){let e=location.href.replace(/isJump=1/,"$&&p="+r);location.href=e}else e%=100,setTimeout(()=>{t[e-1].scrollIntoView(),t[e-1].style.background="#FFF9EB"},0)}()}}); -------------------------------------------------------------------------------- /app/content-scripts/checkReply/jump.js: -------------------------------------------------------------------------------- 1 | // the script can deal with DOM 2 | (function () { 3 | // auto jump 4 | let floor = ~~(location.hash.replace(/#reply/, '')) 5 | let lis = [].slice.call(document.querySelectorAll('#Main > div:nth-child(4) > .cell[id]')) 6 | 7 | // if the floor is not in the current page, then go to the correct page 8 | let expPage = ~~(floor / 100) + 1 // expected page 9 | let curPage = ~~(lis[0].querySelector('table > tbody > tr > td:nth-child(3) > div.fr > span').innerHTML / 100) + 1 10 | 11 | if (curPage !== expPage) { 12 | let curUrl = location.href 13 | let newUrl = curUrl 14 | .replace(/isJump=1/, '$&' + '&p=' + expPage) 15 | 16 | location.href = newUrl 17 | } else { 18 | floor %= 100 19 | 20 | // jump to lis[floor - 1] 21 | setTimeout(() => { 22 | lis[floor - 1].scrollIntoView() 23 | lis[floor - 1].style.background = "#FFF9EB" 24 | }, 0) 25 | } 26 | })() -------------------------------------------------------------------------------- /app/background-scripts/background.js: -------------------------------------------------------------------------------- 1 | import checkConversation from './checkConversation.js' 2 | import uploadImgInTopic from './uploadImg.js' 3 | import setNotifications from './setNotifications.js' 4 | import {getSettingsAsync} from '../settings/settings.js' 5 | 6 | // 一直运行在后台的 js,不能操作 DOM 7 | // 有跨域权限,content_scripts 没有跨域权限 8 | // events listener 9 | (function() { 10 | chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { 11 | switch(message.method) { 12 | case 'uploadImgInTopic': 13 | uploadImgInTopic(message, sendResponse) 14 | // 异步,直到执行 sendResponse() 方法 15 | return true 16 | break 17 | 18 | case 'checkConversationBtn': 19 | checkConversation(message, sendResponse) 20 | return true 21 | break 22 | } 23 | }) 24 | })() 25 | 26 | // 消息提醒 27 | ;(async function() { 28 | let cfg = await getSettingsAsync() 29 | setNotifications(cfg) 30 | })() -------------------------------------------------------------------------------- /app/background-scripts/uploadImg.js: -------------------------------------------------------------------------------- 1 | // 主题贴添加图片 2 | export default function(message, sendResponse) { 3 | // 微博图床接口 4 | let api = 'http://picupload.service.weibo.com/interface/pic_upload.php?\ 5 | mime=image%2Fjpeg&data=base64&url=0' 6 | 7 | let xhr = new XMLHttpRequest() 8 | let data = new FormData() 9 | 10 | // post 的数据,值为图片的 base64 编码 11 | // 需要去掉类似前缀 `data:image/jpeg;base64,` 12 | data.append('b64_data', message.dataURL) 13 | 14 | xhr.onerror = () => {} // todo 15 | 16 | xhr.onload = function () { 17 | try { 18 | const pattern = /"pid":"(.+)"/ 19 | let data = xhr.responseText 20 | let imgUrlPrefix = 'https://ws2.sinaimg.cn/large/' 21 | let pid = data.match(pattern)[1] 22 | 23 | sendResponse({ 24 | status: 0, // success 25 | imgUrl: imgUrlPrefix + pid 26 | }) 27 | } catch(err) { 28 | sendResponse({ 29 | status: 1 // unlogin 30 | }) 31 | } 32 | } 33 | 34 | xhr.open('POST', api) 35 | xhr.send(data) 36 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 子迟 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. 22 | -------------------------------------------------------------------------------- /INTRO.md: -------------------------------------------------------------------------------- 1 | # INTRO 2 | 3 | 多图,显示不了的话建议多刷新几次。 4 | 5 | ## 安装说明 6 | 7 | ![](https://ww1.sinaimg.cn/large/006lOxA2gy1fl7lf24js4g311f0kwx6p.gif) 8 | 9 | ## 功能介绍 10 | 11 | ### 选项设置 12 | 13 | 默认开启全部功能 14 | 15 | ![](https://ws2.sinaimg.cn/large/006tNc79gy1fl7m4xzui8j31hg0vejx4.jpg) 16 | 17 | ### 回复通知增强 18 | 19 | [通知页面](https://www.v2ex.com/notifications) 点击「查看」可跳转到该回复具体楼层 20 | 21 | ![](https://ws2.sinaimg.cn/large/006lOxA2gy1fkns0q2l4tg30wp0j1qv5.gif) 22 | 23 | ### 主题贴图片点击放大 24 | 25 | ![](https://ws2.sinaimg.cn/large/006lOxA2gy1fkns0y16r6g313a0ie1ky.gif) 26 | 27 | ### 主题贴发图 28 | 29 | ![](https://ws2.sinaimg.cn/large/006lOxA2gy1fkns154stbg313a0ie4qp.gif) 30 | 31 | ### 对话详情 32 | 33 | ![](https://ws4.sinaimg.cn/large/006tKfTcgy1fku9k4wbk4g31150ieqo3.gif) 34 | 35 | ### 自动签到 36 | 37 | 该天第一次登录 V 站,会自动帮你签到。 38 | 39 | 默认没有提醒。如果登录的是首页,则会提示如下: 40 | 41 | ![](https://ws1.sinaimg.cn/large/006tNc79gy1fl84fgqirej30z80hi40o.jpg) 42 | 43 | ### 新消息提醒(弹窗模式) 44 | 45 | 如果有新消息,则通过弹窗进行提醒。默认 10 分钟检查一次。 46 | 47 | ![](https://ws3.sinaimg.cn/large/006tNc79gy1fl84esx1kej30zk0lg0v4.jpg) 48 | 49 | ### 新消息提醒(图标提醒) 50 | 51 | 如果有新消息,则右上角该扩展图标显示红色背景消息数进行提醒。默认 10 分钟检查一次。 52 | 53 | ![](https://ws3.sinaimg.cn/large/006tNc79gy1fl843fsxk3j316a0ismz2.jpg) -------------------------------------------------------------------------------- /app/content-scripts/checkReply/insertCheckBtn.js: -------------------------------------------------------------------------------- 1 | import {getSettingsAsync} from '../../settings/settings.js' 2 | // import 'babel-polyfill' 3 | 4 | // the script can deal with DOM 5 | (async function () { 6 | let cfg = await getSettingsAsync() 7 | 8 | if (cfg.cfg_checkReply === false) return 9 | 10 | // find the replies, and append "check" button 11 | let lis = [].slice.call(document.querySelectorAll('#Main > div:nth-child(2) > .cell[id]')) 12 | 13 | lis.forEach(item => { 14 | let html = item.querySelector('table > tbody > tr > td:nth-child(2) > span.fade').innerHTML.trim() 15 | if (html.endsWith('回复了你') || html.endsWith('提到了你')) { // is a reply 16 | let nextNode = item.querySelector('table > tbody > tr > td:nth-child(2) > div.sep5') 17 | let parentNode = item.querySelector('table > tbody > tr > td:nth-child(2)') 18 | let addNode = document.createElement('a') 19 | addNode.style.marginLeft = '5px' 20 | addNode.className = 'node' 21 | addNode.innerHTML = '查看' 22 | 23 | let replyUrl = item.querySelector('table > tbody > tr > td:nth-child(2) > span.fade > a:nth-child(2)').href 24 | replyUrl = replyUrl.replace(/#reply[0-9]+/, '?isJump=1' + '$&') 25 | addNode.href = replyUrl 26 | 27 | parentNode.insertBefore(addNode, nextNode) 28 | } 29 | }) 30 | })() -------------------------------------------------------------------------------- /extension/option/option.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function n(o){if(t[o])return t[o].exports;var c=t[o]={i:o,l:!1,exports:{}};return e[o].call(c.exports,c,c.exports,n),c.l=!0,c.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:o})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=26)}({0:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let o=["checkReply","zoom","uploadImg","checkConversation","signin","notificationsPopup","notificationsIconShowNum"];t.keys=o=o.map(e=>"cfg_"+e),t.options=["回复通知增强","主题贴图片点击放大","主题贴发图","对话详情","自动签到","新消息提醒(弹窗模式)","新消息提醒(图标提醒)"],t.keys=o,t.getSettingsAsync=function(){return new Promise(e=>{chrome.storage.sync.get(o,function(t){o.forEach(e=>{t[e]=!(void 0!==t[e]&&!t[e])}),e(t)})})}},26:function(e,t,n){"use strict";var o=n(0);!async function(){let e=await(0,o.getSettingsAsync)(),t="";o.options.forEach((n,c)=>{let r=o.keys[c],i=!(void 0!==e[r]&&!e[r]);t+=`\n
\n ${n}:\n \n
\n `}),document.querySelector(".container").innerHTML=t,document.querySelector(".container").addEventListener("click",e=>{if("checkbox"!==e.target.type)return;let[t,n]=[e.target.getAttribute("key"),e.target.checked],o={};o[t]=n,chrome.storage.sync.set(o,function(){})},!1)}()}}); -------------------------------------------------------------------------------- /app/background-scripts/setNotifications.js: -------------------------------------------------------------------------------- 1 | export default function(cfg) { 2 | // add browserAction listener 3 | chrome.browserAction.onClicked.addListener(() => { 4 | chrome.browserAction.getBadgeText({}, res => { 5 | if (cfg.cfg_notificationsIconShowNum && res !== '') { 6 | chrome.browserAction.setBadgeText({text: ''}) 7 | window.open('https://www.v2ex.com/notifications') 8 | } else { 9 | window.open('https://www.v2ex.com') 10 | } 11 | }) 12 | }) 13 | 14 | // todo 15 | // 当访问 https://www.v2ex.com/notifications 后 16 | // 如果 icon 有未读提醒,则取消 17 | 18 | if (!cfg.cfg_notificationsPopup && !cfg.cfg_notificationsIconShowNum) return 19 | 20 | setInterval(() => { 21 | fetch('https://www.v2ex.com/', {credentials: 'same-origin'}) // cookie 22 | .then(res => res.text()) 23 | .then(res => { 24 | const p = /(\d+) 条未读提醒/ 25 | const r = p.exec(res) 26 | 27 | if (!r || r[1] === '0') return 28 | 29 | if (cfg.cfg_notificationsPopup) { 30 | chrome.notifications.create(null, { 31 | type: 'basic', 32 | iconUrl: 'icons/icon_48.png', 33 | title: `from V2EX HELPER's Notification`, 34 | message: `您有 ${r[1]} 条来自 V2EX 的新消息!`, 35 | }) 36 | } 37 | 38 | if (cfg.cfg_notificationsIconShowNum) { 39 | chrome.browserAction.setBadgeText({text: r[1]}); 40 | chrome.browserAction.setBadgeBackgroundColor({color: [255, 0, 0, 255]}); 41 | } 42 | }) 43 | }, 1000 * 10 * 60) // every 10 mins 44 | } -------------------------------------------------------------------------------- /extension/content-scripts/checkReply/insertCheckBtn.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=6)}({0:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let r=["checkReply","zoom","uploadImg","checkConversation","signin","notificationsPopup","notificationsIconShowNum"];t.keys=r=r.map(e=>"cfg_"+e),t.options=["回复通知增强","主题贴图片点击放大","主题贴发图","对话详情","自动签到","新消息提醒(弹窗模式)","新消息提醒(图标提醒)"],t.keys=r,t.getSettingsAsync=function(){return new Promise(e=>{chrome.storage.sync.get(r,function(t){r.forEach(e=>{t[e]=!(void 0!==t[e]&&!t[e])}),e(t)})})}},6:function(e,t,n){"use strict";var r=n(0);!async function(){!1!==(await(0,r.getSettingsAsync)()).cfg_checkReply&&[].slice.call(document.querySelectorAll("#Main > div:nth-child(2) > .cell[id]")).forEach(e=>{let t=e.querySelector("table > tbody > tr > td:nth-child(2) > span.fade").innerHTML.trim();if(t.endsWith("回复了你")||t.endsWith("提到了你")){let t=e.querySelector("table > tbody > tr > td:nth-child(2) > div.sep5"),n=e.querySelector("table > tbody > tr > td:nth-child(2)"),r=document.createElement("a");r.style.marginLeft="5px",r.className="node",r.innerHTML="查看";let o=e.querySelector("table > tbody > tr > td:nth-child(2) > span.fade > a:nth-child(2)").href;o=o.replace(/#reply[0-9]+/,"?isJump=1$&"),r.href=o,n.insertBefore(r,t)}})}()}}); -------------------------------------------------------------------------------- /app/content-scripts/signin/signin.js: -------------------------------------------------------------------------------- 1 | import {getSettingsAsync} from '../../settings/settings.js' 2 | // import 'babel-polyfill' 3 | 4 | (async function() { 5 | let cfg = await getSettingsAsync() 6 | 7 | if (cfg.cfg_signin === false) return 8 | 9 | // 先判断今天有没有签到过,如果有,则直接返回 10 | const today = new Date() 11 | const year = today.getFullYear() 12 | const month = today.getMonth() + 1 13 | const day = today.getDate() 14 | const todayStr = [year, month, day].join('-') 15 | 16 | chrome.storage.sync.get(['lastSigninDate'], function(items) { 17 | if (items.lastSigninDate === todayStr) return // 今天已经签到过了 18 | 19 | fetch('//www.v2ex.com/mission/daily', {credentials: 'same-origin'}) // cookie 20 | .then(res => res.text()) 21 | .then(res => { 22 | const isSignin = !res.includes('领取 X 铜币') 23 | if (isSignin) return 24 | 25 | const p = /\/mission\/daily\/redeem\?once=\d+/ 26 | const api = p.exec(res)[0] 27 | 28 | fetch(api, {credentials: 'same-origin'}) // cookie 29 | .then(res => res.text()) 30 | .then(res => { 31 | if (res) { 32 | // 如果是首页,则替换 33 | if (!document.querySelector('.fa.fa-gift')) return 34 | 35 | document.querySelector('.fa.fa-gift').nextElementSibling.innerHTML = '今日已签到' 36 | document.querySelector('.fa.fa-gift').classList.add('fa-check') 37 | document.querySelector('.fa.fa-gift').classList.remove('fa-gift') 38 | 39 | // 今日签到 40 | chrome.storage.sync.set({lastSigninDate: todayStr}, function() {}) 41 | } 42 | }) 43 | }) 44 | }) 45 | })() -------------------------------------------------------------------------------- /extension/content-scripts/signin/signin.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:i})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=11)}({0:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let i=["checkReply","zoom","uploadImg","checkConversation","signin","notificationsPopup","notificationsIconShowNum"];t.keys=i=i.map(e=>"cfg_"+e),t.options=["回复通知增强","主题贴图片点击放大","主题贴发图","对话详情","自动签到","新消息提醒(弹窗模式)","新消息提醒(图标提醒)"],t.keys=i,t.getSettingsAsync=function(){return new Promise(e=>{chrome.storage.sync.get(i,function(t){i.forEach(e=>{t[e]=!(void 0!==t[e]&&!t[e])}),e(t)})})}},11:function(e,t,n){"use strict";var i=n(0);!async function(){if(!1===(await(0,i.getSettingsAsync)()).cfg_signin)return;const e=new Date,t=[e.getFullYear(),e.getMonth()+1,e.getDate()].join("-");chrome.storage.sync.get(["lastSigninDate"],function(e){e.lastSigninDate!==t&&fetch("//www.v2ex.com/mission/daily",{credentials:"same-origin"}).then(e=>e.text()).then(e=>{if(!e.includes("领取 X 铜币"))return;const n=/\/mission\/daily\/redeem\?once=\d+/.exec(e)[0];fetch(n,{credentials:"same-origin"}).then(e=>e.text()).then(e=>{if(e){if(!document.querySelector(".fa.fa-gift"))return;document.querySelector(".fa.fa-gift").nextElementSibling.innerHTML="今日已签到",document.querySelector(".fa.fa-gift").classList.add("fa-check"),document.querySelector(".fa.fa-gift").classList.remove("fa-gift"),chrome.storage.sync.set({lastSigninDate:t},function(){})}})})})}()}}); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin') 3 | const path = require('path') 4 | 5 | module.exports = { 6 | // devtool: 'inline-source-map', 7 | entry: { 8 | // content-scripts 9 | 'content-scripts/zoom/zoom.js': './app/content-scripts/zoom/zoom.js', 10 | 'content-scripts/checkReply/insertCheckBtn.js': './app/content-scripts/checkReply/insertCheckBtn.js', 11 | 'content-scripts/checkReply/jump.js': './app/content-scripts/checkReply/jump.js', 12 | 'content-scripts/uploadImg/uploadImg.js': './app/content-scripts/uploadImg/uploadImg.js', 13 | 'content-scripts/signin/signin.js': './app/content-scripts/signin/signin.js', 14 | 'content-scripts/checkConversation/checkConversation.js': './app/content-scripts/checkConversation/checkConversation.js', 15 | 'content-scripts/collection/collection.js': './app/content-scripts/collection/collection.js', 16 | 17 | // background-scripts 18 | 'background-scripts/background.js': './app/background-scripts/background.js', 19 | 20 | // popup 21 | 'popup/popup.js': './app/popup/popup.js', 22 | 23 | // option 24 | 'option/option.js': './app/option/option.js', 25 | }, 26 | output: { 27 | filename: './extension/[name]' 28 | }, 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.css$/, 33 | use: [ 34 | 'style-loader', 35 | 'css-loader' 36 | ] 37 | }, 38 | { 39 | test: /\.scss$/, 40 | use: [ 41 | 'style-loader', 42 | 'css-loader', 43 | 'sass-loader' 44 | ] 45 | }, 46 | { 47 | test: /\.js$/, 48 | use: ['babel-loader'], 49 | exclude: /node_modules/ 50 | } 51 | ] 52 | }, 53 | plugins: [ 54 | new UglifyJSPlugin(), 55 | ] 56 | }; -------------------------------------------------------------------------------- /app/background-scripts/checkConversation.js: -------------------------------------------------------------------------------- 1 | // 对话详情 2 | export default function(message, sendResponse) { 3 | let {floorOwner, replyUser, topicId} = message 4 | let conversations = [] // 对话详情数据,sendResponse() 返回的数据 5 | // 去除缓存影响,但是 api 一小时只能调用 120 次 6 | let api = 'https://www.v2ex.com/api/replies/show.json?topic_id=' + topicId + '&rdm=' + (+new Date) 7 | 8 | fetch(api) 9 | .then(res => res.json()) 10 | .then(results => { 11 | const pattern = /@(.+?)<\/a>/g // 获取层主回复用户名 12 | 13 | // 遍历回复数据 14 | results.forEach(res => { 15 | let replyContent = res.content_rendered // 该楼层回复内容 16 | let matches = replyContent.match(pattern) // replyContent 中有几个 @ 17 | let _floorOwner = res.member.username // 层主 18 | let avatarsUrl = res.member.avatar_normal // 层主头像 19 | 20 | // replyContent 中有 >=1 个 @ 时 21 | if (matches && matches.length >= 1) { 22 | let _replyUser = [] // 层主回复的用户数组 23 | let matching; 24 | 25 | // 遍历,找到 @ 的用户,即层主回复的用户 26 | do { 27 | matching = pattern.exec(replyContent); 28 | if (matching) { 29 | _replyUser.push(matching[1]) 30 | } 31 | } while (matching !== null) 32 | 33 | if ((_floorOwner === floorOwner && _replyUser.includes(replyUser)) || 34 | (_floorOwner === replyUser && _replyUser.includes(floorOwner))) { 35 | conversations.push({from: _floorOwner, replyContent, avatarsUrl}) 36 | } 37 | } 38 | 39 | // 单纯的回复楼层(回复中没有 @) 40 | if (!matches) { 41 | if (( _floorOwner === floorOwner) || 42 | (_floorOwner === replyUser)) { 43 | conversations.push({from: _floorOwner, replyContent, avatarsUrl}) 44 | } 45 | } 46 | }) 47 | 48 | sendResponse({conversations: conversations}) 49 | }) 50 | } -------------------------------------------------------------------------------- /extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "V2EX HELPER", 3 | "manifest_version": 2, 4 | "version": "2.0.0", 5 | "description": "the way to make v2ex better", 6 | "icons": 7 | { 8 | "16": "icons/icon_16.png", 9 | "48": "icons/icon_48.png", 10 | "128": "icons/icon_128.png" 11 | }, 12 | "browser_action": { 13 | "default_title": "", 14 | "default_icon": { 15 | "19": "icons/icon_19.png", 16 | "38": "icons/icon_38.png" 17 | } 18 | }, 19 | 20 | "background": { 21 | "scripts": ["background-scripts/background.js"] 22 | }, 23 | 24 | "options_ui": { 25 | "page": "option/option.html" 26 | }, 27 | 28 | "permissions": [ 29 | "tabs", 30 | "storage", 31 | "notifications", 32 | "*://*.weibo.com/*" 33 | ], 34 | 35 | "content_scripts": [ 36 | { 37 | "matches": ["*://www.v2ex.com/notifications*"], 38 | "js": ["content-scripts/checkReply/insertCheckBtn.js"], 39 | "run_at": "document_end" 40 | }, 41 | { 42 | "matches": ["*://www.v2ex.com/t/*isJump=1*"], 43 | "js": ["content-scripts/checkReply/jump.js"], 44 | "run_at": "document_end" 45 | }, 46 | { 47 | "matches": ["*://www.v2ex.com/t/*"], 48 | "js": ["content-scripts/zoom/zoom.js"], 49 | "run_at": "document_end" 50 | }, 51 | { 52 | "matches": ["*://www.v2ex.com/new"], 53 | "js": [ 54 | "content-scripts/uploadImg/uploadImg.js" 55 | ], 56 | "run_at": "document_end" 57 | }, 58 | { 59 | "matches": ["*://www.v2ex.com/t/*"], 60 | "js": [ 61 | "content-scripts/checkConversation/checkConversation.js" 62 | ], 63 | "run_at": "document_end" 64 | }, 65 | { 66 | "matches": ["*://www.v2ex.com/t/*"], 67 | "js": [ 68 | "content-scripts/collection/collection.js" 69 | ], 70 | "run_at": "document_end" 71 | }, 72 | { 73 | "matches": ["*://www.v2ex.com/*"], 74 | "js": [ 75 | "content-scripts/signin/signin.js" 76 | ], 77 | "run_at": "document_end" 78 | } 79 | ], 80 | 81 | "web_accessible_resources": [""] 82 | } -------------------------------------------------------------------------------- /extension/manifest_with_comments.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "V2EX HELPER", 3 | "manifest_version": 2, 4 | "version": "2.0.0", 5 | "description": "the way to make v2ex better", 6 | 7 | // 一般情况下提供三种不同尺寸的图标 8 | // 16*16 48*48 128*128(webapp) 9 | "icons": 10 | { 11 | "16": "icons/icon_16.png", 12 | "48": "icons/icon_48.png" 13 | }, 14 | 15 | // 或者是 page_action 16 | "browser_action": { 17 | "default_title": "", 18 | "default_icon": { 19 | "19": "icons/icon_19.png", 20 | "38": "icons/icon_38.png" 21 | } 22 | // "default_popup": "popup/popup.html" 23 | }, 24 | 25 | "background": { 26 | // 或者设置项为 background.html,然后在它那引入 background.js 27 | "scripts": ["background-scripts/background.js"] 28 | }, 29 | 30 | "options_ui": { 31 | "page": "option/option.html" 32 | }, 33 | 34 | "permissions": [ 35 | "tabs", 36 | "storage", 37 | "notifications", 38 | // "*://*.v2ex.com/*" 39 | "*://*.weibo.com/*" // 跨域 40 | ], 41 | 42 | "content_scripts": [ 43 | { 44 | // 回复跳到指定楼层 45 | "matches": ["*://www.v2ex.com/notifications*"], 46 | "js": ["content-scripts/checkReply/insertCheckBtn.js"], 47 | // JS 的注入可以随便一点,但是 CSS 的注入就要千万小心了,因为一不小心就可能影响全局样式 48 | // 代码注入的时间,可选值: "document_start", "document_end", or "document_idle",最后一个表示页面空闲时,默认document_idle 49 | "run_at": "document_end" 50 | }, 51 | { 52 | "matches": ["*://www.v2ex.com/t/*isJump=1*"], 53 | "js": ["content-scripts/checkReply/jump.js"], 54 | "run_at": "document_end" 55 | }, 56 | { 57 | // zoom 58 | "matches": ["*://www.v2ex.com/t/*"], 59 | "js": ["content-scripts/zoom/zoom.js"], 60 | "run_at": "document_end" 61 | }, 62 | { 63 | // upload img in creating a new topic 64 | "matches": ["*://www.v2ex.com/new"], 65 | "js": [ 66 | "content-scripts/uploadImg/uploadImg.js" 67 | ], 68 | "run_at": "document_end" 69 | }, 70 | { 71 | // 对话详情 72 | "matches": ["*://www.v2ex.com/t/*"], 73 | "js": [ 74 | "content-scripts/checkConversation/checkConversation.js" 75 | ], 76 | "run_at": "document_end" 77 | }, 78 | { 79 | // 签到 80 | "matches": ["*://www.v2ex.com/*"], 81 | "js": [ 82 | "content-scripts/signin/signin.js" 83 | ], 84 | "run_at": "document_end" 85 | } 86 | ], 87 | 88 | "web_accessible_resources": [""] 89 | } -------------------------------------------------------------------------------- /app/content-scripts/uploadImg/uploadImg.js: -------------------------------------------------------------------------------- 1 | import './sass/uploadImg.scss' 2 | import {getSettingsAsync} from '../../settings/settings.js' 3 | // import 'babel-polyfill' 4 | 5 | // Add img when creating a new topic 6 | (async function () { 7 | let cfg = await getSettingsAsync() 8 | 9 | if (!cfg.cfg_uploadImg) return 10 | 11 | // append file upload btn and trigger btn 12 | document 13 | .getElementById('content_remaining') 14 | .parentNode 15 | .insertAdjacentHTML('beforeend', 16 | ` 17 | 18 | 19 | `) 20 | 21 | const uploadTriggerBtn = document.getElementById('uploadTriggerBtn') 22 | const uploadImgBtn = document.getElementById('uploadImgBtn') 23 | 24 | uploadTriggerBtn.addEventListener('click', () => { 25 | // TODO use CustomEvent 26 | uploadImgBtn.click() 27 | }) 28 | 29 | // add listener 30 | uploadImgBtn.addEventListener('change', function() { 31 | if (!this.files[0].type.includes('image')) { 32 | alert('请上传正确的图片格式文件 😄') 33 | return 34 | } 35 | 36 | if (this.files.length === 0) return 37 | 38 | // change trigger btn status 39 | uploadTriggerBtn.classList.add('not-allow') 40 | uploadTriggerBtn.innerHTML = ' 图片上传中...' 41 | 42 | let reader = new FileReader() 43 | 44 | reader.onload = () => { 45 | let res = reader.result 46 | let dataURL = res.split(',')[1] 47 | 48 | chrome.runtime.sendMessage({ 49 | method: 'uploadImgInTopic', 50 | dataURL: dataURL 51 | }, function(response) { 52 | if (response.status === 1) { 53 | alert('请先登录微博 😄') 54 | window.open("https://weibo.com/") 55 | location.reload() 56 | return 57 | } 58 | 59 | // change trigger btn status 60 | uploadTriggerBtn.classList.remove('not-allow') 61 | uploadTriggerBtn.innerHTML = ' 上传图片' 62 | 63 | // 需要获取页面的全局变量 editor 64 | // content_scripts 无法获取页面的全局变量 editor,改用 injected_scripts 65 | var script = document.createElement('script') 66 | script.innerHTML = ` 67 | var originVal = editor.getValue() 68 | editor.setValue( 69 | \`\$\{originVal\}![](${response.imgUrl}) 70 | 71 | \` 72 | ) 73 | ` 74 | document.body.appendChild(script) 75 | document.body.removeChild(script) 76 | }) 77 | } 78 | 79 | reader.readAsDataURL(this.files[0]) 80 | }) 81 | })() -------------------------------------------------------------------------------- /app/content-scripts/checkConversation/checkConversation.js: -------------------------------------------------------------------------------- 1 | import 'pure-css-loader/dist/css-loader.css' 2 | import './sass/checkConversation.scss' 3 | import 'tingle.js/dist/tingle.min.css' 4 | import tingle from 'tingle.js' 5 | import {getSettingsAsync} from '../../settings/settings.js' 6 | // import 'babel-polyfill' 7 | 8 | (async function() { 9 | let cfg = await getSettingsAsync() 10 | 11 | if (!cfg.cfg_checkConversation) return 12 | 13 | let replies = [].slice.call(document.querySelectorAll("div[id^='r_']")) 14 | 15 | replies.forEach((item, index) => { 16 | let floorOwner = item.querySelector('strong a').innerHTML // 层主 17 | let replyContent = item.querySelector('.reply_content').innerHTML 18 | const pattern = /@(.+?)<\/a>/g 19 | let matches = replyContent.match(pattern) 20 | 21 | // 只有一个 @ 时,才显示 「对话详情」 22 | if (!matches || matches.length !== 1) return 23 | 24 | // add checkConversation Btn 25 | item 26 | .querySelector('.reply_content') 27 | .previousElementSibling 28 | .insertAdjacentHTML('beforebegin', 29 | ` 30 | 对话详情`) 31 | 32 | // 层主回复的用户 33 | let replyUser = pattern.exec(replyContent)[1] 34 | 35 | item.querySelector('#checkConversationBtn').addEventListener('click', () => { 36 | // loading 37 | document.body.insertAdjacentHTML('beforeend', '
') 38 | 39 | chrome.runtime.sendMessage({ 40 | method: 'checkConversationBtn', 41 | floorOwner: floorOwner, 42 | replyUser: replyUser, 43 | topicId: /\/t\/(\d+)/.exec(location.href)[1] 44 | }, function(response) { // result array 45 | let html = '' 46 | response.conversations.forEach(item => { 47 | html += ` 48 |
49 |
50 | 51 | 52 | 53 |
54 |
55 |
56 | ${item.from} 57 |
58 |
59 | ${item.replyContent} 60 |
61 |
62 |
63 | ` 64 | }) 65 | 66 | // remove loading 67 | let loader = document.querySelector('body .loader') 68 | loader.parentNode.removeChild(loader) 69 | 70 | // show conversation modal 71 | var modal = new tingle.modal({ 72 | closeMethods: ['overlay', 'button', 'escape'] 73 | }) 74 | 75 | // set content 76 | modal.setContent(html) 77 | 78 | // open modal 79 | modal.open() 80 | }) 81 | }, false) 82 | }) 83 | })() -------------------------------------------------------------------------------- /extension/background-scripts/background.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function n(o){if(t[o])return t[o].exports;var c=t[o]={i:o,l:!1,exports:{}};return e[o].call(c.exports,c,c.exports,n),c.l=!0,c.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:o})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=21)}({0:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let o=["checkReply","zoom","uploadImg","checkConversation","signin","notificationsPopup","notificationsIconShowNum"];t.keys=o=o.map(e=>"cfg_"+e),t.options=["回复通知增强","主题贴图片点击放大","主题贴发图","对话详情","自动签到","新消息提醒(弹窗模式)","新消息提醒(图标提醒)"],t.keys=o,t.getSettingsAsync=function(){return new Promise(e=>{chrome.storage.sync.get(o,function(t){o.forEach(e=>{t[e]=!(void 0!==t[e]&&!t[e])}),e(t)})})}},21:function(e,t,n){"use strict";var o=s(n(22)),c=s(n(23)),r=s(n(24)),i=n(0);function s(e){return e&&e.__esModule?e:{default:e}}chrome.runtime.onMessage.addListener(function(e,t,n){switch(e.method){case"uploadImgInTopic":return(0,c.default)(e,n),!0;case"checkConversationBtn":return(0,o.default)(e,n),!0}}),async function(){let e=await(0,i.getSettingsAsync)();(0,r.default)(e)}()},22:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){let{floorOwner:n,replyUser:o,topicId:c}=e,r=[],i="https://www.v2ex.com/api/replies/show.json?topic_id="+c+"&rdm="+ +new Date;fetch(i).then(e=>e.json()).then(e=>{const c=/@(.+?)<\/a>/g;e.forEach(e=>{let t=e.content_rendered,i=t.match(c),s=e.member.username,a=e.member.avatar_normal;if(i&&i.length>=1){let e,i=[];do{(e=c.exec(t))&&i.push(e[1])}while(null!==e);(s===n&&i.includes(o)||s===o&&i.includes(n))&&r.push({from:s,replyContent:t,avatarsUrl:a})}i||s!==n&&s!==o||r.push({from:s,replyContent:t,avatarsUrl:a})}),t({conversations:r})})}},23:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){let n=new XMLHttpRequest,o=new FormData;o.append("b64_data",e.dataURL),n.onerror=(()=>{}),n.onload=function(){try{const e=/"pid":"(.+)"/;let o=n.responseText,c="https://ws2.sinaimg.cn/large/",r=o.match(e)[1];t({status:0,imgUrl:c+r})}catch(e){t({status:1})}},n.open("POST","http://picupload.service.weibo.com/interface/pic_upload.php? mime=image%2Fjpeg&data=base64&url=0"),n.send(o)}},24:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){chrome.browserAction.onClicked.addListener(()=>{chrome.browserAction.getBadgeText({},t=>{e.cfg_notificationsIconShowNum&&""!==t?(chrome.browserAction.setBadgeText({text:""}),window.open("https://www.v2ex.com/notifications")):window.open("https://www.v2ex.com")})}),(e.cfg_notificationsPopup||e.cfg_notificationsIconShowNum)&&setInterval(()=>{fetch("https://www.v2ex.com/",{credentials:"same-origin"}).then(e=>e.text()).then(t=>{const n=/(\d+) 条未读提醒/.exec(t);n&&"0"!==n[1]&&(e.cfg_notificationsPopup&&chrome.notifications.create(null,{type:"basic",iconUrl:"icons/icon_48.png",title:"from V2EX HELPER's Notification",message:`您有 ${n[1]} 条来自 V2EX 的新消息!`}),e.cfg_notificationsIconShowNum&&(chrome.browserAction.setBadgeText({text:n[1]}),chrome.browserAction.setBadgeBackgroundColor({color:[255,0,0,255]})))})},6e5)}}}); -------------------------------------------------------------------------------- /extension/content-scripts/zoom/zoom.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.m=e,o.c=t,o.d=function(e,t,n){o.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=4)}([function(e,t,o){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let n=["checkReply","zoom","uploadImg","checkConversation","signin","notificationsPopup","notificationsIconShowNum"];t.keys=n=n.map(e=>"cfg_"+e),t.options=["回复通知增强","主题贴图片点击放大","主题贴发图","对话详情","自动签到","新消息提醒(弹窗模式)","新消息提醒(图标提醒)"],t.keys=n,t.getSettingsAsync=function(){return new Promise(e=>{chrome.storage.sync.get(n,function(t){n.forEach(e=>{t[e]=!(void 0!==t[e]&&!t[e])}),e(t)})})}},,,,function(e,t,o){"use strict";var n,r=o(5),i=(n=r)&&n.__esModule?n:{default:n},d=o(0);!async function(){(await(0,d.getSettingsAsync)()).cfg_zoom&&(0,i.default)(document.querySelectorAll(".topic_content img"))}()},function(e,t,o){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n,r=Object.assign||function(e){for(var t,o=1;ok.scrollOffset&&y(150)}},b=function(e){o.includes(e.keyCode||e.which)&&y()},L=function(){var e=Math.min;if(C.template){var t=window.innerWidth,o=window.innerHeight,n=t-2*k.margin,r=o-2*k.margin,i=C.zoomedHd||C.template,d=i.naturalWidth,a=void 0===d?n:d,c=i.naturalHeight,m=void 0===c?r:c,l=i.getBoundingClientRect(),s=l.top,u=l.left,f=l.width,p=l.height,v=e(e(a,n)/f,e(m,r)/p)||1,y="scale("+v+") translate3d("+((n-f)/2-u+k.margin)/v+"px, "+((r-p)/2-s+k.margin)/v+"px, 0)";C.zoomed.style.transform=y,C.zoomedHd&&(C.zoomedHd.style.transform=y)}},k={margin:m,background:s,scrollOffset:f,metaClick:void 0===p||p};e instanceof Object&&r(k,e);var H=function(e){try{return Array.isArray(e)?e.filter(n):function(e){return NodeList.prototype.isPrototypeOf(e)||HTMLCollection.prototype.isPrototypeOf(e)}(e)?[].concat(i(e)).filter(n):function(e){return e&&1===e.nodeType}(e)?[e].filter(n):"string"==typeof e?[].concat(i(document.querySelectorAll(e))).filter(n):[].concat(i(document.querySelectorAll(t.map(function(e){return e.toLowerCase()}).join(",")))).filter(d)}catch(e){throw new TypeError("The provided selector is invalid.\nExpects a CSS selector, a Node element, a NodeList, an HTMLCollection or an array.\nSee: https://github.com/francoischalifour/medium-zoom")}}(e),x=function(e){var t=document.createElement("div");return t.classList.add("medium-zoom-overlay"),t.style.backgroundColor=e,t}(k.background),C={template:null,zoomed:null,zoomedHd:null},O=0,A=!1;return H.forEach(function(e){e.classList.add("medium-zoom-image"),e.addEventListener("click",h)}),x.addEventListener("click",y),document.addEventListener("scroll",w),document.addEventListener("keyup",b),window.addEventListener("resize",y),{show:g,hide:y,toggle:g,update:function(){var e=0"cfg_"+e),t.options=["回复通知增强","主题贴图片点击放大","主题贴发图","对话详情","自动签到","新消息提醒(弹窗模式)","新消息提醒(图标提醒)"],t.keys=r,t.getSettingsAsync=function(){return new Promise(e=>{chrome.storage.sync.get(r,function(t){r.forEach(e=>{t[e]=!(void 0!==t[e]&&!t[e])}),e(t)})})}},function(e,t){e.exports=function(e){var t=[];return t.toString=function(){return this.map(function(t){var n=function(e,t){var n=e[1]||"",r=e[3];if(!r)return n;if(t&&"function"==typeof btoa){var o=(a=r,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(a))))+" */"),i=r.sources.map(function(e){return"/*# sourceURL="+r.sourceRoot+e+" */"});return[n].concat(i).concat([o]).join("\n")}var a;return[n].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+n+"}":n}).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var r={},o=0;o=0&&l.splice(t,1)}function v(e){var t=document.createElement("style");return e.attrs.type="text/css",g(t,e.attrs),h(e,t),t}function g(e,t){Object.keys(t).forEach(function(n){e.setAttribute(n,t[n])})}function y(e,t){var n,r,o,i;if(t.transform&&e.css){if(!(i=t.transform(e.css)))return function(){};e.css=i}if(t.singleton){var a=u++;n=c||(c=v(t)),r=L.bind(null,n,a,!1),o=L.bind(null,n,a,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=function(e){var t=document.createElement("link");return e.attrs.type="text/css",e.attrs.rel="stylesheet",g(t,e.attrs),h(e,t),t}(t),r=function(e,t,n){var r=n.css,o=n.sourceMap,i=void 0===t.convertToAbsoluteUrls&&o;(t.convertToAbsoluteUrls||i)&&(r=f(r));o&&(r+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(o))))+" */");var a=new Blob([r],{type:"text/css"}),s=e.href;e.href=URL.createObjectURL(a),s&&URL.revokeObjectURL(s)}.bind(null,n,t),o=function(){m(n),n.href&&URL.revokeObjectURL(n.href)}):(n=v(t),r=function(e,t){var n=t.css,r=t.media;r&&e.setAttribute("media",r);if(e.styleSheet)e.styleSheet.cssText=n;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(n))}}.bind(null,n),o=function(){m(n)});return r(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;r(e=t)}else o()}}e.exports=function(e,t){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(t=t||{}).attrs="object"==typeof t.attrs?t.attrs:{},t.singleton||"boolean"==typeof t.singleton||(t.singleton=a()),t.insertInto||(t.insertInto="head"),t.insertAt||(t.insertAt="bottom");var n=p(e,t);return d(n,t),function(e){for(var r=[],o=0;o(.+?)<\/a>/g;let a=n.match(r);if(!a||1!==a.length)return;t.querySelector(".reply_content").previousElementSibling.insertAdjacentHTML("beforebegin",'\n 对话详情');let l=r.exec(n)[1];t.querySelector("#checkConversationBtn").addEventListener("click",()=>{document.body.insertAdjacentHTML("beforeend",'
'),chrome.runtime.sendMessage({method:"checkConversationBtn",floorOwner:o,replyUser:l,topicId:/\/t\/(\d+)/.exec(location.href)[1]},function(t){let e="";t.conversations.forEach(t=>{e+=`\n
\n
\n \n \n \n
\n
\n
\n ${t.from}\n
\n
\n ${t.replyContent}\n
\n
\n
\n `});let o=document.querySelector("body .loader");o.parentNode.removeChild(o);var n=new i.default.modal({closeMethods:["overlay","button","escape"]});n.setContent(e),n.open()})},!1)})}()},function(t,e,o){var n=o(14);"string"==typeof n&&(n=[[t.i,n,""]]);var r={hmr:!0,transform:void 0};o(2)(n,r);n.locals&&(t.exports=n.locals)},function(t,e,o){(t.exports=o(1)(!1)).push([t.i,'.loader{color:#fff;position:fixed;box-sizing:border-box;left:-9999px;top:-9999px;width:0;height:0;overflow:hidden;z-index:999999}.loader:after,.loader:before{box-sizing:border-box;display:none}.loader.is-active{background-color:rgba(0,0,0,.85);width:100%;height:100%;left:0;top:0}.loader.is-active:after,.loader.is-active:before{display:block}@keyframes rotation{0%{transform:rotate(0)}to{transform:rotate(359deg)}}@keyframes blink{0%{opacity:.5}to{opacity:1}}.loader[data-text]:before{position:fixed;left:0;top:50%;color:currentColor;font-family:Helvetica,Arial,sans-serif;text-align:center;width:100%;font-size:14px}.loader[data-text=""]:before{content:"Loading"}.loader[data-text]:not([data-text=""]):before{content:attr(data-text)}.loader[data-text][data-blink]:before{animation:blink 1s linear infinite alternate}.loader-default[data-text]:before{top:calc(50% - 63px)}.loader-default:after{content:"";position:fixed;width:48px;height:48px;border:8px solid #fff;border-left-color:transparent;border-radius:50%;top:calc(50% - 24px);left:calc(50% - 24px);animation:rotation 1s linear infinite}.loader-default[data-half]:after{border-right-color:transparent}.loader-default[data-inverse]:after{animation-direction:reverse}.loader-double:after,.loader-double:before{content:"";position:fixed;border-radius:50%;border:8px solid;animation:rotation 1s linear infinite}.loader-double:after{width:48px;height:48px;border-color:#fff;border-left-color:transparent;top:calc(50% - 24px);left:calc(50% - 24px)}.loader-double:before{width:64px;height:64px;border-color:#eb974e;border-right-color:transparent;animation-duration:2s;top:calc(50% - 32px);left:calc(50% - 32px)}.loader-bar[data-text]:before{top:calc(50% - 40px);color:#fff}.loader-bar:after{content:"";position:fixed;top:50%;left:50%;width:200px;height:20px;transform:translate(-50%,-50%);background:linear-gradient(-45deg,#4183d7 25%,#52b3d9 0,#52b3d9 50%,#4183d7 0,#4183d7 75%,#52b3d9 0,#52b3d9);background-size:20px 20px;box-shadow:inset 0 10px 0 hsla(0,0%,100%,.2),0 0 0 5px rgba(0,0,0,.2);animation:moveBar 1.5s linear infinite reverse}.loader-bar[data-rounded]:after{border-radius:15px}.loader-bar[data-inverse]:after{animation-direction:normal}@keyframes moveBar{0%{background-position:0 0}to{background-position:20px 20px}}.loader-bar-ping-pong:before{width:200px;background-color:#000}.loader-bar-ping-pong:after,.loader-bar-ping-pong:before{content:"";height:20px;position:absolute;top:calc(50% - 10px);left:calc(50% - 100px)}.loader-bar-ping-pong:after{width:50px;background-color:#f19;animation:moveBarPingPong .5s linear infinite alternate}.loader-bar-ping-pong[data-rounded]:before{border-radius:10px}.loader-bar-ping-pong[data-rounded]:after{border-radius:50%;width:20px;animation-name:moveBarPingPongRounded}@keyframes moveBarPingPong{0%{left:calc(50% - 100px)}to{left:calc(50% - -50px)}}@keyframes moveBarPingPongRounded{0%{left:calc(50% - 100px)}to{left:calc(50% - -80px)}}@keyframes corners{6%{width:60px;height:15px}25%{width:15px;height:15px;left:calc(100% - 15px);top:0}31%{height:60px}50%{height:15px;top:calc(100% - 15px);left:calc(100% - 15px)}56%{width:60px}75%{width:15px;left:0;top:calc(100% - 15px)}81%{height:60px}}.loader-border[data-text]:before{color:#fff}.loader-border:after{content:"";position:absolute;top:0;left:0;width:15px;height:15px;background-color:#ff0;animation:corners 3s ease both infinite}.loader-ball:before{content:"";position:absolute;width:50px;height:50px;top:50%;left:50%;margin:-25px 0 0 -25px;background-color:#fff;border-radius:50%;z-index:1;animation:kick 1s infinite alternate ease-in both}.loader-ball[data-shadow]:before{box-shadow:inset -5px -5px 10px 0 rgba(0,0,0,.5)}.loader-ball:after{content:"";position:absolute;background-color:rgba(0,0,0,.3);border-radius:50%;width:45px;height:20px;top:calc(50% + 10px);left:50%;margin:0 0 0 -22.5px;z-index:0;animation:shadow 1s infinite alternate ease-out both}@keyframes shadow{0%{background-color:transparent;transform:scale(0)}40%{background-color:transparent;transform:scale(0)}95%{background-color:rgba(0,0,0,.75);transform:scale(1)}to{background-color:rgba(0,0,0,.75);transform:scale(1)}}@keyframes kick{0%{transform:translateY(-80px) scaleX(.95)}90%{border-radius:50%}to{transform:translateY(0) scaleX(1);border-radius:50% 50% 20% 20%}}.loader-smartphone:after{content:"";color:#fff;font-size:12px;font-family:Helvetica,Arial,sans-serif;text-align:center;line-height:120px;position:fixed;left:50%;top:50%;width:70px;height:130px;margin:-65px 0 0 -35px;border:5px solid #fd0;border-radius:10px;box-shadow:inset 0 5px 0 0 #fd0;background:radial-gradient(circle at 50% 90%,rgba(0,0,0,.5) 6px,transparent 0),linear-gradient(0deg,#fd0 22px,transparent 0),linear-gradient(0deg,rgba(0,0,0,.5) 22px,rgba(0,0,0,.5));animation:shake 2s cubic-bezier(.36,.07,.19,.97) both infinite}.loader-smartphone[data-screen=""]:after{content:"Loading"}.loader-smartphone:not([data-screen=""]):after{content:attr(data-screen)}@keyframes shake{5%{transform:translate3d(-1px,0,0)}10%{transform:translate3d(1px,0,0)}15%{transform:translate3d(-1px,0,0)}20%{transform:translate3d(1px,0,0)}25%{transform:translate3d(-1px,0,0)}30%{transform:translate3d(1px,0,0)}35%{transform:translate3d(-1px,0,0)}40%{transform:translate3d(1px,0,0)}45%{transform:translate3d(-1px,0,0)}50%{transform:translate3d(1px,0,0)}55%{transform:translate3d(-1px,0,0)}}.loader-clock:before{width:120px;height:120px;border-radius:50%;margin:-60px 0 0 -60px;background:linear-gradient(180deg,transparent 50%,#f5f5f5 0),linear-gradient(90deg,transparent 55px,#2ecc71 0,#2ecc71 65px,transparent 0),linear-gradient(180deg,#f5f5f5 50%,#f5f5f5 0);box-shadow:inset 0 0 0 10px #f5f5f5,0 0 0 5px #555,0 0 0 10px #7b7b7b;animation:rotation infinite 2s linear}.loader-clock:after,.loader-clock:before{content:"";position:fixed;left:50%;top:50%;overflow:hidden}.loader-clock:after{width:60px;height:40px;margin:-20px 0 0 -15px;border-radius:20px 0 0 20px;background:radial-gradient(circle at 14px 20px,#25a25a 10px,transparent 0),radial-gradient(circle at 14px 20px,#1b7943 14px,transparent 0),linear-gradient(180deg,transparent 15px,#2ecc71 0,#2ecc71 25px,transparent 0);animation:rotation infinite 24s linear;transform-origin:15px center}.loader-curtain:after,.loader-curtain:before{position:fixed;width:100%;top:50%;margin-top:-35px;font-size:70px;text-align:center;font-family:Helvetica,Arial,sans-serif;overflow:hidden;line-height:1.2;content:"Loading"}.loader-curtain:before{color:#666}.loader-curtain:after{color:#fff;height:0;animation:curtain 1s linear infinite alternate both}.loader-curtain[data-curtain-text]:not([data-curtain-text=""]):after,.loader-curtain[data-curtain-text]:not([data-curtain-text=""]):before{content:attr(data-curtain-text)}.loader-curtain[data-brazilian]:before{color:#f1c40f}.loader-curtain[data-brazilian]:after{color:#2ecc71}.loader-curtain[data-colorful]:before{animation:maskColorful 2s linear infinite alternate both}.loader-curtain[data-colorful]:after{animation:curtain 1s linear infinite alternate both,maskColorful-front 2s 1s linear infinite alternate both;color:#000}@keyframes maskColorful{0%{color:#3498db}49.5%{color:#3498db}50.5%{color:#e74c3c}to{color:#e74c3c}}@keyframes maskColorful-front{0%{color:#2ecc71}49.5%{color:#2ecc71}50.5%{color:#f1c40f}to{color:#f1c40f}}@keyframes curtain{0%{height:0}to{height:84px}}.loader-music:after,.loader-music:before{content:"";position:fixed;width:240px;height:240px;top:50%;left:50%;margin:-120px 0 0 -120px;border-radius:50%;text-align:center;line-height:240px;color:#fff;font-size:40px;font-family:Helvetica,Arial,sans-serif;text-shadow:1px 1px 0 rgba(0,0,0,.5);letter-spacing:-1px}.loader-music:after{backface-visibility:hidden}.loader-music[data-hey-oh]:after,.loader-music[data-hey-oh]:before{box-shadow:0 0 0 10px}.loader-music[data-hey-oh]:before{background-color:#fff;color:#000;animation:coinBack 2.5s linear infinite,oh 5s 1.25s linear infinite both}.loader-music[data-hey-oh]:after{background-color:#000;animation:coin 2.5s linear infinite,hey 5s linear infinite both}.loader-music[data-no-cry]:after,.loader-music[data-no-cry]:before{background:linear-gradient(45deg,#009b3a 50%,#fed100 51%);box-shadow:0 0 0 10px #000}.loader-music[data-no-cry]:before{animation:coinBack 2.5s linear infinite,cry 5s 1.25s linear infinite both}.loader-music[data-no-cry]:after{animation:coin 2.5s linear infinite,no 5s linear infinite both}.loader-music[data-we-are]:before{animation:coinBack 2.5s linear infinite,theWorld 5s 1.25s linear infinite both;background:radial-gradient(ellipse at center,#4ecdc4 0,#556270)}.loader-music[data-we-are]:after{animation:coin 2.5s linear infinite,weAre 5s linear infinite both;background:radial-gradient(ellipse at center,#26d0ce 0,#1a2980)}.loader-music[data-rock-you]:before{animation:coinBack 2.5s linear infinite,rockYou 5s 1.25s linear infinite both;background:#444}.loader-music[data-rock-you]:after{animation:coin 2.5s linear infinite,weWill 5s linear infinite both;background:#96281b}@keyframes coin{to{transform:rotateY(359deg)}}@keyframes coinBack{0%{transform:rotateY(180deg)}50%{transform:rotateY(1turn)}to{transform:rotateY(180deg)}}@keyframes hey{0%{content:"Hey!"}50%{content:"Let\'s!"}to{content:"Hey!"}}@keyframes oh{0%{content:"Oh!"}50%{content:"Go!"}to{content:"Oh!"}}@keyframes no{0%{content:"No..."}50%{content:"no"}to{content:"No..."}}@keyframes cry{0%{content:"woman"}50%{content:"cry!"}to{content:"woman"}}@keyframes weAre{0%{content:"We are"}50%{content:"we are"}to{content:"We are"}}@keyframes theWorld{0%{content:"the world,"}50%{content:"the children!"}to{content:"the world,"}}@keyframes weWill{0%{content:"We will,"}50%{content:"rock you!"}to{content:"We will,"}}@keyframes rockYou{0%{content:"we will"}50%{content:"\\1F918"}to{content:"we will"}}.loader-pokeball:before{content:"";position:absolute;width:100px;height:100px;top:50%;left:50%;margin:-50px 0 0 -50px;background:linear-gradient(180deg,red 42%,#000 0,#000 58%,#fff 0);background-repeat:no-repeat;background-color:#fff;border-radius:50%;z-index:1;animation:movePokeball 1s linear infinite both}.loader-pokeball:after{content:"";position:absolute;width:24px;height:24px;top:50%;left:50%;margin:-12px 0 0 -12px;background-color:#fff;border-radius:50%;z-index:2;animation:movePokeball 1s linear infinite both,flashPokeball .5s infinite alternate;border:2px solid #000;box-shadow:0 0 0 5px #fff,0 0 0 10px #000}@keyframes movePokeball{0%{transform:translateX(0) rotate(0)}15%{transform:translatex(-10px) rotate(-5deg)}30%{transform:translateX(10px) rotate(5deg)}45%{transform:translatex(0) rotate(0)}}@keyframes flashPokeball{0%{background-color:#fff}to{background-color:#fd0}}',""])},function(t,e,o){var n=o(16);"string"==typeof n&&(n=[[t.i,n,""]]);var r={hmr:!0,transform:void 0};o(2)(n,r);n.locals&&(t.exports=n.locals)},function(t,e,o){(t.exports=o(1)(!1)).push([t.i,'@charset "UTF-8";\n/* www.v2ex.com/t/ */\n/* 对话详情 */\n#checkConversationBtn {\n padding-left: 10px;\n color: #ccc;\n font-size: 12px; }\n #checkConversationBtn:hover {\n opacity: .7; }\n #checkConversationBtn > i {\n padding-right: 4px; }\n\n.tingle-modal-box__content {\n max-width: 800px;\n padding: 10px !important;\n line-height: 1.6 !important;\n word-break: break-all;\n word-wrap: break-word; }\n .tingle-modal-box__content > .cell {\n overflow: hidden; }\n .tingle-modal-box__content > .cell > .left {\n float: left; }\n .tingle-modal-box__content > .cell > .left img {\n border-radius: 4px; }\n .tingle-modal-box__content > .cell > .right {\n margin-left: 60px; }\n .tingle-modal-box__content > .cell > .right > .author {\n color: gray;\n margin-bottom: 10px; }\n',""])},function(t,e,o){var n=o(18);"string"==typeof n&&(n=[[t.i,n,""]]);var r={hmr:!0,transform:void 0};o(2)(n,r);n.locals&&(t.exports=n.locals)},function(t,e,o){(t.exports=o(1)(!1)).push([t.i,'.tingle-modal *{box-sizing:border-box}.tingle-modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;display:-webkit-box;display:-ms-flexbox;display:flex;visibility:hidden;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;overflow:hidden;background:rgba(0,0,0,.8);opacity:0;cursor:pointer;-webkit-transition:-webkit-transform .2s ease;transition:-webkit-transform .2s ease;transition:transform .2s ease;transition:transform .2s ease,-webkit-transform .2s ease}.tingle-modal--noClose .tingle-modal__close,.tingle-modal__closeLabel{display:none}.tingle-modal--confirm .tingle-modal-box{text-align:center}.tingle-modal--noOverlayClose{cursor:default}.tingle-modal__close{position:fixed;top:10px;right:28px;z-index:1000;padding:0;width:5rem;height:5rem;border:none;background-color:transparent;color:#f0f0f0;font-size:6rem;font-family:monospace;line-height:1;cursor:pointer;-webkit-transition:color .3s ease;transition:color .3s ease}.tingle-modal__close:hover{color:#fff}.tingle-modal-box{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:auto;margin-bottom:auto;width:60%;border-radius:4px;background:#fff;opacity:1;cursor:auto;-webkit-transition:-webkit-transform .3s cubic-bezier(.175,.885,.32,1.275);transition:-webkit-transform .3s cubic-bezier(.175,.885,.32,1.275);transition:transform .3s cubic-bezier(.175,.885,.32,1.275);transition:transform .3s cubic-bezier(.175,.885,.32,1.275),-webkit-transform .3s cubic-bezier(.175,.885,.32,1.275);-webkit-transform:scale(.8);-ms-transform:scale(.8);transform:scale(.8)}.tingle-modal-box__content{padding:3rem}.tingle-modal-box__footer{padding:1.5rem 2rem;width:auto;border-bottom-right-radius:4px;border-bottom-left-radius:4px;background-color:#f5f5f5;cursor:auto}.tingle-modal-box__footer::after{display:table;clear:both;content:""}.tingle-modal-box__footer--sticky{position:fixed;bottom:-200px;z-index:10001;opacity:1;-webkit-transition:bottom .3s ease-in-out .3s;transition:bottom .3s ease-in-out .3s}.tingle-enabled{overflow:hidden;height:100%}.tingle-modal--visible .tingle-modal-box__footer{bottom:0}.tingle-enabled .tingle-content-wrapper{-webkit-filter:blur(15px);filter:blur(15px)}.tingle-modal--visible{visibility:visible;opacity:1}.tingle-modal--visible .tingle-modal-box{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}.tingle-modal--overflow{overflow-y:scroll;padding-top:8vh}.tingle-btn{display:inline-block;margin:0 .5rem;padding:1rem 2rem;border:none;background-color:grey;box-shadow:none;color:#fff;vertical-align:middle;text-decoration:none;font-size:inherit;font-family:inherit;line-height:normal;cursor:pointer;-webkit-transition:background-color .4s ease;transition:background-color .4s ease}.tingle-btn--primary{background-color:#3498db}.tingle-btn--danger{background-color:#e74c3c}.tingle-btn--default{background-color:#34495e}.tingle-btn--pull-left{float:left}.tingle-btn--pull-right{float:right}@media (max-width :540px){.tingle-modal{top:0;display:block;padding-top:60px;width:100%}.tingle-modal-box{width:auto;border-radius:0}.tingle-modal-box__content{overflow-y:scroll}.tingle-modal--noClose{top:0}.tingle-modal--noOverlayClose{padding-top:0}.tingle-modal-box__footer .tingle-btn{display:block;float:none;margin-bottom:1rem;width:100%}.tingle-modal__close{top:0;right:0;left:0;display:block;width:100%;height:60px;border:none;background-color:#2c3e50;box-shadow:none;color:#fff;line-height:55px}.tingle-modal__closeLabel{display:inline-block;vertical-align:middle;font-size:1.5rem;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",sans-serif}.tingle-modal__closeIcon{display:inline-block;margin-right:.5rem;vertical-align:middle;font-size:4rem}}',""])},function(t,e,o){var n,r;void 0===(r="function"==typeof(n=function(){function t(t){this.opts=function(){for(var t=1;t=t},t.prototype.checkOverflow=function(){this.modal.classList.contains("tingle-modal--visible")&&(this.isOverflow()?this.modal.classList.add("tingle-modal--overflow"):this.modal.classList.remove("tingle-modal--overflow"),!this.isOverflow()&&this.opts.stickyFooter?this.setStickyFooter(!1):this.isOverflow()&&this.opts.stickyFooter&&(e.call(this),this.setStickyFooter(!0)))},{modal:t}})?n.call(e,o,e,t):n)||(t.exports=r)}]); --------------------------------------------------------------------------------