├── .babelrc.js ├── .browserslistrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── .npmrc ├── .postcssrc.js ├── .stylelintignore ├── COPYING ├── README.md ├── assets ├── images │ ├── arrow-up.svg │ ├── cloud-off.svg │ ├── default-theme.png │ ├── delete-blue.base64.svg │ ├── delete-dark.base64.svg │ ├── delete-grey.base64.svg │ ├── dot.png │ ├── download.svg │ ├── drag-handle-dots.png │ ├── fanfou-logo.svg │ ├── fav-blue.base64.svg │ ├── fav-dark.base64.svg │ ├── fav-grey.base64.svg │ ├── faved-grey.base64.svg │ ├── faved-yellow.base64.svg │ ├── favorite.svg │ ├── gtalk.svg │ ├── location.svg │ ├── mail.svg │ ├── mobile.svg │ ├── msn.svg │ ├── pause.png │ ├── personalized-theme.png │ ├── play.png │ ├── protected.svg │ ├── question-mark.svg │ ├── reply-blue.base64.svg │ ├── reply-dark.base64.svg │ ├── reply-grey.base64.svg │ ├── reply.base64.svg │ ├── repost-blue.base64.svg │ ├── repost-dark.base64.svg │ ├── repost-grey.base64.svg │ ├── repost.base64.svg │ ├── rss.svg │ ├── star.svg │ ├── toggle_down.svg │ ├── toggle_up.svg │ ├── unknown-user.jpg │ ├── uploading-image-icons.svg │ └── verify.svg └── sounds │ ├── ding.mp3 │ └── dingdong.mp3 ├── build ├── pack.js ├── shared.js ├── webpack.config.js ├── webpack.css.config.js └── webpack.js.config.js ├── docs ├── architecture.md ├── contributing.md └── publish.md ├── jest.config.js ├── media └── chrome-web-store-badge.png ├── package.json ├── src ├── background │ ├── environment │ │ ├── index.js │ │ ├── messaging.js │ │ ├── proxiedAudio.js │ │ ├── proxiedCreateTab.js │ │ ├── proxiedFetch.js │ │ ├── settings.js │ │ ├── storage-areas │ │ │ ├── index.js │ │ │ ├── local.js │ │ │ ├── session.js │ │ │ └── sync.js │ │ └── storage.js │ ├── feature │ │ ├── createFeatureClass.js │ │ └── createSubfeatureClass.js │ ├── index.js │ └── modules │ │ ├── index.js │ │ ├── notification.js │ │ └── storage.js ├── constants │ ├── .eslintrc.js │ ├── action-types.js │ ├── assets.js │ ├── custom-event-types.js │ ├── extension-origin.js │ ├── index.js │ ├── message-types.js │ └── others.js ├── content │ ├── environment │ │ ├── bridge.js │ │ ├── index.js │ │ ├── injectMainStyle.js │ │ ├── injectScript.js │ │ ├── messaging.js │ │ └── settings.js │ ├── feature │ │ ├── createFeatureClass.js │ │ └── createSubfeatureClass.js │ ├── index.js │ └── modules │ │ ├── index.js │ │ ├── notification.js │ │ ├── scrollManager.js │ │ ├── statusFormIntersectionObserver.js │ │ ├── storage.js │ │ └── timelineElementObserver.js ├── entries │ ├── background-content-page.js │ └── settings.js ├── features │ ├── .eslintrc.js │ ├── auto-pager │ │ ├── @page.js │ │ └── metadata.js │ ├── batch-manage-relationships │ │ ├── friend-requests@page.js │ │ ├── friend-requests@page.less │ │ ├── friends-and-followers@page.js │ │ ├── friends-and-followers@page.less │ │ └── metadata.js │ ├── batch-remove-private-messages │ │ ├── @page.js │ │ ├── @page.less │ │ └── metadata.js │ ├── batch-remove-statuses │ │ ├── @page.js │ │ ├── @page.less │ │ └── metadata.js │ ├── better-at-autocomplete │ │ ├── disable-old-one@page.js │ │ ├── enable-new-one@page.js │ │ ├── enable-new-one@page.less │ │ └── metadata.js │ ├── box-shadows │ │ ├── @content.less │ │ └── metadata.js │ ├── check-friendship │ │ ├── @page.js │ │ └── metadata.js │ ├── check-saved-searches │ │ ├── constants.js │ │ ├── metadata.js │ │ ├── service@background.js │ │ ├── sidebar-indicators@page.js │ │ └── sidebar-indicators@page.less │ ├── enrich-statuses │ │ ├── @page.js │ │ ├── @page.less │ │ ├── manual-tests.md │ │ ├── metadata.js │ │ └── utils │ │ │ ├── createUrlUnshortener.js │ │ │ ├── isExternalLink.js │ │ │ ├── isPlainLink.js │ │ │ ├── isShortUrl.js │ │ │ ├── urlHandlers.js │ │ │ └── urlTransformers.js │ ├── favorite-fanfouers │ │ ├── home@page.js │ │ ├── home@page.less │ │ ├── metadata.js │ │ ├── shared.js │ │ ├── user-profile@page.js │ │ └── user-profile@page.less │ ├── fix-photo-zoom │ │ ├── @page.js │ │ └── metadata.js │ ├── floating-status-form │ │ ├── floating-status-form@page.js │ │ ├── floating-status-form@page.less │ │ ├── manual-tests.md │ │ ├── metadata.js │ │ ├── replay-and-repost@page.js │ │ └── replay-and-repost@page.less │ ├── go-top-button │ │ ├── @page.js │ │ ├── @page.less │ │ └── metadata.js │ ├── google-analytics │ │ ├── @content.js │ │ └── metadata.js │ ├── index.js │ ├── keyboard-shortcuts │ │ ├── @page.js │ │ └── metadata.js │ ├── notifications │ │ ├── metadata.js │ │ ├── service@background.js │ │ └── update-details@background.js │ ├── process-unread-statuses │ │ ├── metadata.js │ │ ├── process-unread-statuses@page.js │ │ ├── scroll-to-show@page.js │ │ └── scroll-to-show@page.less │ ├── remove-app-recommendations │ │ ├── @content.less │ │ └── metadata.js │ ├── remove-brackets │ │ ├── @content.js │ │ └── metadata.js │ ├── remove-logo-beta │ │ ├── @content.js │ │ ├── @content.less │ │ └── metadata.js │ ├── remove-personalized-theme │ │ ├── @content.js │ │ ├── @content.less │ │ ├── fanfou-default-theme.css │ │ └── metadata.js │ ├── retinafy-photos │ │ ├── metadata.js │ │ ├── photo-album@page.js │ │ ├── photo-entry@page.js │ │ ├── shared.js │ │ ├── status@page.js │ │ └── timeline@page.js │ ├── share-new-avatar │ │ ├── @page.js │ │ └── metadata.js │ ├── share-to-fanfou │ │ ├── @background.js │ │ ├── fix-style@content.js │ │ ├── fix-style@content.less │ │ └── metadata.js │ ├── show-contextual-statuses │ │ ├── @page.js │ │ ├── @page.less │ │ ├── constants.js │ │ ├── fix-reply-and-repost@page.js │ │ └── metadata.js │ ├── sidebar-statistics │ │ ├── @page.js │ │ ├── @page.less │ │ └── metadata.js │ ├── status-form-enhancements │ │ ├── ajax-form@page.js │ │ ├── autofocus-textarea@page.js │ │ ├── fix-dnd-upload@page.js │ │ ├── fix-dnd-upload@page.less │ │ ├── fix-upload-images@page.js │ │ ├── manual-tests.md │ │ ├── metadata.js │ │ ├── misc@page.less │ │ ├── paste-image-from-clipboard@page.js │ │ ├── refresh-status-count@page.js │ │ ├── revoke-event-listeners@page.js │ │ └── textarea-state@page.js │ ├── translucent-sidebar │ │ ├── @content.less │ │ └── metadata.js │ ├── update-timestamps │ │ ├── @page.js │ │ └── metadata.js │ └── user-switcher │ │ ├── login-form@page.js │ │ ├── manual-tests.md │ │ ├── metadata.js │ │ ├── user-switcher@page.js │ │ └── user-switcher@page.less ├── libs │ ├── Deferred.js │ ├── ElementCollection.js │ ├── Timeout.js │ ├── Tooltip.js │ ├── animatedScrollTop.js │ ├── arrayRemove.js │ ├── arrayUniquePush.js │ ├── asyncSingleton.js │ ├── blobToBase64.js │ ├── collapseSelection.js │ ├── compareDomains.js │ ├── compareDomains.test.js │ ├── expose.js │ ├── extensionUnloaded.js │ ├── extractText.js │ ├── fade.js │ ├── findElementWithSpecifiedContentInArray.js │ ├── findUserThemeStyleElement.js │ ├── formatDate.js │ ├── getCurrentPageOwnerUserId.js │ ├── getExtensionOrigin.js │ ├── getExtensionVersion.js │ ├── getLoggedInUserId.js │ ├── getLoggedInUserProfilePageUrl.js │ ├── indexOf.js │ ├── isElementInDocument.js │ ├── isExtensionUpgraded.js │ ├── isExtensionUpgraded.test.js │ ├── isFanfouWebUrl.js │ ├── isFanfouWebUrl.test.js │ ├── isHotkey.js │ ├── isLegacyVersion.js │ ├── isLegacyVersion.test.js │ ├── isNaN.js │ ├── isStatusElement.js │ ├── jsonp.js │ ├── keepRetry.js │ ├── loadAsset.js │ ├── localStorageWrappers.js │ ├── log.js │ ├── memoize.js │ ├── memoize.test.js │ ├── migrate.js │ ├── neg.js │ ├── neg.test.js │ ├── noop.js │ ├── omitBy.js │ ├── pageDetect.js │ ├── parseFilename.js │ ├── parseFilename.test.js │ ├── parseHTML.js │ ├── parseQueryString.js │ ├── parseQueryString.test.js │ ├── parseUrl.js │ ├── parseUrl.test.js │ ├── playSound.js │ ├── preactRender.js │ ├── prependElement.js │ ├── promiseAny.js │ ├── promiseEvery.js │ ├── promisifyChromeApi.js │ ├── replaceExtensionOrigin.js │ ├── requireFanfouLib.js │ ├── safelyInvokeFn.js │ ├── safelyInvokeFns.js │ ├── stringCases.js │ ├── stringCases.test.js │ ├── timestamp.js │ ├── toggleVisibility.js │ ├── truncateFilename.js │ ├── truncateFilename.test.js │ ├── truncateUrl.js │ ├── untilElementRemoved.js │ ├── waitForHead.js │ └── wrapper.js ├── page │ ├── environment │ │ ├── bridge.js │ │ ├── index.js │ │ └── settings.js │ ├── feature │ │ ├── createFeatureClass.js │ │ └── createSubfeatureClass.js │ ├── index.js │ ├── modules │ │ ├── checkMyNewStatus.js │ │ ├── index.js │ │ ├── proxiedAudio.js │ │ ├── proxiedCreateTab.js │ │ ├── proxiedFetch.js │ │ └── storage.js │ └── styles │ │ ├── 00-global.less │ │ ├── 10-animation.less │ │ ├── 10-helpers.less │ │ ├── 10-remove-link-underlines.less │ │ ├── 20-new-style-operation-icons.less │ │ ├── 20-others.less │ │ ├── 90-react-tooltip-lite.less │ │ ├── 90-sharer.less │ │ ├── 90-simplified-view.less │ │ └── index.js ├── settings │ ├── components │ │ ├── App.js │ │ ├── CloudSyncingDisabledTip.js │ │ ├── ExternalLink.js │ │ ├── HelpAndSupport.js │ │ └── VersionHistory.js │ ├── getTabDefs.js │ ├── messaging.js │ ├── settings.js │ └── styles │ │ ├── index.js │ │ ├── settings.less │ │ └── ui-kit-for-chrome-extensions │ │ ├── chrome_shared.css │ │ └── widgets.css └── version-history │ ├── index.js │ ├── parseVersionHistory.js │ └── versionHistory ├── static ├── icons │ ├── icon-128.png │ ├── icon-16.png │ ├── icon-24.png │ ├── icon-256.png │ ├── icon-32.png │ ├── icon-48.png │ └── icon-640.png ├── manifest.json └── settings.html └── stylelint.config.js /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | [ '@babel/plugin-transform-react-jsx', { 4 | pragma: 'h', 5 | } ], 6 | [ '@babel/plugin-proposal-class-properties', { 7 | loose: true, 8 | } ], 9 | '@babel/plugin-proposal-optional-chaining', 10 | '@babel/plugin-proposal-do-expressions', 11 | '@babel/plugin-proposal-export-default-from', 12 | '@babel/plugin-proposal-function-bind', 13 | 'macros', 14 | ], 15 | 16 | env: { 17 | test: { 18 | plugins: [ 19 | '@babel/plugin-transform-modules-commonjs', 20 | ], 21 | }, 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | # Browsers that we support 2 | 3 | last 4 chrome versions 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 'riophae', 'plugin:react/recommended' ], 4 | parser: 'babel-eslint', 5 | 6 | globals: { 7 | chrome: true, 8 | }, 9 | 10 | plugins: [ 'react' ], 11 | 12 | settings: { 13 | 'import/resolver': { 14 | node: null, 15 | webpack: { 16 | config: 'build/webpack.config.js', 17 | }, 18 | }, 19 | 'import/extensions': [ '.js', '.json', '.css', '.less' ], 20 | 21 | react: { 22 | pragma: 'h', 23 | version: require('preact/compat').version, 24 | }, 25 | }, 26 | 27 | rules: { 28 | 'react/no-unknown-property': [ 2, { ignore: [ 'class' ] }], 29 | 'react/prop-types': 0, 30 | 'unicorn/consistent-function-scoping': 0, 31 | 'no-warning-comments': 0, 32 | 'require-atomic-updates': 0, 33 | }, 34 | 35 | overrides: [ { 36 | files: [ '*.test.js' ], 37 | env: { 38 | jest: true, 39 | }, 40 | } ], 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [10.x, 12.x, 13.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: npm install, build, and test 21 | run: | 22 | node --version 23 | npm --version 24 | npm install 25 | npm run build --if-present 26 | npm test 27 | env: 28 | CI: true 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | .cache-loader 3 | dist 4 | *.zip 5 | 6 | .DS_Store 7 | .gitconfig 8 | 9 | node_modules 10 | *.log 11 | 12 | .project 13 | .vscode 14 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | plugins: { 5 | autoprefixer: {}, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | dist 2 | src/settings/styles/ui-kit-for-chrome-extensions 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 太空饭否 logo 2 | 3 | # 太空饭否 4 | 5 | [![](https://github.com/fanfoujs/space-fanfou/workflows/Node%20CI/badge.svg)](https://github.com/fanfoujs/space-fanfou/actions) 6 | [![](https://img.shields.io/github/release/fanfoujs/space-fanfou.svg)](https://github.com/fanfoujs/space-fanfou/releases) 7 | [![](https://img.shields.io/github/license/fanfoujs/space-fanfou.svg)](https://github.com/fanfoujs/space-fanfou/blob/master/LICENSE) 8 | 9 | ### 简介 10 | 11 | 太空饭否是一个免费、用心的开源项目,是目前最强大最好用的饭否浏览器扩展。可以给饭否添加回复和转发展开、桌面通知、浮动输入框、多用户切换、消息批量管理、自动翻页等功能,并且使饭否页面变得更美更舒心,符合您的使用习惯。 12 | 13 | ### 团队 14 | 15 | 16 | 17 | **[@太空小孩](https://fanfou.com/anegie)**|**[@锐风](https://fanfou.com/ruif)**|**[@饭小默](https://fanfou.com/lito)**|**[@Xidorn](https://fanfou.com/xidorn)**|**[@.rex](https://fanfou.com/zhasm)** 18 | :-----:|:-----:|:-----:|:-----:|:-----: 19 | 20 | ### 下载安装 21 | 22 | 请使用 Chrome 浏览器访问网上应用店获取插件下载。 23 | 24 | 25 | Download on the Chrome Web Store 26 | 27 | 28 | ### 参阅 29 | 30 | 31 | 32 | **→ [官方饭否](https://fanfou.com/spacefanfou)**|**→ [入门手册](https://spacekid.me/spacefanfou/)** 33 | :-----:|:-----: 34 | 35 | ### 协议 36 | 37 | 基于 [GPL v3](COPYING) 协议发布。版权所有 © 2011-2020 太空饭否开发组。 38 | -------------------------------------------------------------------------------- /assets/images/arrow-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/cloud-off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/default-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fanfoujs/space-fanfou/e2068914e179dc64f1d1e3b8263b2b7c4a92aa49/assets/images/default-theme.png -------------------------------------------------------------------------------- /assets/images/delete-blue.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/delete-dark.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/delete-grey.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fanfoujs/space-fanfou/e2068914e179dc64f1d1e3b8263b2b7c4a92aa49/assets/images/dot.png -------------------------------------------------------------------------------- /assets/images/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/drag-handle-dots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fanfoujs/space-fanfou/e2068914e179dc64f1d1e3b8263b2b7c4a92aa49/assets/images/drag-handle-dots.png -------------------------------------------------------------------------------- /assets/images/fav-blue.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/fav-dark.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/fav-grey.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/faved-grey.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/faved-yellow.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/favorite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/gtalk.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/location.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/mail.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/mobile.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/msn.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fanfoujs/space-fanfou/e2068914e179dc64f1d1e3b8263b2b7c4a92aa49/assets/images/pause.png -------------------------------------------------------------------------------- /assets/images/personalized-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fanfoujs/space-fanfou/e2068914e179dc64f1d1e3b8263b2b7c4a92aa49/assets/images/personalized-theme.png -------------------------------------------------------------------------------- /assets/images/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fanfoujs/space-fanfou/e2068914e179dc64f1d1e3b8263b2b7c4a92aa49/assets/images/play.png -------------------------------------------------------------------------------- /assets/images/protected.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/question-mark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/reply-blue.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/reply-dark.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/reply-grey.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/reply.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/repost-blue.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/repost-dark.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/repost-grey.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/repost.base64.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/rss.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/toggle_down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/toggle_up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/unknown-user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fanfoujs/space-fanfou/e2068914e179dc64f1d1e3b8263b2b7c4a92aa49/assets/images/unknown-user.jpg -------------------------------------------------------------------------------- /assets/images/verify.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/sounds/ding.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fanfoujs/space-fanfou/e2068914e179dc64f1d1e3b8263b2b7c4a92aa49/assets/sounds/ding.mp3 -------------------------------------------------------------------------------- /assets/sounds/dingdong.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fanfoujs/space-fanfou/e2068914e179dc64f1d1e3b8263b2b7c4a92aa49/assets/sounds/dingdong.mp3 -------------------------------------------------------------------------------- /build/pack.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | 3 | const path = require('path') 4 | const cp = require('child_process') 5 | const chalk = require('chalk') 6 | const timestamp = require('tinydate')('{YYYY}/{MM}/{DD} {HH}:{mm}:{ss}') 7 | const manifest = require('../static/manifest') 8 | 9 | function log(message) { 10 | console.log(`[${chalk.cyan(timestamp())}] ${message}`) 11 | } 12 | 13 | function pack() { 14 | const version = manifest.version_name || manifest.version 15 | const zipName = `space-fanfou-${version}.zip` 16 | const pkgRootPath = path.join(__dirname, '..') 17 | const outPath = path.join(pkgRootPath, zipName) 18 | const distPath = path.join(pkgRootPath, 'dist') 19 | const command = `rm -f ${JSON.stringify(outPath)} && cd ${JSON.stringify(distPath)} && zip -r ${JSON.stringify(outPath)} *` 20 | 21 | log(`正在创建:${chalk.green(zipName)}`) 22 | 23 | cp.exec(command, error => { 24 | if (error) { 25 | log(chalk.red('创建失败')) 26 | console.log(error) 27 | } else { 28 | log(chalk.green('创建成功')) 29 | } 30 | }) 31 | } 32 | pack() 33 | -------------------------------------------------------------------------------- /build/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | require('./webpack.js.config')('background', 'background-content-page'), 3 | require('./webpack.js.config')('content', 'background-content-page'), 4 | require('./webpack.js.config')('page', 'background-content-page'), 5 | require('./webpack.js.config')('settings', 'settings'), 6 | require('./webpack.css.config'), 7 | ] 8 | -------------------------------------------------------------------------------- /build/webpack.css.config.js: -------------------------------------------------------------------------------- 1 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') 2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 3 | const OmitJSforCSSPlugin = require('webpack-omit-js-for-css-plugin') 4 | const { 5 | approot, 6 | defaultArgv, 7 | generateBaseConfig, 8 | generateStyleLoader, 9 | generateFileLoaderForImages, 10 | generateUrlLoaderForImages, 11 | } = require('./shared') 12 | 13 | module.exports = (_, { mode } = defaultArgv) => ({ 14 | name: 'css', 15 | 16 | entry: { 17 | page: approot('src/page/styles/index.js'), 18 | settings: approot('src/settings/styles/index.js'), 19 | }, 20 | 21 | output: { 22 | path: approot('dist'), 23 | // 这里必须得是 .js,代表的是最终打包的结果,并不是我们想要的 CSS 文件 24 | filename: '[name].js', 25 | }, 26 | 27 | ...generateBaseConfig(mode), 28 | 29 | module: { 30 | rules: [ 31 | generateStyleLoader({ extract: true, mode }), 32 | generateUrlLoaderForImages(), 33 | generateFileLoaderForImages({ publicPath: '/' }), 34 | ], 35 | }, 36 | 37 | plugins: [ 38 | // 通过这个插件把打包结果中的 CSS 代码提取出来,写入单独的 .css 文件 39 | new MiniCssExtractPlugin(), 40 | // 通过这个插件阻止 webpack 输出打包结果文件(.js),只保留 .css 文件 41 | new OmitJSforCSSPlugin(), 42 | mode === 'production' && new OptimizeCssAssetsPlugin(), 43 | ].filter(Boolean), 44 | }) 45 | -------------------------------------------------------------------------------- /build/webpack.js.config.js: -------------------------------------------------------------------------------- 1 | const TerserWebpackPlugin = require('terser-webpack-plugin') 2 | const CopyWebpackPlugin = require('copy-webpack-plugin') 3 | const { EXTENSION_ORIGIN_PLACEHOLDER } = require('esm')(module)('../src/constants/extension-origin') 4 | const { 5 | approot, 6 | defaultArgv, 7 | generateBaseConfig, 8 | generateStyleLoader, 9 | generateFileLoaderForImages, 10 | generateUrlLoaderForImages, 11 | generateFileLoaderForOtherAssets, 12 | } = require('./shared') 13 | 14 | module.exports = (id, entryFile) => (_, { mode } = defaultArgv) => ({ 15 | name: 'js', 16 | 17 | entry: { 18 | [id]: approot(`src/entries/${entryFile}.js`), 19 | }, 20 | 21 | output: { 22 | path: approot('dist'), 23 | filename: '[name].js', 24 | }, 25 | 26 | ...generateBaseConfig(mode), 27 | 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.js$/, 32 | use: [ 33 | { 34 | loader: 'cache-loader', 35 | options: { 36 | cacheIdentifier: require('cache-loader/package').version + mode + id, 37 | }, 38 | }, 39 | 'babel-loader', 40 | { 41 | loader: 'ifdef-loader', 42 | options: { 43 | DEVELOPMENT: mode === 'development', 44 | PRODUCTION: mode === 'production', 45 | ENV_BACKGROUND: id === 'background', 46 | ENV_CONTENT: id === 'content', 47 | ENV_PAGE: id === 'page', 48 | }, 49 | }, 50 | ], 51 | }, 52 | generateStyleLoader({ mode }), 53 | generateUrlLoaderForImages(), 54 | generateFileLoaderForImages({ publicPath: `${EXTENSION_ORIGIN_PLACEHOLDER}/` }), 55 | generateFileLoaderForOtherAssets(), 56 | ], 57 | }, 58 | 59 | plugins: [ 60 | new CopyWebpackPlugin([ { 61 | from: approot('static'), 62 | to: approot('dist'), 63 | } ]), 64 | ], 65 | 66 | optimization: { 67 | // 使代码保持在可读的状态,方便用户反馈 bug 后 debug 68 | minimizer: [ 69 | new TerserWebpackPlugin({ 70 | terserOptions: { 71 | ecma: 8, 72 | compress: { 73 | defaults: false, 74 | dead_code: true, // eslint-disable-line camelcase 75 | evaluate: true, 76 | unused: true, 77 | }, 78 | mangle: false, 79 | output: { 80 | beautify: true, 81 | indent_level: 2, // eslint-disable-line camelcase 82 | }, 83 | }, 84 | extractComments: false, 85 | }), 86 | ], 87 | }, 88 | }) 89 | -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | # 架构 2 | 3 | ### 太空饭否分为哪几个部分? 4 | 5 | 太空饭否分为四个部分: 6 | 7 | - Background Scripts - 扩展的背景页面脚本 8 | - Content Scripts - 通过 manifest.json 中 `content_scripts` 部分声明的脚本 9 | - Page Scripts - 上面 Content Scripts 通过 ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | /* eslint sort-keys: 2 */ 2 | 3 | module.exports = { 4 | extends: 'stylelint-config-xo-space', 5 | rules: { 6 | 'at-rule-empty-line-before': [ 'always', { 7 | except: [ 'blockless-after-same-name-blockless' ], 8 | } ], 9 | 'declaration-empty-line-before': null, 10 | 'declaration-no-important': null, 11 | 'declaration-property-value-blacklist': null, 12 | 'function-comma-newline-after': null, 13 | 'function-comma-space-after': null, 14 | 'function-parentheses-newline-inside': null, 15 | 'function-parentheses-space-inside': null, 16 | 'no-descending-specificity': null, 17 | 'no-unknown-animations': null, 18 | 'property-blacklist': null, 19 | 'selector-class-pattern': null, 20 | 'selector-id-pattern': null, 21 | 'string-quotes': [ 'double', { 22 | avoidEscape: false, 23 | } ], 24 | }, 25 | } 26 | --------------------------------------------------------------------------------