├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── validate_translation.yml ├── .gitignore ├── CNAME ├── Gemfile ├── Gemfile.lock ├── README.md ├── _config.yml ├── _layouts └── default.html ├── admin ├── .gitignore ├── README.md ├── babel.config.js ├── package.json ├── public │ ├── export.html │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── assets │ │ └── img │ │ │ └── icon-webpage.svg │ ├── components │ │ ├── ExportDropdown.vue │ │ ├── ImportDropdown.vue │ │ ├── Navbar.vue │ │ ├── SessionCard.vue │ │ ├── SessionHub.vue │ │ ├── SessionSidebar.vue │ │ ├── Sessions.vue │ │ └── TabSpaceBridge.vue │ ├── config.js │ ├── constants.js │ ├── lang.json │ ├── main.js │ ├── notificationCount.json │ ├── pages │ │ ├── Admin.vue │ │ └── Settings.vue │ ├── store.js │ ├── tips.json │ └── utility.js ├── upload.js ├── validate_translation.js ├── vue.config.js └── yarn.lock ├── assets ├── JetBrainsMono-Regular.woff2 ├── Tab_Space_Demo.mp4 ├── css │ ├── main.css │ └── style.scss └── img │ ├── Download_on_the_Mac_App_Store_Badge_US-UK_RGB_blk_092917.svg │ ├── desktop.png │ ├── preview1.png │ ├── preview2.png │ ├── preview3.png │ ├── publicity.001.jpeg │ ├── publicity.002.jpeg │ ├── publicity.003.jpeg │ ├── publicity.004.jpeg │ ├── publicity.005.jpeg │ ├── publicity.006.jpeg │ ├── publicity.007.jpeg │ ├── publicity.008.jpeg │ ├── publicity.009.jpeg │ └── publicity.010.jpeg ├── beta ├── css │ └── app.fd6330c6.css ├── export.html ├── favicon.ico ├── img │ └── icon-webpage.397ba490.svg ├── index.html └── js │ ├── app.f0d04f6b.js │ └── chunk-vendors.f5e4d5e8.js ├── icon.png ├── index-zh.html ├── index.html ├── redirect.html ├── storage.html └── translate.html /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: yuanzhoucq 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. macOS 10.15.5] 28 | - Version [e.g. 3.7.1] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/validate_translation.yml: -------------------------------------------------------------------------------- 1 | name: Validate Tranlation File for Localization 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | validate: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Setup Node.js environment 16 | uses: actions/setup-node@v2.1.2 17 | with: 18 | node-version: '10.x' 19 | 20 | - name: Run script 21 | run: node admin/validate_translation.js 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | mytab.space -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'github-pages', group: :jekyll_plugins 3 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (4.2.11.1) 5 | i18n (~> 0.7) 6 | minitest (~> 5.1) 7 | thread_safe (~> 0.3, >= 0.3.4) 8 | tzinfo (~> 1.1) 9 | addressable (2.6.0) 10 | public_suffix (>= 2.0.2, < 4.0) 11 | coffee-script (2.4.1) 12 | coffee-script-source 13 | execjs 14 | coffee-script-source (1.11.1) 15 | colorator (1.1.0) 16 | commonmarker (0.17.13) 17 | ruby-enum (~> 0.5) 18 | concurrent-ruby (1.1.5) 19 | dnsruby (1.61.3) 20 | addressable (~> 2.5) 21 | em-websocket (0.5.1) 22 | eventmachine (>= 0.12.9) 23 | http_parser.rb (~> 0.6.0) 24 | ethon (0.12.0) 25 | ffi (>= 1.3.0) 26 | eventmachine (1.2.7) 27 | execjs (2.7.0) 28 | faraday (0.15.4) 29 | multipart-post (>= 1.2, < 3) 30 | ffi (1.11.1) 31 | forwardable-extended (2.6.0) 32 | gemoji (3.0.1) 33 | github-pages (198) 34 | activesupport (= 4.2.11.1) 35 | github-pages-health-check (= 1.16.1) 36 | jekyll (= 3.8.5) 37 | jekyll-avatar (= 0.6.0) 38 | jekyll-coffeescript (= 1.1.1) 39 | jekyll-commonmark-ghpages (= 0.1.5) 40 | jekyll-default-layout (= 0.1.4) 41 | jekyll-feed (= 0.11.0) 42 | jekyll-gist (= 1.5.0) 43 | jekyll-github-metadata (= 2.12.1) 44 | jekyll-mentions (= 1.4.1) 45 | jekyll-optional-front-matter (= 0.3.0) 46 | jekyll-paginate (= 1.1.0) 47 | jekyll-readme-index (= 0.2.0) 48 | jekyll-redirect-from (= 0.14.0) 49 | jekyll-relative-links (= 0.6.0) 50 | jekyll-remote-theme (= 0.3.1) 51 | jekyll-sass-converter (= 1.5.2) 52 | jekyll-seo-tag (= 2.5.0) 53 | jekyll-sitemap (= 1.2.0) 54 | jekyll-swiss (= 0.4.0) 55 | jekyll-theme-architect (= 0.1.1) 56 | jekyll-theme-cayman (= 0.1.1) 57 | jekyll-theme-dinky (= 0.1.1) 58 | jekyll-theme-hacker (= 0.1.1) 59 | jekyll-theme-leap-day (= 0.1.1) 60 | jekyll-theme-merlot (= 0.1.1) 61 | jekyll-theme-midnight (= 0.1.1) 62 | jekyll-theme-minimal (= 0.1.1) 63 | jekyll-theme-modernist (= 0.1.1) 64 | jekyll-theme-primer (= 0.5.3) 65 | jekyll-theme-slate (= 0.1.1) 66 | jekyll-theme-tactile (= 0.1.1) 67 | jekyll-theme-time-machine (= 0.1.1) 68 | jekyll-titles-from-headings (= 0.5.1) 69 | jemoji (= 0.10.2) 70 | kramdown (= 1.17.0) 71 | liquid (= 4.0.0) 72 | listen (= 3.1.5) 73 | mercenary (~> 0.3) 74 | minima (= 2.5.0) 75 | nokogiri (>= 1.8.5, < 2.0) 76 | rouge (= 2.2.1) 77 | terminal-table (~> 1.4) 78 | github-pages-health-check (1.16.1) 79 | addressable (~> 2.3) 80 | dnsruby (~> 1.60) 81 | octokit (~> 4.0) 82 | public_suffix (~> 3.0) 83 | typhoeus (~> 1.3) 84 | html-pipeline (2.12.0) 85 | activesupport (>= 2) 86 | nokogiri (>= 1.4) 87 | http_parser.rb (0.6.0) 88 | i18n (0.9.5) 89 | concurrent-ruby (~> 1.0) 90 | jekyll (3.8.5) 91 | addressable (~> 2.4) 92 | colorator (~> 1.0) 93 | em-websocket (~> 0.5) 94 | i18n (~> 0.7) 95 | jekyll-sass-converter (~> 1.0) 96 | jekyll-watch (~> 2.0) 97 | kramdown (~> 1.14) 98 | liquid (~> 4.0) 99 | mercenary (~> 0.3.3) 100 | pathutil (~> 0.9) 101 | rouge (>= 1.7, < 4) 102 | safe_yaml (~> 1.0) 103 | jekyll-avatar (0.6.0) 104 | jekyll (~> 3.0) 105 | jekyll-coffeescript (1.1.1) 106 | coffee-script (~> 2.2) 107 | coffee-script-source (~> 1.11.1) 108 | jekyll-commonmark (1.3.1) 109 | commonmarker (~> 0.14) 110 | jekyll (>= 3.7, < 5.0) 111 | jekyll-commonmark-ghpages (0.1.5) 112 | commonmarker (~> 0.17.6) 113 | jekyll-commonmark (~> 1) 114 | rouge (~> 2) 115 | jekyll-default-layout (0.1.4) 116 | jekyll (~> 3.0) 117 | jekyll-feed (0.11.0) 118 | jekyll (~> 3.3) 119 | jekyll-gist (1.5.0) 120 | octokit (~> 4.2) 121 | jekyll-github-metadata (2.12.1) 122 | jekyll (~> 3.4) 123 | octokit (~> 4.0, != 4.4.0) 124 | jekyll-mentions (1.4.1) 125 | html-pipeline (~> 2.3) 126 | jekyll (~> 3.0) 127 | jekyll-optional-front-matter (0.3.0) 128 | jekyll (~> 3.0) 129 | jekyll-paginate (1.1.0) 130 | jekyll-readme-index (0.2.0) 131 | jekyll (~> 3.0) 132 | jekyll-redirect-from (0.14.0) 133 | jekyll (~> 3.3) 134 | jekyll-relative-links (0.6.0) 135 | jekyll (~> 3.3) 136 | jekyll-remote-theme (0.3.1) 137 | jekyll (~> 3.5) 138 | rubyzip (>= 1.2.1, < 3.0) 139 | jekyll-sass-converter (1.5.2) 140 | sass (~> 3.4) 141 | jekyll-seo-tag (2.5.0) 142 | jekyll (~> 3.3) 143 | jekyll-sitemap (1.2.0) 144 | jekyll (~> 3.3) 145 | jekyll-swiss (0.4.0) 146 | jekyll-theme-architect (0.1.1) 147 | jekyll (~> 3.5) 148 | jekyll-seo-tag (~> 2.0) 149 | jekyll-theme-cayman (0.1.1) 150 | jekyll (~> 3.5) 151 | jekyll-seo-tag (~> 2.0) 152 | jekyll-theme-dinky (0.1.1) 153 | jekyll (~> 3.5) 154 | jekyll-seo-tag (~> 2.0) 155 | jekyll-theme-hacker (0.1.1) 156 | jekyll (~> 3.5) 157 | jekyll-seo-tag (~> 2.0) 158 | jekyll-theme-leap-day (0.1.1) 159 | jekyll (~> 3.5) 160 | jekyll-seo-tag (~> 2.0) 161 | jekyll-theme-merlot (0.1.1) 162 | jekyll (~> 3.5) 163 | jekyll-seo-tag (~> 2.0) 164 | jekyll-theme-midnight (0.1.1) 165 | jekyll (~> 3.5) 166 | jekyll-seo-tag (~> 2.0) 167 | jekyll-theme-minimal (0.1.1) 168 | jekyll (~> 3.5) 169 | jekyll-seo-tag (~> 2.0) 170 | jekyll-theme-modernist (0.1.1) 171 | jekyll (~> 3.5) 172 | jekyll-seo-tag (~> 2.0) 173 | jekyll-theme-primer (0.5.3) 174 | jekyll (~> 3.5) 175 | jekyll-github-metadata (~> 2.9) 176 | jekyll-seo-tag (~> 2.0) 177 | jekyll-theme-slate (0.1.1) 178 | jekyll (~> 3.5) 179 | jekyll-seo-tag (~> 2.0) 180 | jekyll-theme-tactile (0.1.1) 181 | jekyll (~> 3.5) 182 | jekyll-seo-tag (~> 2.0) 183 | jekyll-theme-time-machine (0.1.1) 184 | jekyll (~> 3.5) 185 | jekyll-seo-tag (~> 2.0) 186 | jekyll-titles-from-headings (0.5.1) 187 | jekyll (~> 3.3) 188 | jekyll-watch (2.2.1) 189 | listen (~> 3.0) 190 | jemoji (0.10.2) 191 | gemoji (~> 3.0) 192 | html-pipeline (~> 2.2) 193 | jekyll (~> 3.0) 194 | kramdown (1.17.0) 195 | liquid (4.0.0) 196 | listen (3.1.5) 197 | rb-fsevent (~> 0.9, >= 0.9.4) 198 | rb-inotify (~> 0.9, >= 0.9.7) 199 | ruby_dep (~> 1.2) 200 | mercenary (0.3.6) 201 | mini_portile2 (2.4.0) 202 | minima (2.5.0) 203 | jekyll (~> 3.5) 204 | jekyll-feed (~> 0.9) 205 | jekyll-seo-tag (~> 2.1) 206 | minitest (5.11.3) 207 | multipart-post (2.1.1) 208 | nokogiri (1.10.4) 209 | mini_portile2 (~> 2.4.0) 210 | octokit (4.14.0) 211 | sawyer (~> 0.8.0, >= 0.5.3) 212 | pathutil (0.16.2) 213 | forwardable-extended (~> 2.6) 214 | public_suffix (3.1.1) 215 | rb-fsevent (0.10.3) 216 | rb-inotify (0.10.0) 217 | ffi (~> 1.0) 218 | rouge (2.2.1) 219 | ruby-enum (0.7.2) 220 | i18n 221 | ruby_dep (1.5.0) 222 | rubyzip (1.2.3) 223 | safe_yaml (1.0.5) 224 | sass (3.7.4) 225 | sass-listen (~> 4.0.0) 226 | sass-listen (4.0.0) 227 | rb-fsevent (~> 0.9, >= 0.9.4) 228 | rb-inotify (~> 0.9, >= 0.9.7) 229 | sawyer (0.8.2) 230 | addressable (>= 2.3.5) 231 | faraday (> 0.8, < 2.0) 232 | terminal-table (1.8.0) 233 | unicode-display_width (~> 1.1, >= 1.1.1) 234 | thread_safe (0.3.6) 235 | typhoeus (1.3.1) 236 | ethon (>= 0.9.0) 237 | tzinfo (1.2.5) 238 | thread_safe (~> 0.1) 239 | unicode-display_width (1.6.0) 240 | 241 | PLATFORMS 242 | ruby 243 | 244 | DEPENDENCIES 245 | github-pages 246 | 247 | BUNDLED WITH 248 | 2.0.2 249 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tab Space is a Safari extension app which is born for improving web browsing productivity. It offers tab management ability and some useful shortcuts for Safari. 2 | 3 | > Note: this repo contains the project site and the web app cooperating with Tab Space in Safari. It **does not contain** the Xcode project which builds the entire app. 4 | 5 | 6 | ## Contributing 7 | ### For translation 8 | - [Translation page](https://mytab.space/translate.html) 9 | ### For web app 10 | ``` 11 | $ git clone https://github.com/yuanzhoucq/Tab-Space.git 12 | $ cd Tab-Space/admin 13 | $ npm install 14 | $ npm run serve 15 | ``` 16 | Note that you need to have Tab Space installed and activated in Safari or the web app will not work properly. 17 | 18 | ## Related links 19 | - [Tab Space Homepage](http://mytab.space) 20 | - [Product Hunt page](https://www.producthunt.com/posts/tab-sapce) 21 | - [中文介绍](https://sspai.com/post/56315) 22 | 23 | 24 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% seo %} 9 | 10 | 13 | 14 | 15 |
16 |
17 |

{{ site.title | default: site.github.repository_name }}

18 | 19 | {% if site.logo %} 20 | Logo 21 | {% endif %} 22 | 23 |

{{ site.description | default: site.github.project_tagline }}

24 | 25 | {% if site.github.is_project_page %} 26 |

View the Project on GitHub {{ site.github.repository_nwo }}

27 | {% endif %} 28 | 29 | Tab Space - Improve your web browsing productivity with Safari | Product Hunt Embed 30 | 31 | 32 | {% if site.github.is_user_page %} 33 |

View My GitHub Profile

34 | {% endif %} 35 | 36 |
37 |
38 | 39 | {{ content }} 40 | 41 |
42 | 51 |
52 | 53 | {% if site.google_analytics %} 54 | 62 | {% endif %} 63 | 64 | 65 | -------------------------------------------------------------------------------- /admin/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /admin/README.md: -------------------------------------------------------------------------------- 1 | # tab-space-admin 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | npm run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /admin/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tab-space-admin", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "postbuild": "node validate_translation.js", 9 | "build-beta": "vue-cli-service build --mode development && rm -r ../beta && mv dist ../beta", 10 | "lint": "vue-cli-service lint" 11 | }, 12 | "dependencies": { 13 | "core-js": "^3.6.4", 14 | "lodash": "^4.17.15", 15 | "mdurl": "^1.0.1", 16 | "velocity-animate": "^1.5.2", 17 | "vue": "^2.6.11", 18 | "vue-autosuggest": "^2.2.0", 19 | "vue-icon": "https://github.com/yuanzhoucq/vue-icon.git", 20 | "vue-js-toggle-button": "^1.3.3", 21 | "vue-loading-template": "^1.3.2", 22 | "vue-router": "^3.1.6", 23 | "vuedraggable": "^2.23.2", 24 | "vuex": "^3.1.3" 25 | }, 26 | "devDependencies": { 27 | "@vue/cli-plugin-babel": "^4.2.0", 28 | "@vue/cli-plugin-eslint": "^4.2.0", 29 | "@vue/cli-service": "^4.2.0", 30 | "ali-oss": "^6.10.0", 31 | "babel-eslint": "^10.0.3", 32 | "eslint": "^6.7.2", 33 | "eslint-plugin-vue": "^6.1.2", 34 | "table-builder": "^2.1.1", 35 | "vue-template-compiler": "^2.6.11" 36 | }, 37 | "eslintConfig": { 38 | "root": true, 39 | "env": { 40 | "node": true 41 | }, 42 | "extends": [ 43 | "plugin:vue/essential", 44 | "eslint:recommended" 45 | ], 46 | "parserOptions": { 47 | "parser": "babel-eslint" 48 | }, 49 | "rules": {} 50 | }, 51 | "browserslist": [ 52 | "> 1%", 53 | "last 2 versions" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /admin/public/export.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tab Space Exported Tabs 6 | 7 | 8 | 9 | 10 | 11 | 194 | 195 | 196 | 197 |
198 |
199 | 200 |
201 |

Tab Space

202 |
203 |
204 |
205 |
{{ lang.all }}
206 |
{{tag}}
208 |
209 | 213 |
214 |
{{ session[2] || (`${lang.saveAt} ${(new Date(Number(session[0]))).Format("yyyy-MM-dd hh:mm:ss")}`) }}
216 | {{lang.restore}} 217 | 221 |
222 |
{{ tag }}
223 |
224 |
225 |
226 |
227 |
228 | 233 |
234 | 235 | 275 | 398 | 399 | -------------------------------------------------------------------------------- /admin/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/admin/public/favicon.ico -------------------------------------------------------------------------------- /admin/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Tab Space 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /admin/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 19 | 85 | -------------------------------------------------------------------------------- /admin/src/assets/img/icon-webpage.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /admin/src/components/ExportDropdown.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 95 | 96 | 119 | -------------------------------------------------------------------------------- /admin/src/components/ImportDropdown.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 94 | 95 | 127 | -------------------------------------------------------------------------------- /admin/src/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 41 | 42 | 61 | -------------------------------------------------------------------------------- /admin/src/components/SessionCard.vue: -------------------------------------------------------------------------------- 1 | 138 | 139 | 379 | 380 | 601 | -------------------------------------------------------------------------------- /admin/src/components/SessionHub.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 46 | 47 | -------------------------------------------------------------------------------- /admin/src/components/SessionSidebar.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 76 | 77 | 136 | -------------------------------------------------------------------------------- /admin/src/components/Sessions.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 100 | 101 | 110 | -------------------------------------------------------------------------------- /admin/src/components/TabSpaceBridge.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 88 | 89 | 92 | -------------------------------------------------------------------------------- /admin/src/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | staticResourceEndpoint: "http://static.mytab.space" 3 | } 4 | -------------------------------------------------------------------------------- /admin/src/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | settings: [ 3 | "ignore-pinned-tabs", 4 | "ignore-duplicated-tabs", 5 | "save-all-windows", 6 | "remove-site-after-click", 7 | "shift-shortcuts", 8 | "disable-shortcuts", 9 | "disable-context-menus" 10 | ], 11 | preferredLanguageKey: "preferred-language", 12 | languages: [ 13 | {code: "ar-ae", name: "Arabic (U.A.E.)"}, 14 | {code: "zh-cn", name: "Chinese"}, 15 | {code: "da", name: "Danish"}, 16 | {code: "nl", name: "Dutch"}, 17 | {code: "en-us", name: "English"}, 18 | {code: "fr", name: "French"}, 19 | {code: "de", name: "German"}, 20 | {code: "it", name: "Italian"}, 21 | {code: "ja", name: "Japanese"}, 22 | {code: "ko", name: "Korean"}, 23 | {code: "pl", name: "Polish"}, 24 | {code: "ru", name: "Russian"}, 25 | {code: "es", name: "Spanish"}, 26 | {code: "sv", name: "Swedish"}, 27 | {code: "tr", name: "Turkish"}, 28 | {code: "uk", name: "Ukrainian"}, 29 | ], 30 | externalBrowser1Key: "externalBrowser1", 31 | externalBrowser2Key: "externalBrowser2", 32 | browsers: [ 33 | "Google Chrome", 34 | "Microsoft Edge", 35 | "FireFox", 36 | "Arc", 37 | "Brave Browser" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /admin/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import App from './App'; 4 | import Admin from './pages/Admin.vue' 5 | import Settings from './pages/Settings.vue' 6 | import feather from 'vue-icon' 7 | 8 | import store from './store' 9 | import config from './config' 10 | Vue.config.productionTip = false 11 | Vue.prototype.$myConfig = config 12 | 13 | Vue.use(VueRouter) 14 | Vue.use(feather, 'v-icon') 15 | 16 | const routes = [ 17 | { path: '/', component: Admin }, 18 | { path: '/settings', component: Settings } 19 | ] 20 | 21 | const router = new VueRouter({ 22 | routes 23 | }) 24 | 25 | new Vue({ 26 | store, 27 | router, 28 | render: h => h(App), 29 | }).$mount('#app') 30 | -------------------------------------------------------------------------------- /admin/src/notificationCount.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 0 3 | } -------------------------------------------------------------------------------- /admin/src/pages/Admin.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 95 | 96 | 255 | -------------------------------------------------------------------------------- /admin/src/pages/Settings.vue: -------------------------------------------------------------------------------- 1 | 104 | 105 | 140 | 141 | 206 | -------------------------------------------------------------------------------- /admin/src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import _ from 'lodash' 4 | import LangData from './lang' 5 | import Constants from './constants' 6 | 7 | Vue.use(Vuex); 8 | 9 | const defaultTabSpaceSettings = { 10 | [Constants.preferredLanguageKey]: navigator.language.toLowerCase() 11 | } 12 | function setLang(languageCode) { 13 | // fallback to en-us for lang.json 14 | let lang = LangData[languageCode || defaultTabSpaceSettings[Constants.preferredLanguageKey]] || LangData["en-us"]; 15 | for (let key in LangData["en-us"]) lang[key] = lang[key] || LangData["en-us"][key]; 16 | return lang; 17 | } 18 | 19 | const store = new Vuex.Store({ 20 | // strict: true, 21 | state: { 22 | lang: setLang(), 23 | bridge: null, 24 | initialRefresh: false, 25 | sessions: [], 26 | keyword: "", 27 | collapse: false, 28 | activeTag: "", 29 | editingSessionUuid: "", 30 | tabSpaceSettings: { 31 | ...defaultTabSpaceSettings 32 | } 33 | }, 34 | getters: { 35 | tags: state => { 36 | let tags = new Set() 37 | state.sessions.forEach(session => { 38 | if (session.tags.map(t => t.name).includes("@Trash")) { 39 | // Tags of session in @Trash should not display 40 | tags.add("@Trash") 41 | } else { 42 | session.tags.forEach(tag => tags.add(tag.name)) 43 | } 44 | 45 | }) 46 | return Array.from(tags).sort() 47 | }, 48 | displaySessions: state => { 49 | let displaySessions = state.sessions; 50 | if (state.activeTag) 51 | if (state.activeTag === 'untagged') { 52 | displaySessions = state.sessions.filter(session => session.tags.length === 0) 53 | } else { 54 | displaySessions = displaySessions.filter(session => session.tags.length > 0 55 | && session.tags.map(tag => tag.name).includes(state.activeTag)) 56 | } 57 | if (state.activeTag !== '@Trash') 58 | displaySessions = displaySessions.filter(session => 59 | !session.tags.map(tag => tag.name).includes('@Trash')) 60 | if (state.keyword) { 61 | displaySessions = displaySessions.filter(session => 62 | _.chain(session) 63 | .pick(["title", "sites", "tags", "comment"]) 64 | .values() 65 | .flatten() 66 | .map(o => _.isObject(o) ? _.values(o) : o) 67 | .flatten() 68 | .value() 69 | .join("§") 70 | .toLowerCase() 71 | .includes(state.keyword.toLowerCase()) 72 | ) 73 | displaySessions = displaySessions.map(s => { 74 | s.comment = state.keyword 75 | return s 76 | }) 77 | } 78 | return displaySessions 79 | } 80 | }, 81 | mutations: { 82 | updateLang(state, {key, value}) { 83 | state.lang[key] = value 84 | }, 85 | setBridge(state, bridge) { 86 | state.bridge = bridge 87 | }, 88 | setSessions(state, newSessions) { 89 | state.sessions = newSessions 90 | state.initialRefresh = true 91 | }, 92 | spliceSessions(state, payload) { 93 | const {start, deleteCount, items} = payload 94 | state.sessions.splice(start, deleteCount, ...items) 95 | }, 96 | setKeyword(state, newKeyword) { 97 | state.keyword = newKeyword 98 | }, 99 | toggleCollapse(state) { 100 | state.collapse = !state.collapse 101 | }, 102 | setActiveTag(state, newTag) { 103 | state.activeTag = newTag 104 | }, 105 | setEditingSessionUuid(state, newId) { 106 | state.editingSessionUuid = newId 107 | }, 108 | setTabSpaceSetting(state, {key, value}) { 109 | let settings = { ...state.tabSpaceSettings } 110 | settings[key] = value 111 | if (key === Constants.preferredLanguageKey && Constants.languages.map(i=>i.code).includes(value)) { 112 | state.lang = setLang(value) 113 | } 114 | state.tabSpaceSettings = settings 115 | } 116 | } 117 | }) 118 | 119 | export default store; 120 | -------------------------------------------------------------------------------- /admin/src/tips.json: -------------------------------------------------------------------------------- 1 | { 2 | "en-us": { 3 | "importantTips": [], 4 | "commonTips": [ 5 | "You can help us translate Tab Space to your mother tongue in settings page.", 6 | "From Tab Space 3.6, optimized iCloud Sync will need macOS 10.15 to work. If you still use macOS 10.13/10.14 and want to use iCloud Sync of Tab Space, please turn off \"Automatic Updates\" in App Store's preferences and do not upgrade Tab Space.", 7 | "If you hit \"save tabs\" and found no tabs were saved, it is because Tab Space just upgraded, please restart Safari to enable it. ", 8 | "Your sessions are now synced across all your devices with iCloud. (macOS 10.15 required)", 9 | "You can now use Drag and Drop to reorganize your sessions or sites.", 10 | "Please rate Tab Space on Mac App Store if you like this app.", 11 | "Tab Space shortcuts will not work when Safari address bar is empty. Because empty address bar means a special page where extension could not be loaded." 12 | ] 13 | }, 14 | "zh-cn": { 15 | "importantTips": [], 16 | "commonTips": [ 17 | "从 Tab Space 3.6 开始,优化的 iCloud 同步功能需要在 macOS 10.15 上运行。如果您仍在使用 macOS 10.13/10.14 并且需要使用 Tab Space 的 iCloud 同步功能,请在 App Store 的偏好设置中关闭“自动更新”,以及请勿升级 Tab Space。", 18 | "如果您发现点击“保存”按钮后标签页没有被保存,这很可能是由于 Tab Space 刚刚更新,重启 Safari 以使其生效即可恢复正常。", 19 | "您的书签组现通过 iCloud 在设备间同步。(要求 macOS 10.15 版本)", 20 | "您现在可以通过拖拽保存的会话或站点以重新组织您的记录。", 21 | "如果您喜欢 Tab Space,请在 Mac App Store 进行评分。", 22 | "Tab Space 在 Safari 地址栏为空时无法使用,因为空地址栏意味着当前处于插件无法访问的特殊页面。" 23 | ] 24 | }, 25 | "ru": { 26 | "importantTips": [], 27 | "commonTips": [ 28 | "Если вы нажали \"сохранить вкладки\" и обнаружили что вкладки не сохранились, это потому-что Tab Space только что обновился, пожалуйста перезапустите Safari, что-бы заработало.", 29 | "Ваш сеанс синхронизирован со всеми вашими устройствами через iCloud. (Требуется macOS 10.15)", 30 | "Вы теперь можете перетаскивать ссылки для организации сеансов или сайтов.", 31 | "Пожалуйста оцените Tab Space на Mac App Store, если вам нравится это приложение.", 32 | "Быстрые комбинации Tab Space не будут работать при пустой поисковой строке Safari. Поскольку пустая поисковая строка означает особую страницу где расширения не работают. " 33 | ] 34 | }, 35 | "tr": { 36 | "importantTips": [], 37 | "commonTips": [ 38 | "Eğer \"save tabs\" tıklayıp hiç kayıtlı sekme bulamadıysanız, bunun nedeni Tab Space yeni güncellenmiş olmasıdır, lütfen Safari'yi yeniden başlatın. ", 39 | "Oturumlarınız bütün cihazlarınız arasında iCloud üzerinden senkronize oluyor. (macOS 10.15 gerektirir)", 40 | "Artık Tut ve Sürükle yaparak oturumlarınızı ve websitelerinizi organize edebilirsiniz.", 41 | "Lütfen Tab Space'yi Mac App Store üzerinden yorumlamayı unutmayın.", 42 | "Safari adres çubuğu boş iken kısayollar çalışmaz. Bunun nedeni boş adres çubuğu Safari uzantılarını bloklar." 43 | ] 44 | }, 45 | "de": { 46 | "importantTips": [], 47 | "commonTips": [ 48 | "Wenn du \"Tabs speichern\" drückst, aber keine Tabs gespeichert wurden, liegt es daran, dass Tab Space gerade aktualisiert wurde. Bitte schließe Safari und starte es erneut, um es zu aktivieren", 49 | "Deine Sitzungen sind nun auf all deinen Geräten über iCloud synchronisiert. (macOS 10.15 wird benötigt)", 50 | "Du kannst nun Drag and Drop nutzen, um deine Sitzungen und Seiten zu reorganisieren.", 51 | "Bitte bewerte Tab Space im Mac App Store, wenn dir die App gefällt.", 52 | "Tab Space Shortcuts funktionieren nicht, wenn die Safari Address-Leiste leer ist. Denn eine leere Adress-Leiste bedeutet, dass eine besondere Seite offen ist. Hier können Erweiterungen nicht geladen werden." 53 | ] 54 | }, 55 | "sv": { 56 | "importantTips": [], 57 | "commonTips": [ 58 | "Om du trycker på \"Spara flikar\" och inga flikar sparas är det för att Tab Space precis uppgraderats. Starta om Safari för att aktivera det igen.", 59 | "Dina sessioner är nu synkade till alla dina enheter i iCloud (kräver macOS 10.15)", 60 | "Du kan dra och släppa för att organisera om dina sessioner och sidor", 61 | "Betygsätt gärna Tab Space i Mac Appstore om du gillar detta program", 62 | "Tab Space kortkommandon kommer inte fungera om Safaris adressfält är tomt. Detta för att ett tomt adressfält laddar in en speciell sida som inte laddar in Safaris tillägg." 63 | ] 64 | }, 65 | "it": { 66 | "importantTips": [], 67 | "commonTips": [ 68 | "Se hai selezionato \"Salva le Tab\" e non hai trovato le tab salvate, è perchè Tab Space si è appena aggiornato. Riavvia Safari per rendere effettivo l'aggiornamento. ", 69 | "Le tue sessioni sono ora sincronizzate su tutti i tuoi device con iCloud (richiede macOS 10.15)", 70 | "Puoi ora usare il Drag and Drop per riorganizzare le tue sessioni o i tuoi siti", 71 | "Per favore, dai un voto a Tab Space sul Mac App Store se ti piace quest'app.", 72 | "Le scorciatoie di Tab Space non funzionano quando la barra di navigazione di Safari è vuota. Questo perchè quando la barra di navigazione è vuota si è in una speciale pagina dove le estensioni sono disattivate" 73 | ] 74 | }, 75 | "ko": { 76 | "importantTips": [], 77 | "commonTips": [ 78 | "Tab Space가 방금 업그레이드 된 경우 \"탭 저장\"을 클릭해도 탭이 저장되지 않을 수 있습니다. Safari를 재시작하여 활성화 하십시오. ", 79 | "저장된 세션은 이제 iCloud를 통해 여러분이 연결한 모든 장치로 동기화 됩니다. (macOS 10.15 필요)", 80 | "드래그 앤 드롭으로 세션이나 사이트 위치를 바꿀 수 있습니다.", 81 | "Tab Space가 마음에 드신다면 Mac App Store에 평가를 남겨주세요.", 82 | "Safari 주소창이 비어 있으면 Tab Space 단축키가 작동하지 않습니다. 빈 주소창은 확장 기능이 작동하지 않는 특수 페이지를 뜻하기 때문입니다." 83 | ] 84 | }, 85 | "pl": { 86 | "importantTips": [], 87 | "commonTips": [ 88 | "Jeśli klikniesz \"zapisz karty\" i żadna z kart nie zostanie zapisana, to dlatego, że Tab Space właśnie został zaktualizowany. Zrestartuj Safari, aby móc używać Tab Space ponownie.", 89 | "Twoje sesje zostały zsynchronizowane pomiędzy wszystkimi urządzeniami w iCloud. (wymagany macOS 10.15)", 90 | "Teraz możesz Przeciągać i Upuszczać swoje sesje i strony, aby dowolnie nimi zarządzać.", 91 | "Proszę oceń Tab Space w Mac App Store jeśli podoba ci się aplikacja.", 92 | "Skróty Tab Space nie działają gdy pasek adresu Safari jest pusty. Pusty pasek adresu oznacza specjalną stronę, która nie obsługuje rozszerzeń." 93 | ] 94 | }, 95 | "es": { 96 | "importantTips": [], 97 | "commonTips": [ 98 | "Si pulsaste \"guardar pestañas\" y las pestañas no se guardaron puede ser porque Tab Space ha sido actualizado recientemente. Por favor reinica Safari para habilitarlo. ", 99 | "Tus sesiones están ahora sincronizadas con todos tus dispositivos utilizando iCloud. (macOS 10.15 requerido)", 100 | "Ahora puedes arrastrar y colocar para reorganizar tus sesiones o tus sitios.", 101 | "Por favor califica Tab Space en el Mac App Store si te gusta la aplicación.", 102 | "Los atajos de Tab Space no funcionan cuando la barra de dirección de Safarí está vacía. TabSpace interpreta una página vacía como algo especial donde la extensión no ha sido cargada." 103 | ] 104 | }, 105 | "nl": { 106 | "importantTips": [], 107 | "commonTips": [ 108 | "Als je op ‘Bewaar tabbladen' drukt en je ziet dat er geen tabbladen zijn opgeslagen, is het omdat Tab Space net is geüpgraded, start dan alsjeblieft Safari opnieuw op om het in te schakelen. ", 109 | "Uw sessies worden nu op al uw apparaten gesynchroniseerd met iCloud. (macOS 10.15 vereist)", 110 | "U kunt nu gebruik maken van Drag and Drop om uw sessies of sites te reorganiseren.", 111 | "Beoordeel Tab Space op de Mac App Store als je dit programma leuk vindt.", 112 | "Tab Space shortcuts werkt niet als de adresbalk van Safari leeg is. Omdat een lege adresbalk een speciale pagina betekent waardoor de extensie niet kon worden geladen." 113 | ] 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /admin/src/utility.js: -------------------------------------------------------------------------------- 1 | Date.prototype.Format = function (fmt) { 2 | var o = { 3 | "y+": this.getFullYear(), 4 | "M+": this.getMonth() + 1, 5 | "d+": this.getDate(), 6 | "h+": this.getHours(), 7 | "m+": this.getMinutes(), 8 | "s+": this.getSeconds(), 9 | "q+": Math.floor((this.getMonth() + 3) / 3), 10 | "S+": this.getMilliseconds() 11 | }; 12 | for (var k in o) { 13 | if (new RegExp("(" + k + ")").test(fmt)) { 14 | if (k == "y+") { 15 | fmt = fmt.replace(RegExp.$1, ("" + o[k]).substr(4 - RegExp.$1.length)); 16 | } else if (k == "S+") { 17 | var lens = RegExp.$1.length; 18 | lens = lens == 1 ? 3 : lens; 19 | fmt = fmt.replace(RegExp.$1, ("00" + o[k]).substr(("" + o[k]).length - 1, lens)); 20 | } else { 21 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); 22 | } 23 | } 24 | } 25 | return fmt; 26 | }; 27 | 28 | 29 | module.exports = { 30 | validURL(str) { 31 | // adopted from https://stackoverflow.com/a/43467144/12251250 32 | // note that a scheme http or https must be present as a valid url here 33 | let url; 34 | try { 35 | url = new URL(str); 36 | } catch (_) { 37 | return false; 38 | } 39 | return url.protocol === "http:" || url.protocol === "https:"; 40 | }, 41 | validateSessions(sessions) { 42 | try { 43 | if (!sessions.every(s => s.sites.length > 0)) throw Error 44 | JSON.parse(JSON.stringify(sessions)) 45 | return true 46 | } catch { 47 | return false 48 | } 49 | }, 50 | Clipboard: (function (window, document, navigator) { 51 | var textArea, 52 | copy; 53 | 54 | function isOS() { 55 | return navigator.userAgent.match(/ipad|iphone/i); 56 | } 57 | 58 | function createTextArea(text) { 59 | textArea = document.createElement('textArea'); 60 | textArea.value = text; 61 | document.body.appendChild(textArea); 62 | } 63 | 64 | function selectText() { 65 | var range, 66 | selection; 67 | 68 | if (isOS()) { 69 | range = document.createRange(); 70 | range.selectNodeContents(textArea); 71 | selection = window.getSelection(); 72 | selection.removeAllRanges(); 73 | selection.addRange(range); 74 | textArea.setSelectionRange(0, 999999); 75 | } else { 76 | textArea.select(); 77 | } 78 | } 79 | 80 | function copyToClipboard() { 81 | document.execCommand('copy'); 82 | document.body.removeChild(textArea); 83 | } 84 | 85 | copy = function (text) { 86 | createTextArea(text); 87 | selectText(); 88 | copyToClipboard(); 89 | }; 90 | 91 | return { 92 | copy: copy 93 | }; 94 | })(window, document, navigator), 95 | 96 | download(filename, text) { 97 | var element = document.createElement('a'); 98 | element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); 99 | element.setAttribute('download', filename); 100 | 101 | element.style.display = 'none'; 102 | document.body.appendChild(element); 103 | 104 | element.click(); 105 | 106 | document.body.removeChild(element); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /admin/upload.js: -------------------------------------------------------------------------------- 1 | const OSS = require('ali-oss') 2 | 3 | const client = new OSS({ 4 | bucket: process.env['TS_BUCKET'], 5 | region: process.env['TS_BUCKET_LOCATION'], 6 | accessKeyId: process.env['ALI_ACCESSKEY'], 7 | accessKeySecret: process.env['ALI_ACCESSSECRET'], 8 | }); 9 | 10 | async function put () { 11 | try { 12 | let result1 = await client.put('tips.json', 'src/tips.json'); 13 | let result2 = await client.put('notificationCount.json', 'src/notificationCount.json'); 14 | console.log(result1, result2); 15 | } catch (e) { 16 | console.log(e); 17 | } 18 | } 19 | 20 | put(); -------------------------------------------------------------------------------- /admin/validate_translation.js: -------------------------------------------------------------------------------- 1 | const assert = require("assert") 2 | const LangData = require("./src/lang.json") 3 | 4 | var langCodes = { 5 | "af": "Afrikaans", 6 | "sq": "Albanian", 7 | "an": "Aragonese", 8 | "ar": "Arabic (Standard)", 9 | "ar-dz": "Arabic (Algeria)", 10 | "ar-bh": "Arabic (Bahrain)", 11 | "ar-eg": "Arabic (Egypt)", 12 | "ar-iq": "Arabic (Iraq)", 13 | "ar-jo": "Arabic (Jordan)", 14 | "ar-kw": "Arabic (Kuwait)", 15 | "ar-lb": "Arabic (Lebanon)", 16 | "ar-ly": "Arabic (Libya)", 17 | "ar-ma": "Arabic (Morocco)", 18 | "ar-om": "Arabic (Oman)", 19 | "ar-qa": "Arabic (Qatar)", 20 | "ar-sa": "Arabic (Saudi Arabia)", 21 | "ar-sy": "Arabic (Syria)", 22 | "ar-tn": "Arabic (Tunisia)", 23 | "ar-ae": "Arabic (U.A.E.)", 24 | "ar-ye": "Arabic (Yemen)", 25 | "hy": "Armenian", 26 | "as": "Assamese", 27 | "ast": "Asturian", 28 | "az": "Azerbaijani", 29 | "eu": "Basque", 30 | "bg": "Bulgarian", 31 | "be": "Belarusian", 32 | "bn": "Bengali", 33 | "bs": "Bosnian", 34 | "br": "Breton", 35 | "bg": "Bulgarian", 36 | "my": "Burmese", 37 | "ca": "Catalan", 38 | "ch": "Chamorro", 39 | "ce": "Chechen", 40 | "zh": "Chinese", 41 | "zh-hk": "Chinese (Hong Kong)", 42 | "zh-cn": "Chinese (PRC)", 43 | "zh-sg": "Chinese (Singapore)", 44 | "zh-tw": "Chinese (Taiwan)", 45 | "cv": "Chuvash", 46 | "co": "Corsican", 47 | "cr": "Cree", 48 | "hr": "Croatian", 49 | "cs": "Czech", 50 | "da": "Danish", 51 | "nl": "Dutch (Standard)", 52 | "nl-be": "Dutch (Belgian)", 53 | "en": "English", 54 | "en-au": "English (Australia)", 55 | "en-bz": "English (Belize)", 56 | "en-ca": "English (Canada)", 57 | "en-ie": "English (Ireland)", 58 | "en-jm": "English (Jamaica)", 59 | "en-nz": "English (New Zealand)", 60 | "en-ph": "English (Philippines)", 61 | "en-za": "English (South Africa)", 62 | "en-tt": "English (Trinidad & Tobago)", 63 | "en-gb": "English (United Kingdom)", 64 | "en-us": "English (United States)", 65 | "en-zw": "English (Zimbabwe)", 66 | "eo": "Esperanto", 67 | "et": "Estonian", 68 | "fo": "Faeroese", 69 | "fa": "Farsi", 70 | "fj": "Fijian", 71 | "fi": "Finnish", 72 | "fr": "French (Standard)", 73 | "fr-be": "French (Belgium)", 74 | "fr-ca": "French (Canada)", 75 | "fr-fr": "French (France)", 76 | "fr-lu": "French (Luxembourg)", 77 | "fr-mc": "French (Monaco)", 78 | "fr-ch": "French (Switzerland)", 79 | "fy": "Frisian", 80 | "fur": "Friulian", 81 | "gd": "Gaelic (Scots)", 82 | "gd-ie": "Gaelic (Irish)", 83 | "gl": "Galacian", 84 | "ka": "Georgian", 85 | "de": "German (Standard)", 86 | "de-at": "German (Austria)", 87 | "de-de": "German (Germany)", 88 | "de-li": "German (Liechtenstein)", 89 | "de-lu": "German (Luxembourg)", 90 | "de-ch": "German (Switzerland)", 91 | "el": "Greek", 92 | "gu": "Gujurati", 93 | "ht": "Haitian", 94 | "he": "Hebrew", 95 | "hi": "Hindi", 96 | "hu": "Hungarian", 97 | "is": "Icelandic", 98 | "id": "Indonesian", 99 | "iu": "Inuktitut", 100 | "ga": "Irish", 101 | "it": "Italian (Standard)", 102 | "it-ch": "Italian (Switzerland)", 103 | "ja": "Japanese", 104 | "kn": "Kannada", 105 | "ks": "Kashmiri", 106 | "kk": "Kazakh", 107 | "km": "Khmer", 108 | "ky": "Kirghiz", 109 | "tlh": "Klingon", 110 | "ko": "Korean", 111 | "ko-kp": "Korean (North Korea)", 112 | "ko-kr": "Korean (South Korea)", 113 | "la": "Latin", 114 | "lv": "Latvian", 115 | "lt": "Lithuanian", 116 | "lb": "Luxembourgish", 117 | "mk": "FYRO Macedonian", 118 | "ms": "Malay", 119 | "ml": "Malayalam", 120 | "mt": "Maltese", 121 | "mi": "Maori", 122 | "mr": "Marathi", 123 | "mo": "Moldavian", 124 | "nv": "Navajo", 125 | "ng": "Ndonga", 126 | "ne": "Nepali", 127 | "no": "Norwegian", 128 | "nb": "Norwegian (Bokmal)", 129 | "nn": "Norwegian (Nynorsk)", 130 | "oc": "Occitan", 131 | "or": "Oriya", 132 | "om": "Oromo", 133 | "fa": "Persian", 134 | "fa-ir": "Persian/Iran", 135 | "pl": "Polish", 136 | "pt": "Portuguese", 137 | "pt-br": "Portuguese (Brazil)", 138 | "pa": "Punjabi", 139 | "pa-in": "Punjabi (India)", 140 | "pa-pk": "Punjabi (Pakistan)", 141 | "qu": "Quechua", 142 | "rm": "Rhaeto-Romanic", 143 | "ro": "Romanian", 144 | "ro-mo": "Romanian (Moldavia)", 145 | "ru": "Russian", 146 | "ru-mo": "Russian (Moldavia)", 147 | "sz": "Sami (Lappish)", 148 | "sg": "Sango", 149 | "sa": "Sanskrit", 150 | "sc": "Sardinian", 151 | "gd": "Scots Gaelic", 152 | "sd": "Sindhi", 153 | "si": "Singhalese", 154 | "sr": "Serbian", 155 | "sk": "Slovak", 156 | "sl": "Slovenian", 157 | "so": "Somani", 158 | "sb": "Sorbian", 159 | "es": "Spanish", 160 | "es-ar": "Spanish (Argentina)", 161 | "es-bo": "Spanish (Bolivia)", 162 | "es-cl": "Spanish (Chile)", 163 | "es-co": "Spanish (Colombia)", 164 | "es-cr": "Spanish (Costa Rica)", 165 | "es-do": "Spanish (Dominican Republic)", 166 | "es-ec": "Spanish (Ecuador)", 167 | "es-sv": "Spanish (El Salvador)", 168 | "es-gt": "Spanish (Guatemala)", 169 | "es-hn": "Spanish (Honduras)", 170 | "es-mx": "Spanish (Mexico)", 171 | "es-ni": "Spanish (Nicaragua)", 172 | "es-pa": "Spanish (Panama)", 173 | "es-py": "Spanish (Paraguay)", 174 | "es-pe": "Spanish (Peru)", 175 | "es-pr": "Spanish (Puerto Rico)", 176 | "es-es": "Spanish (Spain)", 177 | "es-uy": "Spanish (Uruguay)", 178 | "es-ve": "Spanish (Venezuela)", 179 | "sx": "Sutu", 180 | "sw": "Swahili", 181 | "sv": "Swedish", 182 | "sv-fi": "Swedish (Finland)", 183 | "sv-sv": "Swedish (Sweden)", 184 | "ta": "Tamil", 185 | "tt": "Tatar", 186 | "te": "Teluga", 187 | "th": "Thai", 188 | "tig": "Tigre", 189 | "ts": "Tsonga", 190 | "tn": "Tswana", 191 | "tr": "Turkish", 192 | "tk": "Turkmen", 193 | "uk": "Ukrainian", 194 | "hsb": "Upper Sorbian", 195 | "ur": "Urdu", 196 | "ve": "Venda", 197 | "vi": "Vietnamese", 198 | "vo": "Volapuk", 199 | "wa": "Walloon", 200 | "cy": "Welsh", 201 | "xh": "Xhosa", 202 | "ji": "Yiddish", 203 | "zu": "Zulu", 204 | }; 205 | 206 | const enUsKeys = Object.keys(LangData["en-us"]) 207 | let extraKeysLength = 0 208 | let header = {lang: "Language Code", name: "Lanuage", progress: "Progress", missingKeys: "Missing Keys"} 209 | let data = [] 210 | for (let lang in LangData) { 211 | if (lang !== "en-us") { 212 | console.log(`\x1b[1m\x1b[33m\n### Progress of Language "${lang}":\x1b[0m`) 213 | const langKeys = Object.keys(LangData[lang]) 214 | let missingKeys = enUsKeys.filter(key => !langKeys.includes(key)).sort() 215 | const extraKeys = langKeys.filter(key => !enUsKeys.includes(key)).sort() 216 | const progress = `${(100 * (1 - missingKeys.length / enUsKeys.length)).toFixed(2)}%` 217 | missingKeys = missingKeys.join(", ") || "-" 218 | console.log(`* Translation coverage: \x1b[32m${progress}\x1b[0m`) 219 | console.log(`* Missing keys: \x1b[35m${missingKeys}\x1b[0m`) 220 | extraKeys.length > 0 && console.log(`* Extra keys to remove: \x1b[31m${extraKeys}\x1b[0m`) 221 | extraKeysLength += extraKeys.length 222 | data.push({ 223 | lang, name: langCodes[lang], progress, missingKeys 224 | }) 225 | } 226 | } 227 | 228 | try { 229 | const Table = require('table-builder'); 230 | const fs = require('fs') 231 | const table = (new Table({'class': 'greyGridTable'})) 232 | .setHeaders(header) 233 | .setData(data) 234 | .render() 235 | // console.log('\n', table); 236 | let tranlateHtml = fs.readFileSync('../translate.html').toString() 237 | tranlateHtml = tranlateHtml.replace(/\/, table) 238 | fs.writeFileSync('../translate.html', tranlateHtml) 239 | } catch (e) { 240 | console.log('\nTable-builder not installed.') 241 | } 242 | assert(extraKeysLength === 0) 243 | -------------------------------------------------------------------------------- /admin/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: '', 3 | productionSourceMap: false 4 | }; 5 | -------------------------------------------------------------------------------- /assets/JetBrainsMono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/JetBrainsMono-Regular.woff2 -------------------------------------------------------------------------------- /assets/Tab_Space_Demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/Tab_Space_Demo.mp4 -------------------------------------------------------------------------------- /assets/css/main.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "JetBrains Mono"; 3 | src: url('../JetBrainsMono-Regular.woff2') 4 | } 5 | 6 | body { 7 | color: #000; 8 | font-family: "JetBrains Mono", -apple-system, -apple-system-font, sans-serif; 9 | font-size: 1em; 10 | line-height: 1.5 11 | } 12 | 13 | h1, h2, h3, h4, h5, h6 { 14 | font-family: "SF Pro Display", -apple-system, sans-serif; 15 | font-size: 1em; 16 | line-height: 1.2; 17 | margin: 0 0 .75em; 18 | } 19 | 20 | h1 { 21 | font-size: 2.375em; 22 | font-weight: 600 23 | } 24 | 25 | h2 { 26 | font-size: 2.125em; 27 | font-weight: 600 28 | } 29 | 30 | 31 | p { 32 | margin: 0 0 .75em; 33 | color: #444444; 34 | } 35 | 36 | a { 37 | color: #1C8BF3; 38 | text-decoration: none; 39 | transition: color 150ms ease 40 | } 41 | 42 | a:active, a:focus, a:hover { 43 | color: #1568b6 44 | } 45 | 46 | hr { 47 | border-bottom: 1px solid #ddd; 48 | border-left: 0; 49 | border-right: 0; 50 | border-top: 0; 51 | margin: 1.5em 0 52 | } 53 | 54 | img, picture { 55 | margin: 0; 56 | max-width: 100% 57 | } 58 | 59 | body, html { 60 | background-color: #fff; 61 | margin: 0; 62 | overflow-x: hidden 63 | } 64 | 65 | html { 66 | box-sizing: border-box 67 | } 68 | 69 | *, *:before, *:after { 70 | box-sizing: inherit 71 | } 72 | 73 | .container { 74 | display: flex; 75 | margin: 0 auto; 76 | max-width: 1440px 77 | } 78 | 79 | p { 80 | font-size: 1.275em; 81 | font-weight: 300; 82 | line-height: 1.65; 83 | } 84 | 85 | p b { 86 | font-weight: 500 87 | } 88 | 89 | header { 90 | padding: 0 64px 28px; 91 | display: flex; 92 | align-items: flex-end 93 | } 94 | 95 | @media only screen and (max-width: 425px) { 96 | header { 97 | padding-left: 32px; 98 | padding-right: 32px 99 | } 100 | } 101 | 102 | header h1 { 103 | font-size: 1.625em; 104 | margin: 0; 105 | font-weight: 300 106 | } 107 | 108 | header h1 b { 109 | font-weight: 600 110 | } 111 | 112 | header .logo { 113 | display: block; 114 | float: left; 115 | margin-right: 18px; 116 | height: 56px; 117 | width: 56px; 118 | position: relative; 119 | top: -1px 120 | } 121 | 122 | header .title { 123 | clear: right; 124 | display: block; 125 | float: left; 126 | margin-top: 11px 127 | } 128 | 129 | .button:hover, .button:active, .button:focus { 130 | color: #fff 131 | } 132 | 133 | .app-main { 134 | min-height: 735px; 135 | margin: 7vh 0 0; 136 | display: flex; 137 | flex-direction: column; 138 | align-content: center 139 | } 140 | 141 | .app-main .container { 142 | flex: 1 143 | } 144 | 145 | @media only screen and (max-width: 1120px) { 146 | .app-main .container { 147 | display: block 148 | } 149 | } 150 | 151 | .app-main .img { 152 | background-position: top right; 153 | background-repeat: no-repeat; 154 | position: relative; 155 | background-size: 100%; 156 | margin-left: -3%; 157 | width: 925px; 158 | height: 535px; 159 | transform: scale(1.2); 160 | transform-origin: top right; 161 | border-radius: 15px; 162 | } 163 | 164 | @media only screen and (max-width: 1120px) { 165 | .app-main .img { 166 | transform-origin: top center; 167 | background-position: top center; 168 | margin: 0 auto; 169 | width: 96vw; 170 | height: 68vw 171 | } 172 | } 173 | 174 | .app-main video { 175 | background: #000; 176 | width: 100%; 177 | margin-top: 4%; 178 | transform-origin: top center; 179 | transform: scale(0.76); 180 | border-radius: 15px; 181 | } 182 | 183 | 184 | @media only screen and (-webkit-min-device-pixel-ratio: 1.3), only screen and (min--moz-device-pixel-ratio: 1.3), only screen and (-o-min-device-pixel-ratio: 1.3 / 1), only screen and (min-resolution: 125dpi), only screen and (min-resolution: 1.3dppx) { 185 | .app-main .app { 186 | background-size: 402px 259px 187 | } 188 | } 189 | 190 | .app-main .app-article { 191 | flex: 1; 192 | padding: 0 30px 0 0 193 | } 194 | 195 | @media only screen and (max-width: 1120px) { 196 | .app-main .app-article { 197 | padding: 0 198 | } 199 | } 200 | 201 | @media only screen and (max-width: 480px) { 202 | .app-main .app-article { 203 | min-width: inherit; 204 | padding: 0 40px 205 | } 206 | } 207 | 208 | .app-main .app-article .inner { 209 | padding: 100px 30px 0 0; 210 | max-width: 440px 211 | } 212 | 213 | @media only screen and (max-width: 1120px) { 214 | .app-main .app-article .inner { 215 | max-width: 480px; 216 | padding-top: 80px; 217 | padding-right: 0; 218 | margin: 0 auto 219 | } 220 | } 221 | 222 | .app-main .app-article h1 { 223 | margin-bottom: 0.55em 224 | } 225 | 226 | .app-main .app-article p { 227 | margin: 0 0 80px 228 | } 229 | 230 | @media only screen and (max-width: 1120px) { 231 | .app-main .app-article p { 232 | margin-bottom: 60px 233 | } 234 | } 235 | 236 | .app-main .app-article .app-buttons { 237 | display: flex; 238 | flex-wrap: wrap; 239 | justify-content: flex-start 240 | } 241 | 242 | .app-main .app-article .app-buttons.full-width .button { 243 | flex: 1 244 | } 245 | 246 | .app-main .app-article .tooltip { 247 | background: #2D3741; 248 | border-radius: 20px; 249 | padding: 8px 20px; 250 | position: absolute; 251 | color: #fff; 252 | font-size: 0.875rem; 253 | box-shadow: 0 3px 7px 0 rgba(0, 0, 0, 0.16); 254 | font-family: -apple-system, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "Helvetica", "Roboto", "Arial", sans-serif; 255 | top: -30px; 256 | left: calc(50% - 63px); 257 | opacity: 0; 258 | transition: top 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94) 259 | } 260 | 261 | .app-main .app-article .tooltip:after { 262 | height: 0; 263 | width: 0; 264 | border-left: 6px solid transparent; 265 | border-right: 6px solid transparent; 266 | border-top: 6px solid #2D3741; 267 | content: " "; 268 | position: absolute; 269 | top: 100%; 270 | left: calc(50% - 6px) 271 | } 272 | 273 | .app-main .app-article .tooltip.show { 274 | opacity: 1; 275 | top: -45px 276 | } 277 | 278 | @media only screen and (max-width: 1120px) { 279 | .app-main .app-article .button { 280 | flex: 1 281 | } 282 | } 283 | 284 | @media only screen and (max-width: 480px) { 285 | .app-main .app-article .button { 286 | width: 100% 287 | } 288 | } 289 | 290 | .app-main .app-article .button.download { 291 | flex: 1 292 | } 293 | 294 | @media only screen and (max-width: 480px) { 295 | .app-main .app-article .button.download { 296 | margin-bottom: 20px 297 | } 298 | } 299 | 300 | .app-main .app-article .button.sign-up { 301 | background: none; 302 | box-shadow: inset 0 0 0 1px #1C8BF3; 303 | color: #0c79df; 304 | position: relative 305 | } 306 | 307 | @media only screen and (max-width: 1120px) { 308 | .app-main .app-article .button.sign-up { 309 | flex: 0.5 310 | } 311 | } 312 | 313 | @media only screen and (max-width: 480px) { 314 | .app-main .app-article .button.sign-up { 315 | flex: 1 316 | } 317 | } 318 | 319 | .app-main .app-article .requirements { 320 | margin-top: 30px; 321 | display: flex; 322 | margin-bottom: 20px 323 | } 324 | 325 | .app-main .app-article .requirements img { 326 | display: block; 327 | margin-right: 8px; 328 | height: 32px; 329 | width: 32px 330 | } 331 | 332 | .app-main .app-article .requirements .title { 333 | color: rgba(74, 74, 74, 0.95); 334 | font-size: 0.875em; 335 | font-weight: 400; 336 | line-height: 1.1; 337 | -webkit-font-smoothing: antialiased; 338 | -moz-osx-font-smoothing: grayscale 339 | } 340 | 341 | .app-main .app-article .requirements .subtitle { 342 | color: #999; 343 | font-size: 0.75em; 344 | font-weight: 400; 345 | -webkit-font-smoothing: antialiased; 346 | -moz-osx-font-smoothing: grayscale 347 | } 348 | 349 | .app-main .app-article .catalina-setup { 350 | background: #F5F5F5; 351 | border-radius: 50px; 352 | padding: 10px 22px 10px 20px; 353 | margin: 0px 11px 0 0; 354 | font-size: 0.875em 355 | } 356 | 357 | .app-main .app-article .catalina-setup:hover .svg-arrow { 358 | transform: translateY(2px) translateX(4px) 359 | } 360 | 361 | .app-main .app-article .catalina-setup a { 362 | display: flex; 363 | flex-direction: row; 364 | justify-content: space-between 365 | } 366 | 367 | .app-main .app-article .catalina-setup .svg-arrow { 368 | transform: translateY(2px) translateX(0px); 369 | transition: all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94) 370 | } 371 | 372 | .product-info { 373 | background-color: #F5F5F5 374 | } 375 | 376 | .product-info .container { 377 | align-items: center; 378 | display: flex; 379 | padding: 16vh 40px 18vh; 380 | min-height: 660px 381 | } 382 | 383 | @media only screen and (max-width: 1120px) { 384 | .product-info .container { 385 | display: block 386 | } 387 | } 388 | 389 | .product-info .product-copy { 390 | flex: 1; 391 | display: flex; 392 | flex-direction: column; 393 | justify-content: flex-start; 394 | align-items: center; 395 | margin-top: -76px 396 | } 397 | 398 | @media only screen and (max-width: 1120px) { 399 | .product-info .product-copy { 400 | margin-top: 0 401 | } 402 | } 403 | 404 | .product-info .product-copy .inner { 405 | max-width: 440px; 406 | padding-right: 26px 407 | } 408 | 409 | @media only screen and (max-width: 1120px) { 410 | .product-info .product-copy .inner { 411 | max-width: 480px 412 | } 413 | } 414 | 415 | @media only screen and (max-width: 425px) { 416 | .product-info .product-copy .inner { 417 | padding-right: 0 418 | } 419 | } 420 | 421 | .product-info .product-preview { 422 | flex: 1; 423 | height: 400px; 424 | max-width: 631px; 425 | min-width: 631px 426 | } 427 | 428 | @media only screen and (max-width: 1120px) { 429 | .product-info .product-preview { 430 | margin: 60px auto 0; 431 | position: relative; 432 | left: -10px 433 | } 434 | } 435 | 436 | .product-info .product-preview .wrapper { 437 | width: 631px; 438 | position: relative 439 | } 440 | 441 | .product-info .product-preview .product-image-white { 442 | position: absolute; 443 | top: 0; 444 | left: 0; 445 | z-index: 2; 446 | will-change: transform 447 | } 448 | 449 | @media only screen and (max-width: 1120px) { 450 | .product-info .product-preview .product-image-white { 451 | left: 50px 452 | } 453 | } 454 | 455 | @media only screen and (max-width: 500px) { 456 | .product-info .product-preview .product-image-white { 457 | left: 30px 458 | } 459 | } 460 | 461 | .product-info .product-preview .product-image-dark { 462 | position: absolute; 463 | top: -50px; 464 | left: 50px; 465 | z-index: 1; 466 | will-change: transform 467 | } 468 | 469 | @media only screen and (max-width: 1120px) { 470 | .product-info .product-preview .product-image-dark { 471 | left: 20px 472 | } 473 | } 474 | 475 | @media only screen and (max-width: 500px) { 476 | .product-info .product-preview .product-image-dark { 477 | left: 0px 478 | } 479 | } 480 | 481 | .download-footer { 482 | background-color: #F5F5F5; 483 | position: relative; 484 | z-index: 1 485 | } 486 | 487 | .download-footer .container { 488 | padding: 10vh 40px 0 489 | } 490 | 491 | .download-footer .inner { 492 | text-align: center; 493 | margin: 0 auto; 494 | position: relative 495 | } 496 | 497 | .download-footer .button { 498 | margin-bottom: 6vh; 499 | padding: 20px 48px 21px 500 | } 501 | 502 | .download-footer .image { 503 | height: 220px; 504 | overflow: hidden; 505 | transform: translateY(60px); 506 | transition: all 0.45s cubic-bezier(0.25, 0.46, 0.45, 0.94); 507 | opacity: 0 508 | } 509 | 510 | .download-footer .image.animate { 511 | transform: translateY(0px); 512 | opacity: 1 513 | } 514 | 515 | .download-footer img { 516 | width: 672px; 517 | margin: 0 auto; 518 | display: block; 519 | max-width: 100%; 520 | height: auto 521 | } 522 | 523 | footer { 524 | position: relative; 525 | z-index: 2; 526 | background: #fff; 527 | height: 80px 528 | } 529 | 530 | footer .inner { 531 | padding: 30px 64px 0; 532 | width: 100% 533 | } 534 | 535 | footer .author { 536 | text-align: center; 537 | display: block; 538 | font-size: 0.75em; 539 | color: #4A4A4A; 540 | font-weight: 400 541 | } 542 | 543 | footer .author b { 544 | font-weight: 600 545 | } 546 | 547 | @media only screen and (max-width: 548px) { 548 | footer .author { 549 | float: none; 550 | text-align: center; 551 | margin: 30px 0 552 | } 553 | } 554 | 555 | footer .author a { 556 | color: #4A4A4A 557 | } 558 | 559 | footer .author a:hover { 560 | border-bottom: 1px solid #999 561 | } 562 | 563 | @media only screen and (max-width: 548px) { 564 | footer .sharing { 565 | float: none; 566 | text-align: center 567 | } 568 | } 569 | 570 | body { 571 | color: #777; 572 | } 573 | 574 | .loader { 575 | position: absolute; 576 | width: 5em; 577 | text-align: center; 578 | left: 28em; 579 | top: 15em; 580 | z-index: 10000; 581 | background-color: rgba(0, 0, 0, 0.1); 582 | padding: 0.5em; 583 | border-radius: 1em; 584 | } 585 | 586 | @media only screen and (max-width: 1120px) { 587 | .loader { 588 | display: none; 589 | } 590 | } 591 | 592 | svg path, 593 | svg rect { 594 | fill: #FF6700; 595 | } 596 | -------------------------------------------------------------------------------- /assets/css/style.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @import "{{ site.theme }}"; 5 | 6 | @media (min-width: 600px) { 7 | header { 8 | width: 180px; 9 | } 10 | 11 | section { 12 | width: 600px; 13 | } 14 | 15 | footer { 16 | width: 180px; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /assets/img/Download_on_the_Mac_App_Store_Badge_US-UK_RGB_blk_092917.svg: -------------------------------------------------------------------------------- 1 | 2 | Download_on_the_Mac_App_Store_Badge_US-UK_RGB_blk_092917 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /assets/img/desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/img/desktop.png -------------------------------------------------------------------------------- /assets/img/preview1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/img/preview1.png -------------------------------------------------------------------------------- /assets/img/preview2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/img/preview2.png -------------------------------------------------------------------------------- /assets/img/preview3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/img/preview3.png -------------------------------------------------------------------------------- /assets/img/publicity.001.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/img/publicity.001.jpeg -------------------------------------------------------------------------------- /assets/img/publicity.002.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/img/publicity.002.jpeg -------------------------------------------------------------------------------- /assets/img/publicity.003.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/img/publicity.003.jpeg -------------------------------------------------------------------------------- /assets/img/publicity.004.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/img/publicity.004.jpeg -------------------------------------------------------------------------------- /assets/img/publicity.005.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/img/publicity.005.jpeg -------------------------------------------------------------------------------- /assets/img/publicity.006.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/img/publicity.006.jpeg -------------------------------------------------------------------------------- /assets/img/publicity.007.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/img/publicity.007.jpeg -------------------------------------------------------------------------------- /assets/img/publicity.008.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/img/publicity.008.jpeg -------------------------------------------------------------------------------- /assets/img/publicity.009.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/img/publicity.009.jpeg -------------------------------------------------------------------------------- /assets/img/publicity.010.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/assets/img/publicity.010.jpeg -------------------------------------------------------------------------------- /beta/css/app.fd6330c6.css: -------------------------------------------------------------------------------- 1 | .icon{color:#666;width:18px}.icon-filled{fill:salmon}@media (prefers-color-scheme:dark){.icon{color:#888}}.export-dropdown[data-v-d4494ca2]{display:none;position:absolute;margin-left:-40px;padding:3px;font-size:12px;border:1px solid grey;border-radius:5px;text-align:left;background-color:#fbfbfb}@media (prefers-color-scheme:dark){.export-dropdown[data-v-d4494ca2]{background-color:#353535}}.import-dropdown[data-v-607dc878]{display:none;position:absolute;margin-left:-40px;padding:3px;font-size:12px;border:1px solid grey;border-radius:5px;text-align:left;background-color:#fbfbfb}#import-modal[data-v-607dc878]{display:none;position:absolute;width:50vw;top:20vh;left:25vw;right:25vw;z-index:1000;background-color:#fbfbfb;border-radius:10px;border:1px solid #eee}@media (prefers-color-scheme:dark){#import-modal[data-v-607dc878],.import-dropdown[data-v-607dc878]{background-color:#353535}}nav{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}nav>div{margin-right:5px}.export,.import{display:inline-block}#file-input{position:fixed;right:-500px}.session-sidebar[data-v-1e170489]{width:100px;margin-right:25px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-transition:.2s;transition:.2s}.tag-filter[data-v-1e170489]{background-color:#fff;padding:5px;padding-left:10px;border-radius:5px;cursor:pointer}.tag-filter[data-v-1e170489]:hover{background-color:#ffa07a;border-width:0;-webkit-transition:.15s;transition:.15s;margin-right:-5px;color:#f5f5f5}.active-tag[data-v-1e170489]{background-color:salmon;color:#fff!important;-webkit-transition:.1s;transition:.1s;font-weight:700;margin-right:-10px}.tips[data-v-1e170489]{margin-top:20px;font-size:12px;color:#aaa}.upper-border[data-v-1e170489]{border-top:1px solid #eee}@media (prefers-color-scheme:dark){.tag-filter[data-v-1e170489]{background-color:#252525;color:#d0d0d0}.tag-filter[data-v-1e170489]:hover{color:#eee;background-color:rgba(255,160,122,.705)}.active-tag[data-v-1e170489]{background-color:rgba(250,128,114,.719)}}.button[data-v-920e371e]{width:30px;cursor:pointer}.button[data-v-920e371e]:hover{opacity:.7}.session[data-v-6d9cd137]{border-radius:5px;text-decoration:none;width:600px;margin:0 auto 20px;padding:15px 20px 10px 30px;background-color:#fff;-webkit-box-shadow:rgba(46,41,51,.0784314) 0 1px 2px,rgba(71,63,79,.0784314) 0 2px 4px;box-shadow:0 1px 2px rgba(46,41,51,.0784314),0 2px 4px rgba(71,63,79,.0784314);-webkit-transition-duration:.25s,.25s,.25s;transition-duration:.25s,.25s,.25s;-webkit-transition-timing-function:cubic-bezier(.4,0,.2,1),cubic-bezier(.4,0,.2,1),cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1),cubic-bezier(.4,0,.2,1),cubic-bezier(.4,0,.2,1);-webkit-transition-property:padding,-webkit-transform,-webkit-box-shadow;transition-property:padding,-webkit-transform,-webkit-box-shadow;transition-property:transform,box-shadow,padding;transition-property:transform,box-shadow,padding,-webkit-transform,-webkit-box-shadow}.session[data-v-6d9cd137]:hover{-webkit-box-shadow:rgba(46,41,51,.0784314) 0 2px 4px,rgba(71,63,79,.156863) 0 5px 10px;box-shadow:0 2px 4px rgba(46,41,51,.0784314),0 5px 10px rgba(71,63,79,.156863)}.session-tags[data-v-6d9cd137]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:3px}.tag[data-v-6d9cd137]{background-color:#f5f5f5;border:1px solid #ddd;padding:3px;color:#333;font-size:12px;border-radius:8px;margin-right:5px;margin-bottom:5px}.tag[data-v-6d9cd137]:hover{cursor:pointer;text-decoration:line-through;opacity:.8;-webkit-transition:.2s;transition:.2s}.btn-icon[data-v-6d9cd137]{color:#fff;width:14px}.tag-btn[data-v-6d9cd137]{opacity:1;margin-top:2px;width:20px;height:20px;cursor:pointer}.tag-btn[data-v-6d9cd137]:hover{opacity:.7;-webkit-transition:.1s;transition:.1s}.tag-editor input[data-v-6d9cd137]{width:40px;margin-left:5px}.session-title[data-v-6d9cd137]{font-size:20px;font-weight:700;-webkit-transition:-webkit-box-shadow .2s linear;transition:-webkit-box-shadow .2s linear;transition:box-shadow .2s linear;transition:box-shadow .2s linear,-webkit-box-shadow .2s linear;-webkit-box-shadow:inset 0 -10px #fadc23;box-shadow:inset 0 -10px #fadc23;display:inline-block;min-height:26px;max-width:300px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:20px;margin-bottom:5px}.session-title[data-v-6d9cd137]:hover{cursor:pointer}.site-title[data-v-6d9cd137]{white-space:nowrap;overflow-x:hidden;text-overflow:ellipsis}.btn[data-v-6d9cd137]{display:inline-block;padding:3px 5px;margin-right:5px;color:#fff;font-family:Helvetica,Arial,sans-serif;font-size:12px;height:16px;line-height:16px;background-color:#00b51d;cursor:pointer;font-weight:400;border-radius:2px;min-width:40px;text-align:center;-webkit-box-shadow:.5px .5px .5px rgba(46,41,51,.0784314);box-shadow:.5px .5px .5px rgba(46,41,51,.0784314)}.btn[data-v-6d9cd137]:hover{opacity:.8;-webkit-transition:.1s;transition:.1s}.del-btn[data-v-6d9cd137]{background-color:#eb5205}.del-res-btn[data-v-6d9cd137]{background-color:#35abe5}.tag-prompt[data-v-6d9cd137]{font-size:14px;color:#999;margin-top:2px;margin-right:5px}.fav[data-v-6d9cd137]{display:inline-block;margin-right:5px}.fav-img[data-v-6d9cd137],.fav[data-v-6d9cd137]{width:16px;height:16px}.del-item[data-v-6d9cd137]{padding:0 10px;color:#eb5205;display:none;font-weight:700;font-style:italic;cursor:pointer}.tab-edit[data-v-6d9cd137]{background-color:#dedede;outline:none;border-radius:3px;border-width:0;padding-left:5px;width:400px;font-size:14px;height:18px;line-height:18px;margin:4px}@media (prefers-color-scheme:dark){.tab-edit[data-v-6d9cd137]{background-color:#555}.session[data-v-6d9cd137]{background-color:#252525;color:#d0d0d0}.link[data-v-6d9cd137]{background-color:transparent}.session-title[data-v-6d9cd137]{color:#f5f5f5;-webkit-box-shadow:inset 0 -10px #685e02;box-shadow:inset 0 -10px #685e02}.btn[data-v-6d9cd137]{opacity:.9}.fav>img[data-v-6d9cd137]{background-color:#aaa;border-radius:2px}.tag[data-v-6d9cd137]{background-color:#555;border:#555;color:#eee}}.session-placeholder[data-v-3a9a821c]{position:absolute;left:calc(50vw - 55px);top:180px;color:#555;-webkit-transition:.3s;transition:.3s}body{background-image:linear-gradient(-45deg,#efefef,#fbfbfb);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif}li{list-style:none;padding:4px 0 4px 40px;margin-right:100px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}li:hover .del-item{display:block;margin-left:-29.5px;-webkit-transition:.3s;transition:.3s}ul{margin:0 0 0 -45px}#main{min-height:calc(100vh - 35px)}footer{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;font-size:14px}.link{color:#36313d;font-weight:400}.link:hover{-webkit-box-shadow:inset 0 -10px rgba(0,181,29,.17451);box-shadow:inset 0 -10px rgba(0,181,29,.17451);color:#00b51d}#title{width:800px;margin:-20px auto 0}.footer-sep{width:10px}.sessions-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-transition:.2s;transition:.2s;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:left;-ms-flex-pack:left;justify-content:left;width:840px;margin:0 auto}.session-move{-webkit-transition:-webkit-transform .5s;transition:-webkit-transform .5s;transition:transform .5s;transition:transform .5s,-webkit-transform .5s}.export:hover .export-dropdown,.import:hover .import-dropdown{display:block}.msg-prompt{color:red;font-weight:700}.msg{max-width:900px;margin:0 auto;background-color:#ddd}.lose-tabs,.msg{color:#444;padding:3px 10px}.lose-tabs{max-width:600px;text-align:center;text-decoration:underline;margin:20px auto;font-size:12px}#keyword{margin:10px 10px 10px 260px;outline:none;border-radius:4px;border-width:0;height:30px;font-size:16px;color:#444;width:200px;padding-left:10px}.highlight{background-color:#fadd23}@media (prefers-color-scheme:dark){body{background-image:linear-gradient(-45deg,#343434,#343536);color:#eee}input{background-color:#252525}.link,input{color:#d0d0d0}#keyword{color:#eee}.highlight{background-color:rgba(250,221,35,.43);color:#fff}}body[data-v-267db3d0]{background-color:#fbfbfb;font-family:PingFang SC,sans-serif}#main[data-v-267db3d0]{min-height:calc(100vh - 80px)}footer[data-v-267db3d0]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.footer-sep[data-v-267db3d0]{width:10px}.link[data-v-267db3d0]{color:#36313d;font-weight:400}.link[data-v-267db3d0]:hover{-webkit-box-shadow:inset 0 -10px rgba(0,181,29,.17451);box-shadow:inset 0 -10px rgba(0,181,29,.17451);color:#00b51d}#settings[data-v-267db3d0],#title[data-v-267db3d0],.tips[data-v-267db3d0]{max-width:600px;margin:0 auto}code[data-v-267db3d0]{background-color:#ff8c00;padding:4px;border-radius:3px;color:#f5f5f5}.setting[data-v-267db3d0]{margin-bottom:10px}.toggle-label[data-v-267db3d0]{margin-left:15px}@media (prefers-color-scheme:dark){.link[data-v-267db3d0]{color:#eee}} -------------------------------------------------------------------------------- /beta/export.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tab Space Exported Tabs 6 | 7 | 8 | 9 | 10 | 11 | 194 | 195 | 196 | 197 |
198 |
199 | 200 |
201 |

Tab Space

202 |
203 |
204 |
205 |
{{ lang.all }}
206 |
{{tag}}
208 |
209 | 213 |
214 |
{{ session[2] || (`${lang.saveAt} ${(new Date(Number(session[0]))).Format("yyyy-MM-dd hh:mm:ss")}`) }}
216 | {{lang.restore}} 217 | 221 |
222 |
{{ tag }}
223 |
224 |
225 |
226 |
227 |
228 | 233 |
234 | 235 | 275 | 398 | 399 | -------------------------------------------------------------------------------- /beta/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/beta/favicon.ico -------------------------------------------------------------------------------- /beta/img/icon-webpage.397ba490.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /beta/index.html: -------------------------------------------------------------------------------- 1 | Tab Space
-------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanzhoucq/Tab-Space/5a087bb30af18e2ebe8619d8cde247ea0ce75b45/icon.png -------------------------------------------------------------------------------- /index-zh.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | Tab Space - Safari 的标签页和书签管理扩展 15 | 17 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
English
33 | 34 |
35 |
36 |
37 |
38 | 40 | 41 | 45 | 46 | 47 | 51 | 52 | 53 | 57 | 58 | 59 |
60 | 64 |
65 | 66 |
67 |
68 |

69 | 70 | Tab Space 71 |

72 |

把浏览器标签页保存为工作空间

Tab 73 | Space 是一个为 Safari 浏览器提供标签页保存、会话管理和快捷键的应用程序。

74 |
75 | Tab Space download badge 77 |
78 | 82 |
83 |
84 |
要求 macOS 10.13 或更新版本
85 |
86 |
87 |
88 |
89 |
90 |
91 | 92 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | Tab Space - Tab saver & Bookmark manager for Safari 15 | 17 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
中文
33 | 34 |
35 |
36 |
37 |
38 | 40 | 41 | 45 | 46 | 47 | 51 | 52 | 53 | 57 | 58 | 59 |
60 | 64 |
65 | 66 |
67 |
68 |

69 | 70 | Tab Space 71 |

72 |

Save browser tabs as a workspace.

Tab 73 | Space is a nifty extension app that helps you save and manage your tabs as separate sessions, as well as providing some handy shortcuts for Safari.

74 |
75 | Tab Space download badge 77 |
78 |
Changelog 81 |
82 |
83 |
84 |
Requires macOS 10.13 or later
85 |
86 |
87 |
88 |
89 |
90 |
91 | 92 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tab Space Redirection 7 | 24 | 25 | 26 |

Tab Space

27 |

Redirecting...
Due to Safari extension's restrictions, we need this page to redirect to your space page. Please make sure that you have installed Tab Space App, or you will not be redirected.

28 |

重定向中...
由于 Safari 浏览器的限制,我们需要此页面以跳转到你的 Space 记录页面。请确保你已安装 Tab Space App,否则此页面将不会跳转。

29 | 30 | Tab Space 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /storage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tab Space Storage 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /translate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Translate Tab Space 7 | 63 | 64 | 65 | 66 |

Help Us Translate Tab Space

67 |

It's great to have software that's available in our native language as it can make us feel more comfortable. We would like to offer support for more languages in Tab Space, but unfortunately, we don't speak many languages ourselves. That's why we would greatly appreciate your help in translating Tab Space into your language.

68 |

Current Progress

69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
Language CodeLanuageProgressMissing Keys
zh-cnChinese (PRC)100.00%-
ukUkrainian67.80%activeTabs, ctrlD, ctrlK, disable-context-menus, editPrompt, favorites, ignore-duplicated-tabs, location, mergePrompt, movePrompt, newSession, openIn, recentlySaved, remove-site-after-click, shift-shortcuts, tipContents, topPrompt, upgradeSafari, whatsNew
esSpanish79.66%ctrlD, ctrlK, disable-context-menus, editPrompt, location, mergePrompt, movePrompt, newSession, openIn, remove-site-after-click, topPrompt, whatsNew
daDanish67.80%activeTabs, ctrlD, ctrlK, disable-context-menus, editPrompt, favorites, ignore-duplicated-tabs, location, mergePrompt, movePrompt, newSession, openIn, recentlySaved, remove-site-after-click, shift-shortcuts, tipContents, topPrompt, upgradeSafari, whatsNew
plPolish81.36%ctrlD, ctrlK, disable-context-menus, editPrompt, location, mergePrompt, movePrompt, newSession, openIn, remove-site-after-click, topPrompt
ruRussian94.92%ctrlD, ctrlK, openIn
trTurkish77.97%activeTabs, ctrlD, ctrlK, disable-context-menus, editPrompt, location, mergePrompt, movePrompt, newSession, openIn, remove-site-after-click, shift-shortcuts, topPrompt
deGerman (Standard)79.66%activeTabs, ctrlD, ctrlK, editPrompt, location, mergePrompt, movePrompt, newSession, openIn, remove-site-after-click, shift-shortcuts, topPrompt
svSwedish81.36%ctrlD, ctrlK, disable-context-menus, editPrompt, location, mergePrompt, movePrompt, newSession, openIn, remove-site-after-click, topPrompt
itItalian (Standard)77.97%activeTabs, ctrlD, ctrlK, disable-context-menus, editPrompt, location, mergePrompt, movePrompt, newSession, openIn, remove-site-after-click, shift-shortcuts, topPrompt
koKorean77.97%ctrlD, ctrlK, disable-context-menus, editPrompt, location, mergePrompt, movePrompt, newSession, openIn, remove-site-after-click, shift-shortcuts, topPrompt, untagged
ar-aeArabic (U.A.E.)93.22%ctrlD, ctrlK, openIn, untagged
nlDutch (Standard)93.22%ctrlD, ctrlK, openIn, untagged
frFrench (Standard)91.53%ctrlD, ctrlK, disable-context-menus, openIn, remove-site-after-click
jaJapanese93.22%ctrlD, ctrlK, openIn, untagged
84 | 85 |

To add a translation, you simply need to append a block to the [file] and translate the red text under the "en-us" label. If you have any translations or improvements to offer, please send them to joyuercn@icloud.com, and we will quickly add them to Tab Space. Thank you!

87 |

Translation Source

88 | 90 | 91 | 92 | --------------------------------------------------------------------------------