├── .github ├── dependabot.yml └── workflows │ ├── build-release.yml │ └── build-test.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierignore ├── .prettierrc.json ├── .yarnrc ├── LICENSE ├── README.md ├── package.json ├── public ├── favicon.ico ├── images │ ├── icon128a.png │ ├── icon16.png │ ├── icon16a.png │ ├── icon24.png │ ├── icon24a.png │ ├── icon32.png │ ├── icon32a.png │ └── icon48a.png └── manifest.json ├── src ├── assets │ ├── favicon.ico │ ├── index.ejs │ └── main.css ├── background │ └── index.ts ├── common │ ├── invite.ts │ ├── ipfs.ts │ ├── multi-accounts.ts │ ├── rss3.ts │ └── sync-control.ts ├── components │ ├── Avatar.vue │ ├── BackButton.vue │ ├── Button.vue │ ├── CollapseMenu.vue │ ├── Footer.vue │ ├── Input.vue │ ├── ItemList.vue │ ├── KeyContainer.vue │ ├── Logo.vue │ ├── LogoTitle.vue │ ├── PopupContainer.vue │ ├── SingleItem.vue │ ├── SingleUser.vue │ ├── ToggleSwitch.vue │ ├── Tooltip.vue │ ├── UserList.vue │ └── icons │ │ ├── Icon.vue │ │ ├── IconAdd.vue │ │ ├── IconBack.vue │ │ ├── IconCopy.vue │ │ ├── IconHide.vue │ │ ├── IconMore.vue │ │ ├── IconRight.vue │ │ ├── IconView.vue │ │ └── IconX.vue ├── content-script │ ├── components │ │ └── twitter.ts │ ├── handlers │ │ └── twitter.ts │ ├── index.ts │ ├── locationChange.ts │ └── utils.ts ├── options │ ├── App.vue │ ├── index.ts │ ├── router.ts │ └── views │ │ ├── Index.vue │ │ ├── Start │ │ ├── Address.vue │ │ ├── Base.vue │ │ ├── Congrats.vue │ │ ├── Index.vue │ │ ├── Login.vue │ │ ├── LoginBack.vue │ │ ├── New.vue │ │ ├── Pending.vue │ │ ├── PrivateKey.vue │ │ ├── Profile.vue │ │ └── SavePrivateKey.vue │ │ └── Tabs │ │ ├── Advanced │ │ ├── Base.vue │ │ ├── Delete.vue │ │ └── View.vue │ │ ├── Base.vue │ │ ├── FollowUsers │ │ ├── Followers.vue │ │ └── Following.vue │ │ ├── Home.vue │ │ ├── Invite.vue │ │ ├── Profile.vue │ │ ├── Settings.vue │ │ └── components │ │ ├── leftside │ │ ├── Footer.vue │ │ ├── NavMenu.vue │ │ └── Profile.vue │ │ └── sidebars │ │ ├── SidebarLeft.vue │ │ └── SidebarRight.vue ├── popup │ ├── App.vue │ ├── index.ts │ ├── router.ts │ └── views │ │ ├── Account.vue │ │ ├── FollowUsers │ │ ├── Followers.vue │ │ └── Following.vue │ │ ├── Home.vue │ │ ├── Index.vue │ │ ├── Invite.vue │ │ ├── Login.vue │ │ ├── LoginBack.vue │ │ ├── Onboarding.vue │ │ ├── Pending.vue │ │ └── Profile.vue └── types │ └── shims-vue.d.ts ├── tailwind.config.js ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/build-release.yml: -------------------------------------------------------------------------------- 1 | name: Build release pack 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node-version: [16.x] 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | 17 | - name: Checkout tag 18 | run: | 19 | git fetch --depth=1 origin +refs/tags/*:refs/tags/* 20 | tag_name="${GITHUB_REF##*/}" 21 | echo Tag $tag_name 22 | git checkout $tag_name 23 | echo "TAG_NAME=${tag_name}" >> $GITHUB_ENV 24 | 25 | - name: Use Node.js ${{ matrix.node-version }} 26 | uses: actions/setup-node@v2 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | 30 | - name: Install Dependencies 31 | run: yarn install --frozen-lockfile 32 | 33 | - name: Build 34 | run: yarn build 35 | 36 | - name: Package 37 | run: | 38 | cd ./dist/ 39 | zip -r ../re-id.zip ./ 40 | cd ../ 41 | 42 | - name: Release 43 | uses: softprops/action-gh-release@v1 44 | with: 45 | draft: true 46 | name: ${{ env.TAG_NAME }} 47 | tag_name: ${{ env.TAG_NAME }} 48 | files: ./re-id.zip 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Build test pack 2 | 3 | on: 4 | push: 5 | branches: [develop, release/*] 6 | pull_request: 7 | branches: [develop] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [16.x] 16 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v2 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: yarn install --frozen-lockfile 25 | - run: yarn build 26 | 27 | - name: Pack and upload Artifact 28 | uses: actions/upload-artifact@v2 29 | with: 30 | name: reid-test-pack 31 | path: dist/ # this will pack automatically, so no other packs needed 32 | retention-days: 7 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .vscode 4 | .idea 5 | .DS_Store 6 | yarn-error.log 7 | dist 8 | logs 9 | storage 10 | .env 11 | .cache -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .vscode 4 | .idea 5 | .DS_Store 6 | yarn-error.log 7 | dist 8 | logs 9 | storage 10 | .env 11 | .cache -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | --exact true 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Re: ID 3 |

4 |

Re: ID

5 | 6 | 7 | 8 | > 🐋 The first step to own your own data. 9 | 10 | [![test-build](https://img.shields.io/github/workflow/status/NaturalSelectionLabs/Re-ID/Build%20test%20pack?label=test%20build&style=for-the-badge)](https://github.com/NaturalSelectionLabs/Re-ID/actions/workflows/build-test.yml) 11 | [![release-build](https://img.shields.io/github/workflow/status/NaturalSelectionLabs/Re-ID/Build%20release%20pack?label=release%20build&style=for-the-badge)](https://github.com/NaturalSelectionLabs/Re-ID/actions/workflows/build-release.yml) 12 | [![release](https://img.shields.io/github/v/release/NaturalSelectionLabs/Re-ID?color=%235d66f5&style=for-the-badge)](https://github.com/NaturalSelectionLabs/Re-ID/releases) 13 | [![chrome web store](https://img.shields.io/chrome-web-store/v/hcioafpcjhamfeiegnnahpjnmnlilkil?color=%23f55d66&style=for-the-badge)](https://chrome.google.com/webstore/detail/re-id/hcioafpcjhamfeiegnnahpjnmnlilkil) 14 | 15 | [![available in chrome web store](https://storage.googleapis.com/chrome-gcs-uploader.appspot.com/image/WlD8wC6g8khYWPJUsQceQkhXSlv1/UV4C4ybeBTsZt43U4xis.png)](https://chrome.google.com/webstore/detail/re-id/hcioafpcjhamfeiegnnahpjnmnlilkil) 16 | 17 | 18 | 19 | --- 20 | 21 | ## 🍭 Introduction 22 | 23 | Automatically sync your posts on social medias to a crypto based [RSS3](https://github.com/NaturalSelectionLabs/RSS3) files that is completely controlled by **YOU**. 24 | 25 | With Re:ID, the content create on social medias platforms will be no longer bonded with the platform itself, who lock up your data as their own treasure. 26 | 27 | You will have your own copy which can be carried to any RSS3 compatible applications. 28 | 29 | ## 🎁 Installation 30 | 31 | The latest stable version can be grabbed from [Release Page](https://github.com/NaturalSelectionLabs/Re-ID/releases) with **re-id.zip** file attached. 32 | 33 | You can also migrate to [Chrome Web Store](https://chrome.google.com/webstore/detail/re-id/hcioafpcjhamfeiegnnahpjnmnlilkil) for the published version. It might have some latency than the Release version because google need to review it before publish. 34 | 35 | ## ⚙ Development 36 | 37 | 1. Build dev pack 38 | 39 | - yarn (recommended) 40 | 41 | ```bash 42 | yarn 43 | yarn dev 44 | ``` 45 | 46 | - npm 47 | ```bash 48 | npm install 49 | npm run dev 50 | ``` 51 | 52 | 2. Open `chrome://extensions/` 53 | 54 | 3. Open `Developer mode` in the top right corner 55 | 56 | 4. Click `Load unpacked` in the top left corner 57 | 58 | 5. Select `path/Re-ID/dist` 59 | 60 | ## 🔨 Builds 61 | 62 | ### Release builds 63 | 64 | You can navigate to [Release page](https://github.com/NaturalSelectionLabs/Re-ID/releases) for the release packs. 65 | 66 | ### Test builds 67 | 68 | You can navigate to [Workflow Run page](https://github.com/NaturalSelectionLabs/Re-ID/actions/workflows/build-test.yml) for the test artifacts. If they are expired, you can build your own pack. 69 | 70 | ### Build by yourself 71 | 72 | - yarn (recommended) 73 | 74 | ```bash 75 | yarn build 76 | ``` 77 | 78 | - npm 79 | ```bash 80 | npm run build 81 | ``` 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "re-id", 3 | "version": "0.2.2", 4 | "description": "The first step to own your own data.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack -w --mode development", 8 | "build": "NODE_ENV=production webpack --mode production", 9 | "prepare": "husky install" 10 | }, 11 | "lint-staged": { 12 | "**/*": "prettier --write --ignore-unknown" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/NaturalSelectionLabs/Re-ID.git" 17 | }, 18 | "keywords": [ 19 | "RSS3" 20 | ], 21 | "license": "GPL-3.0", 22 | "author": "NaturalSelectionLabs", 23 | "bugs": { 24 | "url": "https://github.com/NaturalSelectionLabs/Re-ID/issues" 25 | }, 26 | "homepage": "https://github.com/NaturalSelectionLabs/Re-ID#readme", 27 | "dependencies": { 28 | "assert": "2.0.0", 29 | "axios": "0.21.1", 30 | "buffer": "6.0.3", 31 | "crypto-browserify": "3.12.0", 32 | "eth-crypto": "2.0.0", 33 | "lodash": "4.17.21", 34 | "moment": "2.29.1", 35 | "postcss-nested": "5.0.6", 36 | "process": "0.11.10", 37 | "rss3": "0.5.0", 38 | "stream-browserify": "3.0.0", 39 | "typescript": "4.3.5", 40 | "vue": "3.1.5", 41 | "vue-class-component": "8.0.0-rc.1", 42 | "vue-moment": "4.1.0" 43 | }, 44 | "devDependencies": { 45 | "@babel/core": "7.17.10", 46 | "@babel/preset-env": "7.17.10", 47 | "@types/chrome": "0.0.150", 48 | "@types/lodash": "4.14.182", 49 | "@vue/compiler-sfc": "3.1.5", 50 | "autoprefixer": "10.3.1", 51 | "babel-loader": "8.2.2", 52 | "copy-webpack-plugin": "9.0.1", 53 | "css-loader": "6.2.0", 54 | "cssnano": "5.0.7", 55 | "html-webpack-plugin": "5.5.0", 56 | "husky": "8.0.1", 57 | "less-loader": "10.0.1", 58 | "lint-staged": "12.4.1", 59 | "postcss": "8.3.6", 60 | "postcss-loader": "6.2.1", 61 | "prettier": "2.3.2", 62 | "tailwindcss": "2.2.7", 63 | "terser-webpack-plugin": "5.1.4", 64 | "ts-loader": "9.3.0", 65 | "url-loader": "4.1.1", 66 | "vue-loader": "16.4.1", 67 | "vue-router": "4.0.12", 68 | "vue-style-loader": "4.1.3", 69 | "webpack": "5.72.0", 70 | "webpack-cli": "4.7.2" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NaturalSelectionLabs/Re-ID/6d96908c59b7272c9e5fbc7f9ba6aae2327d31ab/public/favicon.ico -------------------------------------------------------------------------------- /public/images/icon128a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NaturalSelectionLabs/Re-ID/6d96908c59b7272c9e5fbc7f9ba6aae2327d31ab/public/images/icon128a.png -------------------------------------------------------------------------------- /public/images/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NaturalSelectionLabs/Re-ID/6d96908c59b7272c9e5fbc7f9ba6aae2327d31ab/public/images/icon16.png -------------------------------------------------------------------------------- /public/images/icon16a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NaturalSelectionLabs/Re-ID/6d96908c59b7272c9e5fbc7f9ba6aae2327d31ab/public/images/icon16a.png -------------------------------------------------------------------------------- /public/images/icon24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NaturalSelectionLabs/Re-ID/6d96908c59b7272c9e5fbc7f9ba6aae2327d31ab/public/images/icon24.png -------------------------------------------------------------------------------- /public/images/icon24a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NaturalSelectionLabs/Re-ID/6d96908c59b7272c9e5fbc7f9ba6aae2327d31ab/public/images/icon24a.png -------------------------------------------------------------------------------- /public/images/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NaturalSelectionLabs/Re-ID/6d96908c59b7272c9e5fbc7f9ba6aae2327d31ab/public/images/icon32.png -------------------------------------------------------------------------------- /public/images/icon32a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NaturalSelectionLabs/Re-ID/6d96908c59b7272c9e5fbc7f9ba6aae2327d31ab/public/images/icon32a.png -------------------------------------------------------------------------------- /public/images/icon48a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NaturalSelectionLabs/Re-ID/6d96908c59b7272c9e5fbc7f9ba6aae2327d31ab/public/images/icon48a.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Re: ID", 4 | "version": "0.2.2", 5 | 6 | "action": { 7 | "default_popup": "popup.html", 8 | "default_icon": { 9 | "16": "images/icon16.png", 10 | "24": "images/icon24.png", 11 | "32": "images/icon32.png" 12 | } 13 | }, 14 | "description": "The first step to own your own data.", 15 | "icons": { 16 | "16": "images/icon16a.png", 17 | "24": "images/icon24a.png", 18 | "32": "images/icon32a.png", 19 | "48": "images/icon48a.png", 20 | "128": "images/icon128a.png" 21 | }, 22 | 23 | "author": "NaturalSelectionLabs", 24 | "background": { 25 | "service_worker": "background.js" 26 | }, 27 | "content_scripts": [ 28 | { 29 | "matches": ["https://twitter.com/*"], 30 | "js": ["content-script.js"] 31 | } 32 | ], 33 | "options_page": "options.html", 34 | "homepage_url": "https://reid.town/", 35 | "permissions": ["tabs", "storage"], 36 | "host_permissions": ["https://twitter.com/*"] 37 | } 38 | -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NaturalSelectionLabs/Re-ID/6d96908c59b7272c9e5fbc7f9ba6aae2327d31ab/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/assets/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/main.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | body { 7 | @apply font-montserrat; 8 | } 9 | 10 | ::-webkit-scrollbar { 11 | width: 3px; 12 | height: 3px; 13 | } 14 | ::-webkit-scrollbar-thumb { 15 | background: #c5c5c7; 16 | border-radius: 2px; 17 | } 18 | ::-webkit-scrollbar-thumb:hover { 19 | background: #3c3c43; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/background/index.ts: -------------------------------------------------------------------------------- 1 | const supportedHost = ['twitter.com']; 2 | 3 | function setIcon(url: string) { 4 | const host = new URL(url); 5 | if (host.host === chrome.runtime.id || supportedHost.includes(host.host)) { 6 | chrome.action.setIcon({ 7 | path: { 8 | '16': 'images/icon16a.png', 9 | '24': 'images/icon24a.png', 10 | '32': 'images/icon32a.png', 11 | }, 12 | }); 13 | } else { 14 | chrome.action.setIcon({ 15 | path: { 16 | '16': 'images/icon16.png', 17 | '24': 'images/icon24.png', 18 | '32': 'images/icon32.png', 19 | }, 20 | }); 21 | } 22 | } 23 | chrome.tabs.onActivated.addListener((tab) => { 24 | chrome.tabs.query( 25 | { 26 | active: true, 27 | }, 28 | (tab) => { 29 | if (tab?.[0]?.url) { 30 | setIcon(tab[0].url); 31 | } 32 | }, 33 | ); 34 | }); 35 | 36 | chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { 37 | if (changeInfo.url && tab.active) { 38 | setIcon(changeInfo.url); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /src/common/invite.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | const Accounts = require('web3-eth-accounts'); 3 | 4 | const inviteEndpoint = 'https://re-id-invite-database-q335m.ondigitalocean.app'; 5 | // const inviteEndpoint = 'http://localhost:8080'; 6 | 7 | let msg = ''; 8 | 9 | const accounts = new Accounts(); 10 | 11 | export default { 12 | invite: { 13 | check: async (addr: string): Promise => { 14 | return new Promise((resolve) => { 15 | chrome.storage.sync.get(['invited'], async (result) => { 16 | if (result.invited) { 17 | resolve(true); 18 | } else { 19 | const res = await axios.get(inviteEndpoint + '/invite/check/' + addr); 20 | if (res.data.ok) { 21 | chrome.storage.sync.set({ 22 | invited: true, 23 | }); 24 | } 25 | msg = res.data.message; 26 | resolve(res.data.ok); 27 | } 28 | }); 29 | }); 30 | }, 31 | 32 | new: async (inviter: string, invitee: string, inviterKey: string): Promise => { 33 | const res = await axios.post(inviteEndpoint + '/invite/new', { 34 | from: inviter, 35 | address: invitee, 36 | signature: accounts.sign('Invite' + invitee, inviterKey).signature, 37 | }); 38 | msg = res.data.message; 39 | return res.data.ok; 40 | }, 41 | 42 | msg: (): string => { 43 | return msg; 44 | }, 45 | }, 46 | 47 | bind: { 48 | new: async (address: string, platform: string, username: string, privateKey: string): Promise => { 49 | const res = await axios.post(inviteEndpoint + '/bind/bind', { 50 | address: address, 51 | platform: platform, 52 | username: username, 53 | signature: accounts.sign(`${platform}:${username}`, privateKey).signature, 54 | }); 55 | msg = res.data.message; 56 | return res.data.ok; 57 | }, 58 | 59 | searchByUsername: async (platform: string, username: string): Promise => { 60 | const res = await axios.get(inviteEndpoint + '/bind/' + platform + '/username/' + username); 61 | msg = res.data.message; 62 | return res.data.data; 63 | }, 64 | 65 | searchByAddress: async (platform: string, address: string): Promise => { 66 | const res = await axios.get(inviteEndpoint + '/bind/' + platform + '/address/' + address); 67 | msg = res.data.message; 68 | return res.data.data; 69 | }, 70 | 71 | msg: (): string => { 72 | return msg; 73 | }, 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /src/common/ipfs.ts: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | export default { 4 | upload: (file: File | Blob) => { 5 | return new Promise(async (resolve) => { 6 | if (file) { 7 | let pinataMetadataStringify = JSON.stringify({ 8 | name: 'Re: ID', 9 | }); 10 | const formData = new FormData(); 11 | formData.append('file', file); 12 | formData.append('cidVersion', '0'); 13 | formData.append('pinataMetadata', pinataMetadataStringify); 14 | const response = await axios.post('https://api.pinata.cloud/pinning/pinFileToIPFS', formData, { 15 | maxBodyLength: 'Infinity', 16 | headers: { 17 | 'Content-Type': `multipart/form-data;`, 18 | pinata_api_key: '7a1cd1286fddab8cb792', 19 | pinata_secret_api_key: 'eed51e69e8c6ffc04700f83597b46c617f275e9aae3218d9bbe037ea3889ad58', 20 | }, 21 | }); 22 | resolve('https://gateway.pinata.cloud/ipfs/' + response.data.IpfsHash); 23 | } 24 | }); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/common/multi-accounts.ts: -------------------------------------------------------------------------------- 1 | export type UserAccount = { 2 | avatar: string; 3 | name: string; 4 | address: string; 5 | privateKey?: string; 6 | }; 7 | 8 | async function GetAccounts() { 9 | return new Promise((resolve) => { 10 | chrome.storage.sync.get(['rss3accounts'], (accountsShowList) => { 11 | if (accountsShowList?.['rss3accounts']) { 12 | resolve(accountsShowList['rss3accounts']); 13 | } else { 14 | resolve(null); 15 | } 16 | }); 17 | }); 18 | } 19 | 20 | export default { 21 | get: GetAccounts, 22 | set: async (account: UserAccount) => { 23 | const accountsList: UserAccount[] = (await GetAccounts()) || []; 24 | const userIndex = accountsList.findIndex((user) => { 25 | return user.privateKey === account.privateKey; 26 | }); 27 | if (userIndex === -1) { 28 | // Do not exist 29 | accountsList.push(account); 30 | } else { 31 | // Update user info 32 | accountsList[userIndex] = account; 33 | } 34 | await chrome.storage.sync.set({ 35 | rss3accounts: accountsList, 36 | }); 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/common/rss3.ts: -------------------------------------------------------------------------------- 1 | import RSS3 from 'rss3'; 2 | 3 | let rss3: RSS3 | null; 4 | 5 | export type IRSS3 = RSS3; 6 | 7 | export default { 8 | set: (privateKey?: string) => { 9 | const config: { 10 | endpoint: string; 11 | privateKey?: string; 12 | } = { 13 | endpoint: 'https://hub.rss3.io', 14 | }; 15 | if (privateKey) { 16 | config.privateKey = privateKey; 17 | } 18 | rss3 = new RSS3(config); 19 | chrome.storage.sync.set({ 20 | privateKey: rss3.persona.privateKey, 21 | }); 22 | return rss3; 23 | }, 24 | get: () => { 25 | return new Promise((resolve) => { 26 | if (rss3) { 27 | resolve(rss3); 28 | } else { 29 | chrome.storage.sync.get(['privateKey'], (result) => { 30 | if (result.privateKey) { 31 | rss3 = new RSS3({ 32 | endpoint: 'https://hub.rss3.io', 33 | privateKey: result.privateKey, 34 | }); 35 | } 36 | resolve(rss3); 37 | }); 38 | } 39 | }); 40 | }, 41 | clear: () => { 42 | return new Promise((resolve) => { 43 | rss3 = null; 44 | chrome.storage.sync.remove('invited'); 45 | chrome.storage.sync.remove('privateKey', () => { 46 | resolve(null); 47 | }); 48 | }); 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /src/common/sync-control.ts: -------------------------------------------------------------------------------- 1 | import reidInvite from './invite'; 2 | import RSS3 from './rss3'; 3 | 4 | async function allow() { 5 | return new Promise(async (resolve) => { 6 | const rss3 = await RSS3.get(); 7 | if (rss3) { 8 | if (await reidInvite.invite.check(rss3.persona.id)) { 9 | resolve(true); 10 | } else { 11 | resolve(false); 12 | } 13 | } else { 14 | resolve(false); 15 | } 16 | }); 17 | } 18 | 19 | export default { 20 | allow: () => { 21 | return allow(); 22 | }, 23 | get: async (): Promise => { 24 | return new Promise(async (resolve) => { 25 | if (await allow()) { 26 | chrome.storage.sync.get(['sync-disabled'], async (result) => { 27 | resolve(!result['sync-disabled']); 28 | }); 29 | } else { 30 | resolve(false); 31 | } 32 | }); 33 | }, 34 | 35 | set: async (status: boolean): Promise => { 36 | return new Promise(async (resolve) => { 37 | if (!status || (await allow())) { 38 | chrome.storage.sync.set( 39 | { 40 | 'sync-disabled': !status, 41 | }, 42 | () => { 43 | resolve(true); 44 | }, 45 | ); 46 | } else { 47 | resolve(false); 48 | } 49 | }); 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /src/components/Avatar.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 63 | 64 | 75 | -------------------------------------------------------------------------------- /src/components/BackButton.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 39 | 40 | 62 | -------------------------------------------------------------------------------- /src/components/Button.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 39 | 40 | 106 | -------------------------------------------------------------------------------- /src/components/CollapseMenu.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 36 | 37 | 44 | -------------------------------------------------------------------------------- /src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 26 | 27 | 32 | -------------------------------------------------------------------------------- /src/components/Input.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 69 | 70 | 105 | -------------------------------------------------------------------------------- /src/components/ItemList.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | 24 | 38 | -------------------------------------------------------------------------------- /src/components/KeyContainer.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 79 | 80 | 126 | -------------------------------------------------------------------------------- /src/components/Logo.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 36 | 37 | 47 | -------------------------------------------------------------------------------- /src/components/LogoTitle.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 40 | -------------------------------------------------------------------------------- /src/components/PopupContainer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/SingleItem.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 57 | 58 | 82 | -------------------------------------------------------------------------------- /src/components/SingleUser.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 36 | 37 | 113 | -------------------------------------------------------------------------------- /src/components/ToggleSwitch.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | 23 | 48 | -------------------------------------------------------------------------------- /src/components/Tooltip.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 35 | 36 | 51 | -------------------------------------------------------------------------------- /src/components/UserList.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | 24 | 39 | -------------------------------------------------------------------------------- /src/components/icons/Icon.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 39 | -------------------------------------------------------------------------------- /src/components/icons/IconAdd.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 34 | -------------------------------------------------------------------------------- /src/components/icons/IconBack.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | -------------------------------------------------------------------------------- /src/components/icons/IconCopy.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 34 | -------------------------------------------------------------------------------- /src/components/icons/IconHide.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 33 | -------------------------------------------------------------------------------- /src/components/icons/IconMore.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | -------------------------------------------------------------------------------- /src/components/icons/IconRight.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | -------------------------------------------------------------------------------- /src/components/icons/IconView.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 32 | -------------------------------------------------------------------------------- /src/components/icons/IconX.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | -------------------------------------------------------------------------------- /src/content-script/components/twitter.ts: -------------------------------------------------------------------------------- 1 | const ReIDLogoOutlined = ` 2 | 3 | `; 4 | 5 | const ReIDLogoWhite = ` 6 | 7 | `; 8 | 9 | export const ReIDLogoColor = ` 10 | 11 | `; 12 | 13 | export const TwitterButtonSync = ` 14 | 15 | 122 |
123 |
124 | ${ReIDLogoOutlined} 125 |
126 | 127 |
128 |
129 |

Sync to RSS3 file?

130 | Toggle RSS3 file syncing for this post 131 |
132 |
133 |
    134 |
  • 135 |
    Syncing on
    136 |
  • 137 |
  • 138 |
    Syncing off
    139 |
  • 140 |
141 |
142 |
143 |
144 | `; 145 | 146 | export const TwitterButtonFollow = ` 147 |
148 |
149 |
150 | ${ReIDLogoWhite} 151 |
152 |
153 | ${ReIDLogoOutlined} 154 |
155 |
156 |
157 | 197 | `; 198 | -------------------------------------------------------------------------------- /src/content-script/handlers/twitter.ts: -------------------------------------------------------------------------------- 1 | import { TwitterButtonSync, TwitterButtonFollow, ReIDLogoColor } from '@/content-script/components/twitter'; 2 | import syncControl from '@/common/sync-control'; 3 | import ipfs from '@/common/ipfs'; 4 | import RSS3 from '@/common/rss3'; 5 | import reidInvite from '@/common/invite'; 6 | import { debounce } from 'lodash'; 7 | 8 | async function checkBind(address: string, privateKey: string) { 9 | if (window.location.pathname === '/home') { 10 | // Only check in homepage 11 | const username = (document.querySelector('main[role=main] a[role=link]'))?.pathname.replace( 12 | '/', 13 | '', 14 | ); 15 | if (username) { 16 | // send bind request 17 | await reidInvite.bind.new(address, 'twitter', username, privateKey); 18 | } 19 | } 20 | } 21 | 22 | async function syncPost() { 23 | const summary = (document.querySelector('[data-testid=tweetTextarea_0]'))?.innerText; 24 | 25 | const attachments = document.querySelectorAll( 26 | '[data-testid="attachments"] img, [data-testid="attachments"] source', 27 | ); 28 | 29 | const contents = await Promise.all( 30 | [...attachments].map(async (attachment) => { 31 | const result = await fetch((attachment).src); 32 | const blob = await result.blob(); 33 | return { 34 | address: [await ipfs.upload(blob)], 35 | mime_type: blob.type, 36 | size_in_bytes: blob.size + '', 37 | }; 38 | }), 39 | ); 40 | 41 | if (await syncControl.get()) { 42 | const rss3 = await RSS3.get(); 43 | if (rss3) { 44 | const twitItem = { 45 | summary, 46 | tags: ['Re: ID', 'Twitter'], 47 | contents, 48 | }; 49 | const tagsInTwit = [...summary?.matchAll(/#(\S+)/g)]; 50 | tagsInTwit.forEach((tagMatch) => { 51 | twitItem.tags.push(tagMatch[1]); 52 | }); 53 | await rss3.item.post(twitItem); 54 | await rss3.persona.sync(); 55 | 56 | await checkBind(rss3.persona.id, rss3.persona.privateKey); 57 | } 58 | } 59 | } 60 | 61 | async function mountControlButton(ele: Element) { 62 | if (ele.lastElementChild?.id !== 'reid-sync-switch') { 63 | ele.insertAdjacentHTML('beforeend', TwitterButtonSync); 64 | 65 | { 66 | // Listen events 67 | function updateSyncStatusClass(enabled: boolean) { 68 | const twiBtnSyncUut = ele.getElementsByClassName('reid-sync-active-status')[0]; 69 | if (twiBtnSyncUut !== null) { 70 | if (enabled) { 71 | twiBtnSyncUut.classList.add('active'); 72 | } else { 73 | twiBtnSyncUut.classList.remove('active'); 74 | } 75 | } 76 | } 77 | 78 | async function setRSS3Sync(enabled: boolean) { 79 | await syncControl.set(enabled); 80 | updateSyncStatusClass(enabled); 81 | window.dispatchEvent(new Event('reid-sync-status-change')); 82 | } 83 | 84 | window.addEventListener('reid-sync-status-change', async () => { 85 | updateSyncStatusClass(await syncControl.get()); 86 | }); 87 | 88 | updateSyncStatusClass(await syncControl.get()); 89 | const twiBtnSyncEnaUut = ele.getElementsByClassName('reid-sync-button-activate')[0]; 90 | if (twiBtnSyncEnaUut !== null) { 91 | twiBtnSyncEnaUut.addEventListener('click', () => { 92 | setRSS3Sync(true); 93 | }); 94 | } 95 | const twiBtnSyncDeUut = ele.getElementsByClassName('reid-sync-button-deactivate')[0]; 96 | if (twiBtnSyncDeUut !== null) { 97 | twiBtnSyncDeUut.addEventListener('click', () => { 98 | setRSS3Sync(false); 99 | }); 100 | } 101 | } 102 | } 103 | } 104 | 105 | function setTwitterColor(baseColor: string) { 106 | if (document.documentElement.style.getPropertyValue('--twitter-theme-color') !== baseColor) { 107 | document.documentElement.style.setProperty('--twitter-theme-color', baseColor); 108 | const hoverBG = baseColor.replace('rgb(', 'rgba(').replace(')', ', 0.1)'); 109 | document.documentElement.style.setProperty('--twitter-theme-hover-background', hoverBG); 110 | } 111 | } 112 | 113 | async function keyEvent(e: KeyboardEvent) { 114 | if (e.key === 'Enter' && e.ctrlKey) { 115 | await syncPost(); 116 | } 117 | } 118 | 119 | async function syncPostWhenTweet(ele: Element) { 120 | const baseColor = window.getComputedStyle(ele, '').backgroundColor; 121 | setTwitterColor(baseColor); 122 | 123 | document.removeEventListener('keydown', keyEvent); 124 | document.addEventListener('keydown', keyEvent); 125 | 126 | ele.removeEventListener('click', syncPost); // if any, prevent multiple trigger 127 | ele.addEventListener('click', syncPost); 128 | } 129 | 130 | async function loadFiles(ele: Element) { 131 | ele.addEventListener('change', (): void => { 132 | const files = (ele).files; 133 | if (files?.[0]) { 134 | for (let i = 0; i < files.length; i++) { 135 | console.log(URL.createObjectURL(files[i])); 136 | } 137 | } 138 | }); 139 | } 140 | 141 | const rss3BindRecords = new Map(); 142 | 143 | async function getRSS3BindAddress(username: string): Promise { 144 | if (!rss3BindRecords.has(username)) { 145 | const userAddr = await reidInvite.bind.searchByUsername('twitter', username); 146 | rss3BindRecords.set(username, userAddr); 147 | return userAddr; 148 | } else { 149 | return rss3BindRecords.get(username); 150 | } 151 | } 152 | 153 | async function mountRSS3FollowButton(ele: Element) { 154 | function updateFollowButtonStatus(fostat: boolean) { 155 | const twiBtnFoUut = document.getElementById('reid-follow'); 156 | if (twiBtnFoUut !== null) { 157 | if (fostat) { 158 | twiBtnFoUut.classList.add('active'); 159 | } else { 160 | twiBtnFoUut.classList.remove('active'); 161 | } 162 | } 163 | } 164 | 165 | async function updateFollowStatus(status: boolean) { 166 | if (typeof userAddr !== 'undefined') { 167 | if (status) { 168 | await rss3?.link.post('following', userAddr); 169 | } else { 170 | await rss3?.link.delete('following', userAddr); 171 | } 172 | } 173 | 174 | updateFollowButtonStatus(status); 175 | 176 | await rss3?.persona.sync(); 177 | } 178 | 179 | const followToggleButton = ele.querySelector('[data-testid$=follow]'); 180 | let followOnTwitterStatus = followToggleButton?.getAttribute('data-testid')?.includes('-unfollow') || false; 181 | 182 | const rss3 = await RSS3.get(); 183 | 184 | let userAddr = await getRSS3BindAddress(window.location.pathname.replace('/', '')); 185 | if (rss3 && typeof userAddr !== 'undefined') { 186 | let followOnRSS3Status = false; 187 | 188 | // User has joined and bind username 189 | 190 | let followList = await rss3.links.get(rss3.persona.id, 'following'); 191 | 192 | if (typeof followList === 'undefined') { 193 | followList = await rss3.links.post({ 194 | type: 'following', 195 | }); 196 | } 197 | if (followList?.list?.includes(userAddr)) { 198 | followOnRSS3Status = true; 199 | } 200 | 201 | if (document.getElementById('reid-follow-button-toggle') === null) { 202 | const tweetButton = document.querySelector('[data-testid=SideNav_NewTweet_Button]'); 203 | if (tweetButton !== null) { 204 | const baseColor = window.getComputedStyle(tweetButton, '').backgroundColor; 205 | setTwitterColor(baseColor); 206 | } 207 | 208 | ele.insertAdjacentHTML('beforebegin', TwitterButtonFollow); 209 | 210 | // Listen events 211 | 212 | const twiBtnFoToUut = document.getElementById('reid-follow-button-toggle'); 213 | twiBtnFoToUut?.addEventListener('click', async () => { 214 | followOnRSS3Status = !followOnRSS3Status; 215 | await updateFollowStatus(followOnRSS3Status); 216 | }); 217 | 218 | // Sync follow / unfollow 219 | followToggleButton?.addEventListener('click', async () => { 220 | followOnTwitterStatus = !followOnTwitterStatus; 221 | followOnRSS3Status = !followOnRSS3Status; 222 | await updateFollowStatus(followOnRSS3Status); 223 | }); 224 | 225 | if (followOnTwitterStatus !== followOnRSS3Status) { 226 | // Sync follow on RSS3 227 | followOnRSS3Status = followOnTwitterStatus; 228 | await updateFollowStatus(followOnRSS3Status); 229 | } 230 | 231 | updateFollowButtonStatus(followOnRSS3Status); 232 | } 233 | } 234 | } 235 | 236 | async function identifyReIDUsers() { 237 | // Identify Re: ID user in home 238 | const allTweets = document.querySelectorAll('[data-testid=tweet]'); 239 | 240 | for (const tweet of allTweets) { 241 | const userNameFiled = tweet.querySelectorAll('div > div > div > div > div > div > a[role=link]')[1]; 242 | const userDisplayNameElement = userNameFiled.querySelector('div[dir=auto] > span'); 243 | if (userDisplayNameElement !== null && userDisplayNameElement.querySelector('.reid-logo') === null) { 244 | const userName = userNameFiled.querySelector('div[dir=ltr] > span')?.innerHTML; 245 | if (typeof userName !== 'undefined') { 246 | const rss3Addr = await getRSS3BindAddress(userName.replace('@', '')); 247 | if (typeof rss3Addr !== 'undefined') { 248 | userDisplayNameElement.insertAdjacentHTML( 249 | 'beforeend', 250 | ``, 251 | ); 252 | } 253 | } 254 | } 255 | } 256 | } 257 | 258 | let initScrollTriggerReIdentify = true; 259 | 260 | async function setIdentifyReIDUsersEvents() { 261 | if (initScrollTriggerReIdentify) { 262 | window.addEventListener('scroll', debounce(identifyReIDUsers, 100)); 263 | initScrollTriggerReIdentify = false; 264 | } 265 | await identifyReIDUsers(); 266 | } 267 | 268 | export default [ 269 | { 270 | // Sync control button and expand box 271 | selector: '[data-testid="toolBar"] div', 272 | callback: mountControlButton, 273 | }, 274 | { 275 | // Sync post when tweet 276 | selector: '[data-testid="tweetButtonInline"], [data-testid="tweetButton"]', 277 | callback: syncPostWhenTweet, 278 | }, 279 | { 280 | // Load files (?) 281 | selector: '[data-testid="fileInput"]', 282 | callback: loadFiles, 283 | }, 284 | { 285 | // Mount RSS3 on button 286 | selector: '[data-testid="placementTracking"]', 287 | callback: mountRSS3FollowButton, 288 | }, 289 | { 290 | // Mount icon on Re: ID users' name 291 | selector: '[id^=accessible-list-]', 292 | callback: setIdentifyReIDUsersEvents, 293 | }, 294 | ]; 295 | -------------------------------------------------------------------------------- /src/content-script/index.ts: -------------------------------------------------------------------------------- 1 | import { observe } from './utils'; 2 | import './locationChange'; 3 | 4 | import syncControl from '@/common/sync-control'; 5 | 6 | import twitterHandlers from './handlers/twitter'; 7 | 8 | (async () => { 9 | if (await syncControl.allow()) { 10 | switch (new URL(window.location.href).host) { 11 | case 'twitter.com': 12 | twitterHandlers.forEach((h) => { 13 | observe(h.selector, h.callback); 14 | }); 15 | break; 16 | default: 17 | // Not supported 18 | break; 19 | } 20 | } 21 | })(); 22 | -------------------------------------------------------------------------------- /src/content-script/locationChange.ts: -------------------------------------------------------------------------------- 1 | // What if user clicks something and our element disappears? 2 | document.addEventListener('click', () => { 3 | setTimeout(() => { 4 | // Location changed 5 | window.dispatchEvent(new Event('locationchange')); 6 | }, 100); 7 | }); 8 | 9 | // Learn more about this hack from https://stackoverflow.com/a/52809105/1986338 10 | history.pushState = ((f) => 11 | function pushState(data: any, title: string, url?: string | null | undefined) { 12 | const ret = f.apply(history, [data, title, url]); 13 | window.dispatchEvent(new Event('pushstate')); 14 | window.dispatchEvent(new Event('locationchange')); 15 | return ret; 16 | })(history.pushState); 17 | 18 | history.replaceState = ((f) => 19 | function replaceState(data: any, title: string, url?: string | null | undefined) { 20 | const ret = f.apply(history, [data, title, url]); 21 | window.dispatchEvent(new Event('replacestate')); 22 | window.dispatchEvent(new Event('locationchange')); 23 | return ret; 24 | })(history.replaceState); 25 | 26 | window.addEventListener('popstate', () => { 27 | window.dispatchEvent(new Event('locationchange')); 28 | }); 29 | -------------------------------------------------------------------------------- /src/content-script/utils.ts: -------------------------------------------------------------------------------- 1 | import { debounce } from 'lodash'; 2 | 3 | const interval = 200; 4 | const maxCount = 10000 / interval; 5 | export function observe(selector: string, callback: (ele: Element) => void) { 6 | let observer: MutationObserver; 7 | let cache: Element; 8 | const cbk = (result: Element) => { 9 | if (result !== cache) { 10 | cache = result; 11 | callback(result); 12 | } 13 | }; 14 | const run = () => { 15 | let currentCount = 0; 16 | const result = document.querySelector(selector); 17 | if (result) { 18 | cbk(result); 19 | } else { 20 | if (observer) { 21 | observer.disconnect(); 22 | } 23 | observer = new MutationObserver( 24 | debounce(() => { 25 | const result = document.querySelector(selector); 26 | if (result) { 27 | observer.disconnect(); 28 | cbk(result); 29 | } else if (currentCount > maxCount) { 30 | observer.disconnect(); 31 | } else { 32 | currentCount++; 33 | } 34 | }, interval), 35 | ); 36 | observer.observe(document.body, { 37 | childList: true, 38 | subtree: true, 39 | }); 40 | } 41 | }; 42 | 43 | run(); 44 | 45 | window.addEventListener('locationchange', () => { 46 | setTimeout(() => { 47 | run(); 48 | }, 0); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /src/options/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/options/index.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import '@/assets/main.css'; 3 | 4 | import App from './App.vue'; 5 | import router from './router'; 6 | 7 | const app = createApp(App); 8 | 9 | app.use(router); 10 | 11 | app.mount('#app'); 12 | -------------------------------------------------------------------------------- /src/options/router.ts: -------------------------------------------------------------------------------- 1 | import * as VueRouter from 'vue-router'; 2 | 3 | import Index from './views/Index.vue'; 4 | 5 | import StartIndex from './views/Start/Index.vue'; 6 | import Start from './views/Start/Base.vue'; 7 | import StartNew from './views/Start/New.vue'; 8 | import StartLogin from './views/Start/Login.vue'; 9 | import StartLoginBack from './views/Start/LoginBack.vue'; 10 | import StartPrivateKey from './views/Start/PrivateKey.vue'; 11 | import StartSavePrivateKey from './views/Start/SavePrivateKey.vue'; 12 | import StartAddress from './views/Start/Address.vue'; 13 | import StartPending from './views/Start/Pending.vue'; 14 | import StartProfile from './views/Start/Profile.vue'; 15 | import StartCongrats from './views/Start/Congrats.vue'; 16 | 17 | import TabsBase from './views/Tabs/Base.vue'; 18 | import TabsHome from './views/Tabs/Home.vue'; 19 | import TabsAdvancedBase from './views/Tabs/Advanced/Base.vue'; 20 | import TabsAdvancedView from './views/Tabs/Advanced/View.vue'; 21 | import TabsAdvancedDelete from './views/Tabs/Advanced/Delete.vue'; 22 | import TabsProfile from './views/Tabs/Profile.vue'; 23 | import TabsInvite from './views/Tabs/Invite.vue'; 24 | import TabsSettings from './views/Tabs/Settings.vue'; 25 | import TabsFollowers from './views/Tabs/FollowUsers/Followers.vue'; 26 | import TabsFollowing from './views/Tabs/FollowUsers/Following.vue'; 27 | 28 | const routes = [ 29 | { 30 | path: '/', 31 | component: Index, 32 | }, 33 | 34 | { 35 | path: '/start', 36 | component: StartIndex, 37 | }, 38 | { 39 | path: '/start', 40 | component: Start, 41 | children: [ 42 | { 43 | path: 'new', 44 | component: StartNew, 45 | }, 46 | { 47 | path: 'login-back', 48 | component: StartLoginBack, 49 | }, 50 | { 51 | path: 'login', 52 | component: StartLogin, 53 | }, 54 | { 55 | path: 'privatekey', 56 | component: StartPrivateKey, 57 | }, 58 | { 59 | path: 'saveprivatekey', 60 | component: StartSavePrivateKey, 61 | }, 62 | { 63 | path: 'address', 64 | component: StartAddress, 65 | }, 66 | { 67 | path: 'pending', 68 | component: StartPending, 69 | }, 70 | { 71 | path: 'profile', 72 | component: StartProfile, 73 | }, 74 | { 75 | path: 'congrats', 76 | component: StartCongrats, 77 | }, 78 | ], 79 | }, 80 | 81 | { 82 | path: '/', 83 | component: TabsBase, 84 | children: [ 85 | { 86 | path: 'home', 87 | component: TabsHome, 88 | }, 89 | { 90 | path: 'advanced', 91 | component: TabsAdvancedBase, 92 | children: [ 93 | { 94 | path: '', 95 | component: TabsAdvancedView, 96 | }, 97 | { 98 | path: 'delete', 99 | component: TabsAdvancedDelete, 100 | }, 101 | ], 102 | }, 103 | { 104 | path: 'profile', 105 | component: TabsProfile, 106 | }, 107 | { 108 | path: 'invite', 109 | component: TabsInvite, 110 | }, 111 | { 112 | path: 'settings', 113 | component: TabsSettings, 114 | }, 115 | { 116 | path: 'followers', 117 | component: TabsFollowers, 118 | }, 119 | { 120 | path: 'following', 121 | component: TabsFollowing, 122 | }, 123 | ], 124 | }, 125 | ]; 126 | 127 | const router = VueRouter.createRouter({ 128 | history: VueRouter.createWebHashHistory(), 129 | routes, 130 | }); 131 | 132 | export default router; 133 | -------------------------------------------------------------------------------- /src/options/views/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/options/views/Start/Address.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/options/views/Start/Base.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 125 | 126 | 138 | -------------------------------------------------------------------------------- /src/options/views/Start/Congrats.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/options/views/Start/Index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 42 | 43 | 48 | -------------------------------------------------------------------------------- /src/options/views/Start/Login.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/options/views/Start/LoginBack.vue: -------------------------------------------------------------------------------- 1 | 15 | 60 | -------------------------------------------------------------------------------- /src/options/views/Start/New.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/options/views/Start/Pending.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/options/views/Start/PrivateKey.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/options/views/Start/Profile.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/options/views/Start/SavePrivateKey.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/options/views/Tabs/Advanced/Base.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 51 | 52 | 93 | -------------------------------------------------------------------------------- /src/options/views/Tabs/Advanced/Delete.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 52 | 53 | 68 | -------------------------------------------------------------------------------- /src/options/views/Tabs/Advanced/View.vue: -------------------------------------------------------------------------------- 1 |