├── .eslintignore
├── _config.yml
├── icon.psd
├── static
├── icons
│ ├── 16.png
│ ├── 19.png
│ ├── 38.png
│ ├── 64.png
│ └── 128.png
└── assets
│ └── download.css
├── .github
├── assets
│ ├── icon.png
│ ├── promo1.ai
│ ├── promo1.png
│ ├── click_icon1.png
│ ├── click_icon2.png
│ ├── screenshot1.png
│ ├── screenshot2.png
│ ├── screenshot3.png
│ ├── small_promo.jpg
│ ├── small_promo.psd
│ └── tryitnowbutton_small.png
└── CONTRIBUTING.md
├── assets
└── background-1200x630.ai
├── src
├── tab
│ ├── classes
│ │ ├── User.js
│ │ ├── Threads.js
│ │ └── Thread.js
│ ├── router
│ │ └── index.js
│ ├── components
│ │ ├── download
│ │ │ ├── Html.vue
│ │ │ ├── Thread.vue
│ │ │ ├── Event.vue
│ │ │ └── Message.vue
│ │ ├── ListPage.vue
│ │ ├── BarChart.js
│ │ ├── ThreadList
│ │ │ ├── DetailTemplate
│ │ │ │ ├── EmojiChooserDropdown.vue
│ │ │ │ ├── ColorChooserDropdown.vue
│ │ │ │ ├── MuteUntil.vue
│ │ │ │ ├── Chooser.vue
│ │ │ │ └── index.vue
│ │ │ ├── OperationButton.vue
│ │ │ ├── NameTemplate
│ │ │ │ ├── index.vue
│ │ │ │ └── ThreadName.vue
│ │ │ ├── Avatar.vue
│ │ │ └── index.vue
│ │ ├── AboutPage.vue
│ │ ├── SharingDialog.vue
│ │ └── ChartPage.vue
│ ├── README.md
│ ├── lib
│ │ ├── shareOnFb.js
│ │ ├── downloadMessages.js
│ │ ├── fetchThread.js
│ │ ├── generateCanvas.js
│ │ ├── fetchThreads.js
│ │ ├── changeThreadSetting.js
│ │ ├── util.js
│ │ └── fetchThreadDetail.js
│ ├── root.vue
│ └── index.js
├── content
│ └── index.js
├── options
│ ├── index.js
│ └── root.vue
├── ext
│ ├── storage.js
│ └── Indexeddb.js
├── manifest.js
├── backend
│ └── index.js
└── _locales
│ ├── zh_TW
│ └── messages.js
│ └── en
│ └── messages.js
├── .stickler.yml
├── .postcssrc.js
├── .gitignore
├── element-variables.scss
├── .babelrc
├── core
├── .env.example.js
├── page.ejs
├── webpack.dev.js
├── webpack.beta.js
├── webpack.prod.js
├── deploy.js
├── tools.js
└── webpack.base.js
├── .eslintrc.js
├── plugins
└── GenerateLocaleJsonPlugin.js
├── README-zh-TW.md
├── package.json
└── README.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/icon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/icon.psd
--------------------------------------------------------------------------------
/static/icons/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/static/icons/16.png
--------------------------------------------------------------------------------
/static/icons/19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/static/icons/19.png
--------------------------------------------------------------------------------
/static/icons/38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/static/icons/38.png
--------------------------------------------------------------------------------
/static/icons/64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/static/icons/64.png
--------------------------------------------------------------------------------
/static/icons/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/static/icons/128.png
--------------------------------------------------------------------------------
/.github/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/.github/assets/icon.png
--------------------------------------------------------------------------------
/.github/assets/promo1.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/.github/assets/promo1.ai
--------------------------------------------------------------------------------
/.github/assets/promo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/.github/assets/promo1.png
--------------------------------------------------------------------------------
/assets/background-1200x630.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/assets/background-1200x630.ai
--------------------------------------------------------------------------------
/.github/assets/click_icon1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/.github/assets/click_icon1.png
--------------------------------------------------------------------------------
/.github/assets/click_icon2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/.github/assets/click_icon2.png
--------------------------------------------------------------------------------
/.github/assets/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/.github/assets/screenshot1.png
--------------------------------------------------------------------------------
/.github/assets/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/.github/assets/screenshot2.png
--------------------------------------------------------------------------------
/.github/assets/screenshot3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/.github/assets/screenshot3.png
--------------------------------------------------------------------------------
/.github/assets/small_promo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/.github/assets/small_promo.jpg
--------------------------------------------------------------------------------
/.github/assets/small_promo.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/.github/assets/small_promo.psd
--------------------------------------------------------------------------------
/src/tab/classes/User.js:
--------------------------------------------------------------------------------
1 | export default class User {
2 | constructor (data) {
3 | Object.assign(this, data)
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.github/assets/tryitnowbutton_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ALiangLiang/Counter-for-Messenger/HEAD/.github/assets/tryitnowbutton_small.png
--------------------------------------------------------------------------------
/src/content/index.js:
--------------------------------------------------------------------------------
1 | // submit a message about submit. means "login"
2 | document.addEventListener('submit', function () {
3 | chrome.runtime.sendMessage({})
4 | })
5 |
--------------------------------------------------------------------------------
/.stickler.yml:
--------------------------------------------------------------------------------
1 | linters:
2 | eslint:
3 | config: .eslintrc.js
4 | fixer: true
5 | files:
6 | ignore:
7 | - 'bower_components/*'
8 | - 'node_modules/*'
9 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | // to edit target browsers: use "browserslist" field in package.json
6 | "autoprefixer": {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # env
2 | core/.env.*
3 | !core/.env.example.*
4 |
5 | # IDE
6 | .vscode
7 |
8 | # dependencies
9 | node_modules
10 | package-lock.json
11 |
12 | # logs
13 | npm-debug.log
14 | yarn-error.log
15 |
16 | # Backpack build
17 | build
18 |
19 | # package zip
20 | beta-*.zip
21 | release-*.zip
22 |
--------------------------------------------------------------------------------
/element-variables.scss:
--------------------------------------------------------------------------------
1 | /* theme color */
2 | $--color-primary: #0084ff !default;
3 | $--color-success: #4bcc1f !default;
4 | $--color-danger: #f03c24 !default;
5 |
6 | /* icon font path, required */
7 | $--font-path: '~element-ui/lib/theme-chalk/fonts';
8 |
9 | @import "~element-ui/packages/theme-chalk/src/index";
10 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }]
9 | ],
10 | "plugins": ["@babel/transform-runtime", "transform-vue-jsx"],
11 | "env": {
12 | "test": {
13 | "presets": ["@babel/preset-env"],
14 | "plugins": ["istanbul"]
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/tab/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import AboutPage from '../components/AboutPage'
4 | import ListPage from '../components/ListPage'
5 | import ChartPage from '../components/ChartPage'
6 |
7 | Vue.use(Router)
8 |
9 | /* eslint-disable no-multi-spaces */
10 | export default new Router({
11 | routes: [
12 | { path: '/', redirect: '/list' },
13 | { path: '/about', name: 'About', component: AboutPage },
14 | { path: '/list', name: 'ListPage', component: ListPage },
15 | { path: '/chart', name: 'ChartPage', component: ChartPage }
16 | ]
17 | })
18 |
--------------------------------------------------------------------------------
/src/options/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import { Form, FormItem, Switch } from 'element-ui'
3 | import 'element-ui/lib/theme-chalk/index.css'
4 | import enLocale from 'element-ui/lib/locale/lang/en'
5 | import zhLocale from 'element-ui/lib/locale/lang/zh-TW'
6 | import locale from 'element-ui/lib/locale'
7 | import root from './root.vue'
8 |
9 | Vue.config.productionTip = false
10 |
11 | const mainLangName = chrome.i18n.getUILanguage().split('-')[0]
12 | locale.use((mainLangName === 'zh') ? zhLocale : enLocale)
13 |
14 | Vue.use(Form, { locale })
15 | Vue.use(FormItem, { locale })
16 | Vue.use(Switch, { locale })
17 |
18 | /* eslint-disable no-new */
19 | new Vue({
20 | el: '#root',
21 | render: h => h(root)
22 | })
23 |
--------------------------------------------------------------------------------
/core/.env.example.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | release:{
3 | extensionId: 'ecnglinljpjkbgmdpeiglonddahpbkea',
4 | clientId: 'xxxxxxxxxx',
5 | clientSecret: 'xxxxxxxxxx',
6 | refreshToken: 'xxxxxxxxxx',
7 | key: 'fasdaklgflgmfbwehfebfhabonsdn' // Optional
8 | },
9 | beta:{
10 | extensionId: 'ecnglinljpjkbgmdpeiglonddahpbkeb',
11 | clientId: 'xxxxxxxxxx',
12 | clientSecret: 'xxxxxxxxxx',
13 | refreshToken: 'xxxxxxxxxx',
14 | key: 'fasdaklgflgmfbwehfebfhabonsdn' // Optional
15 | },
16 | fb: {
17 | id: '12346578901234657890',
18 | version: 'v2.12',
19 | domain: 'chrome.google.com',
20 | website: 'https://chrome.google.com/webstore/detail/ecnglinljpjkbgmdpeiglonddahpbkeb/'
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/ext/storage.js:
--------------------------------------------------------------------------------
1 | export default {
2 | get (key, defaultVal) {
3 | try {
4 | const result = JSON.parse(localStorage.getItem(key))
5 | if (result) {
6 | return result
7 | } else {
8 | localStorage.setItem(key, JSON.stringify(defaultVal))
9 | return defaultVal
10 | }
11 | } catch (e) {
12 | localStorage.setItem(key, JSON.stringify(defaultVal))
13 | return defaultVal
14 | }
15 | },
16 | set (key, val) {
17 | try {
18 | localStorage.setItem(key, JSON.stringify(val))
19 | } catch (e) {}
20 | },
21 | remove (key) {
22 | try {
23 | localStorage.removeItem(key)
24 | } catch (e) {}
25 | },
26 | clear () {
27 | try {
28 | localStorage.clear()
29 | } catch (e) {}
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/tab/components/download/Html.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ title }}
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
26 |
--------------------------------------------------------------------------------
/src/tab/components/ListPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
31 |
32 |
38 |
39 |
46 |
--------------------------------------------------------------------------------
/src/tab/components/BarChart.js:
--------------------------------------------------------------------------------
1 | import { HorizontalBar, mixins } from 'vue-chartjs'
2 | const { reactiveProp } = mixins
3 |
4 | export default {
5 | extends: HorizontalBar,
6 |
7 | mixins: [reactiveProp],
8 |
9 | props: ['chartData', 'options'],
10 |
11 | data () {
12 | return {
13 | canvas: this.$refs.canvas
14 | }
15 | },
16 |
17 | mounted () {
18 | this.canvas = this.$refs.canvas
19 | this.renderChart(this.chartData, this.options)
20 | },
21 |
22 | watch: {
23 | // By default, options are not reactive. So create a watcher for options.title.text
24 | // ref: https://github.com/apertureless/vue-chartjs/issues/106#issuecomment-299782906
25 | 'options.title.text' () {
26 | this.$data._chart.destroy()
27 | this.renderChart(this.chartData, this.options)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/tab/components/download/Thread.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
28 |
--------------------------------------------------------------------------------
/src/tab/components/download/Event.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ messageData.snippet }}
5 |
6 |
7 |
8 |
9 |
26 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parser: 'vue-eslint-parser',
6 | parserOptions: {
7 | parser: "babel-eslint",
8 | ecmaVersion: 2017,
9 | sourceType: 'module'
10 | },
11 | globals: {
12 | chrome: true
13 | // chrome: true
14 | },
15 | env: {
16 | browser: true,
17 | },
18 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md
19 | extends: ['plugin:vue/base', 'standard'],
20 | // required to lint *.vue files
21 | plugins: [
22 | 'vue'
23 | ],
24 | // add your custom rules here
25 | rules: {
26 | // allow paren-less arrow functions
27 | 'arrow-parens': 0,
28 | // allow async-await
29 | 'generator-star-spacing': 0,
30 | // allow debugger during development
31 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/tab/components/ThreadList/DetailTemplate/EmojiChooserDropdown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | {{ emoji }}
11 |
12 |
13 |
14 |
15 |
33 |
38 |
--------------------------------------------------------------------------------
/core/page.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | <%= htmlWebpackPlugin.options.title %>
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/core/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const merge = require('webpack-merge')
3 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
4 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
5 | const { styleLoaders } = require('./tools')
6 | const baseWebpack = require('./webpack.base')
7 |
8 | function genPlugins () {
9 | const plugins = [
10 | new webpack.NoEmitOnErrorsPlugin(),
11 | new FriendlyErrorsPlugin()
12 | ]
13 | if (process.env.ANALYZER) {
14 | plugins.push(new BundleAnalyzerPlugin())
15 | }
16 | return plugins
17 | }
18 |
19 | module.exports = (env) => {
20 | env.NODE_ENV = 'development'
21 | env.DEV = 'true'
22 | return merge(baseWebpack(env), {
23 | mode: 'development',
24 | watch: true,
25 | module: { rules: styleLoaders({ sourceMap: false }) },
26 | devtool: '#cheap-module-eval-source-map',
27 | // devtool: '#cheap-module-source-map',
28 | plugins: genPlugins()
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/core/webpack.beta.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const merge = require('webpack-merge')
3 | const baseWebpack = require('./webpack.base')
4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
5 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
6 | const ZipPlugin = require('zip-webpack-plugin')
7 | const { styleLoaders } = require('./tools')
8 |
9 | module.exports = (env) => {
10 | env.NODE_ENV = 'production'
11 | env.BETA = true
12 | return merge(baseWebpack(env), {
13 | mode: 'production',
14 | module: { rules: styleLoaders({ extract: true, sourceMap: true }) },
15 | plugins: [
16 | new OptimizeCSSPlugin({ cssProcessorOptions: { safe: true } }),
17 | new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash].css' }),
18 | new webpack.HashedModuleIdsPlugin(),
19 | new ZipPlugin({
20 | path: '../..',
21 | filename: (env.FIREFOX) ? 'beta-firefox.zip' : 'beta-chrome.zip'
22 | })
23 | ]
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/core/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const merge = require('webpack-merge')
3 | const baseWebpack = require('./webpack.base')
4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
5 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
6 | const ZipPlugin = require('zip-webpack-plugin')
7 | const { styleLoaders } = require('./tools')
8 |
9 | module.exports = (env) => {
10 | env.NODE_ENV = 'production'
11 | return merge(baseWebpack(env), {
12 | mode: 'production',
13 | module: { rules: styleLoaders({ extract: true, sourceMap: true }) },
14 | devtool: '#cheap-module-source-map',
15 | plugins: [
16 | new OptimizeCSSPlugin({ cssProcessorOptions: { safe: true } }),
17 | new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash].css' }),
18 | new webpack.HashedModuleIdsPlugin(),
19 | new ZipPlugin({
20 | path: '../..',
21 | filename: (env.FIREFOX) ? 'release-firefox.zip' : 'release-chrome.zip'
22 | })
23 | ]
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/src/tab/classes/Threads.js:
--------------------------------------------------------------------------------
1 | export default class Threads extends Array {
2 | /**
3 | * @constructor
4 | * @param {Array