├── src
├── css
│ ├── popup.css
│ └── options.css
├── img
│ ├── close.png
│ ├── icon.png
│ ├── open.png
│ ├── icon-34.png
│ └── icon-128.png
├── js
│ ├── Options
│ │ ├── Translation.svelte
│ │ ├── Shortcuts.svelte
│ │ ├── Contact.svelte
│ │ ├── ShortcutInput.svelte
│ │ ├── index.svelte
│ │ ├── DirectorySelect.svelte
│ │ ├── Miscellaneous.svelte
│ │ ├── Appearance.svelte
│ │ └── Behaviors.svelte
│ ├── popup.js
│ ├── options.js
│ ├── Popup
│ │ ├── URL.svelte
│ │ ├── CustomStyle.svelte
│ │ ├── Children.svelte
│ │ ├── Node.svelte
│ │ ├── index.svelte
│ │ └── behaviors.js
│ ├── background.js
│ └── store.js
├── background.html
├── popup.html
├── options.html
├── manifest.json
└── _locales
│ ├── zh_CN
│ └── messages.json
│ ├── zh_TW
│ └── messages.json
│ └── en
│ └── messages.json
├── .gitignore
├── utils
├── env.js
├── build.js
└── webserver.js
├── README.md
├── package.json
├── LICENSE.md
└── webpack.config.js
/src/css/popup.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/css/options.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | secrets.*.js
4 | build.crx
5 | build.pem
6 |
--------------------------------------------------------------------------------
/src/img/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayamomiji/bookmark-tree/HEAD/src/img/close.png
--------------------------------------------------------------------------------
/src/img/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayamomiji/bookmark-tree/HEAD/src/img/icon.png
--------------------------------------------------------------------------------
/src/img/open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayamomiji/bookmark-tree/HEAD/src/img/open.png
--------------------------------------------------------------------------------
/src/img/icon-34.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayamomiji/bookmark-tree/HEAD/src/img/icon-34.png
--------------------------------------------------------------------------------
/src/img/icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayamomiji/bookmark-tree/HEAD/src/img/icon-128.png
--------------------------------------------------------------------------------
/src/js/Options/Translation.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 | {chrome.i18n.getMessage(key) || `translation missing: ${key}`}
6 |
--------------------------------------------------------------------------------
/src/background.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/js/popup.js:
--------------------------------------------------------------------------------
1 | import '../img/open.png'
2 | import '../img/close.png'
3 |
4 | import Popup from './Popup/index.svelte'
5 |
6 | new Popup({
7 | target: document.body
8 | })
9 |
--------------------------------------------------------------------------------
/utils/env.js:
--------------------------------------------------------------------------------
1 | // tiny wrapper with default env vars
2 | module.exports = {
3 | NODE_ENV: (process.env.NODE_ENV || "development"),
4 | PORT: (process.env.PORT || 3000)
5 | };
6 |
--------------------------------------------------------------------------------
/src/js/options.js:
--------------------------------------------------------------------------------
1 | import '../img/open.png'
2 | import '../img/close.png'
3 |
4 | import Options from './Options/index.svelte'
5 |
6 | new Options({
7 | target: document.body
8 | })
9 |
--------------------------------------------------------------------------------
/src/js/Options/Shortcuts.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | chrome://extensions/shortcuts
9 |
--------------------------------------------------------------------------------
/src/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/utils/build.js:
--------------------------------------------------------------------------------
1 | var webpack = require("webpack"),
2 | config = require("../webpack.config");
3 |
4 | delete config.chromeExtensionBoilerplate;
5 |
6 | webpack(
7 | config,
8 | function (err) { if (err) throw err; }
9 | );
10 |
--------------------------------------------------------------------------------
/src/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ayaya's Bookmark Tree Options
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ayaya's bookmark tree
2 |
3 | A Google Chrome extension that provides a button to show bookmark tree and open bookmark in new tab.
4 |
5 | https://chrome.google.com/webstore/detail/dneehabidhbfdiohdhbhjbbljobchgab
6 |
7 | ## Development
8 |
9 | npm run start
10 |
11 | ## Build
12 |
13 | NODE_ENV=production npm run build
14 |
15 | ## License
16 |
17 | ayaya's bookmark tree is released under the MIT License.
18 |
--------------------------------------------------------------------------------
/src/js/Popup/URL.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
14 |
15 |
16 | {#if $hoveringNode}
17 | {#if $hoveringNode.children}
18 | {$hoveringNode.children.length} item(s).
19 | {:else}
20 | {$hoveringNode.url}
21 | {/if}
22 | {/if}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/js/Options/Contact.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
25 |
--------------------------------------------------------------------------------
/src/js/Popup/CustomStyle.svelte:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 | body {
21 | font-family: {$fontFace};
22 | font-size: {$fontSize};
23 | width: {$width}px;
24 | height: {$height}px;
25 | }
26 |
27 | {$customStyle}
28 |
29 |
--------------------------------------------------------------------------------
/src/js/Options/ShortcutInput.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
25 |
--------------------------------------------------------------------------------
/src/js/Options/index.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
21 |
22 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bookmark-tree",
3 | "version": "0.14.2",
4 | "description": "Provides a button to show bookmark tree and open bookmark in new tab.",
5 | "scripts": {
6 | "build": "node utils/build.js",
7 | "start": "node utils/webserver.js"
8 | },
9 | "devDependencies": {
10 | "clean-webpack-plugin": "3.0.0",
11 | "copy-webpack-plugin": "^5.1.1",
12 | "css-loader": "^3.4.0",
13 | "file-loader": "^5.0.0",
14 | "fs-extra": "8.1.0",
15 | "html-loader": "0.5.5",
16 | "html-webpack-plugin": "3.2.0",
17 | "style-loader": "^1.1.1",
18 | "svelte": "^3.16.7",
19 | "svelte-loader": "^2.13.6",
20 | "webpack": "^4.41.4",
21 | "webpack-cli": "^3.3.10",
22 | "webpack-dev-server": "^3.10.1",
23 | "write-file-webpack-plugin": "4.5.1"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/js/Options/DirectorySelect.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 | {#if directories}
22 |
29 | {/if}
30 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "__MSG_bookmarkTree__",
3 | "description": "__MSG_description__",
4 | "default_locale": "en",
5 | "options_page": "options.html",
6 | "background": {
7 | "page": "background.html"
8 | },
9 | "browser_action": {
10 | "default_popup": "popup.html",
11 | "default_icon": "icon.png"
12 | },
13 | "icons": {
14 | "128": "icon.png"
15 | },
16 | "manifest_version": 2,
17 | "permissions": [
18 | "chrome://favicon/",
19 | "bookmarks"
20 | ],
21 | "commands": {
22 | "openBookmarkTreeInNewTab": {
23 | "description": "__MSG_openBookmarkTreeInNewTab__",
24 | "suggested_key": {
25 | "default": "Alt+B",
26 | "mac": "Command+B"
27 | }
28 | },
29 | "openBookmarkTreeInNewWindow": {
30 | "description": "__MSG_openBookmarkTreeInNewWindow__",
31 | "suggested_key": {
32 | "default": "Alt+K",
33 | "mac": "Command+K"
34 | }
35 | }
36 | },
37 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2020 ayaya zhao
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/utils/webserver.js:
--------------------------------------------------------------------------------
1 | var WebpackDevServer = require("webpack-dev-server"),
2 | webpack = require("webpack"),
3 | config = require("../webpack.config"),
4 | env = require("./env"),
5 | path = require("path");
6 |
7 | var options = (config.chromeExtensionBoilerplate || {});
8 | var excludeEntriesToHotReload = (options.notHotReload || []);
9 |
10 | for (var entryName in config.entry) {
11 | if (excludeEntriesToHotReload.indexOf(entryName) === -1) {
12 | config.entry[entryName] =
13 | [
14 | ("webpack-dev-server/client?http://localhost:" + env.PORT),
15 | "webpack/hot/dev-server"
16 | ].concat(config.entry[entryName]);
17 | }
18 | }
19 |
20 | config.plugins =
21 | [new webpack.HotModuleReplacementPlugin()].concat(config.plugins || []);
22 |
23 | delete config.chromeExtensionBoilerplate;
24 |
25 | var compiler = webpack(config);
26 |
27 | var server =
28 | new WebpackDevServer(compiler, {
29 | hot: true,
30 | contentBase: path.join(__dirname, "../build"),
31 | sockPort: env.PORT,
32 | headers: {
33 | "Access-Control-Allow-Origin": "*"
34 | },
35 | disableHostCheck: true
36 | });
37 |
38 | server.listen(env.PORT);
39 |
--------------------------------------------------------------------------------
/src/js/Popup/Children.svelte:
--------------------------------------------------------------------------------
1 |
40 |
41 | {#each sort(nodes) as child (child.id)}
42 |
43 | {/each}
44 |
--------------------------------------------------------------------------------
/src/js/background.js:
--------------------------------------------------------------------------------
1 | import { readWidth, readHeight } from './store'
2 | import '../img/icon.png'
3 |
4 | chrome.extension.onRequest.addListener(function (req, sender, callback) {
5 | switch (req.type) {
6 | case 'openAllInNewWindow':
7 | openAllInNewWindow(req.directory.children)
8 | break
9 | case 'openAllInNewIncognitoWindow':
10 | openAllInNewWindow(req.directory.children, { incognito: true })
11 | break
12 | }
13 | })
14 |
15 | chrome.commands.onCommand.addListener(function(command) {
16 | switch (command) {
17 | case 'openBookmarkTreeInNewTab':
18 | chrome.tabs.create({
19 | url: chrome.extension.getURL('popup.html?full')
20 | })
21 | break
22 | case 'openBookmarkTreeInNewWindow':
23 | window.open(chrome.extension.getURL('popup.html?full'),
24 | '',
25 | `width=${readWidth()},height=${readHeight()}`)
26 | break
27 | }
28 | })
29 |
30 | function openAllInNewWindow (children, options = {}) {
31 | // open the first bookmark in new window
32 | chrome.windows.create(
33 | { url: children.shift().url, ...options },
34 | // and then open remaining bookmarks in the opened window
35 | window => children.forEach(child => {
36 | !child.children && chrome.tabs.create({
37 | url: child.url,
38 | windowId: window.id,
39 | selected: false
40 | })
41 | })
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/src/js/Popup/Node.svelte:
--------------------------------------------------------------------------------
1 |
27 |
28 |
48 |
49 | event.preventDefault()} on:mouseup={handleClick}
51 | on:mouseenter={setHovering} on:mouseleave={unsetHovering}>
52 |
53 |
{node.title}
54 |
55 |
56 | {#if node.children && $opening}
57 |
58 | {/if}
59 |
--------------------------------------------------------------------------------
/src/js/Popup/index.svelte:
--------------------------------------------------------------------------------
1 |
24 |
25 |
53 |
54 | e.preventDefault()} />
55 |
56 |
57 | {#if !$disableSearchBar}
58 |
60 | {/if}
61 |
62 |
63 | {#if result}
64 |
65 | {:else if rootNode}
66 |
67 | {:else}
68 | Loading...
69 | {/if}
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/js/Options/Miscellaneous.svelte:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
24 |
27 |
28 |
29 |
30 |
32 |
35 |
36 |
37 |
38 |
40 |
43 |
44 |
45 |
46 |
49 |
50 |
51 |
52 |
53 |
56 |
62 |
63 |
--------------------------------------------------------------------------------
/src/js/Options/Appearance.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
25 |
34 |
35 |
36 |
37 |
38 |
47 |
59 |
60 |
61 |
62 |
64 |
65 |
66 | Available selectors are:
67 | body,
68 | .container,
69 | .search,
70 | #tree,
71 | #url,
72 | .node,
73 | .node-title and
74 | .icon
75 |
76 |
--------------------------------------------------------------------------------
/src/js/Popup/behaviors.js:
--------------------------------------------------------------------------------
1 | import { get } from 'svelte/store'
2 | import {
3 | toggleDirectory,
4 | directoryLeftBehavior, directoryMiddleBehavior, directoryRightBehavior,
5 | bookmarkLeftBehavior, bookmarkMiddleBehavior, bookmarkRightBehavior
6 | } from '../store'
7 |
8 | const buttons = ['left', 'middle', 'right']
9 |
10 | function executeBehavior (node, event) {
11 | const isDirectory = !!node.children
12 | // simulates middle click by cmd/ctrl + left click
13 | const buttonIndex =
14 | ((event.metaKey || event.ctrlKey) && event.button === 0) ? 1 : event.button
15 | const button = buttons[buttonIndex]
16 | if (isDirectory) {
17 | executeDirectoryBehavior(node, button)
18 | } else {
19 | executeBookmarkBehavior(node, button)
20 | }
21 | }
22 |
23 | function executeBookmarkBehavior (node, button) {
24 | const behavior = getBookmarkBehavior(button)
25 | switch (behavior) {
26 | case 'openInNewTab':
27 | chrome.tabs.create({ url: node.url })
28 | break
29 | case 'openInCurrentTab':
30 | chrome.tabs.getSelected(null, tab => {
31 | chrome.tabs.update(tab.id, { url: node.url })
32 | })
33 | break
34 | case 'openInBackgroundTab':
35 | chrome.tabs.create({ url: node.url, active: false })
36 | break
37 | case 'openInNewWindow':
38 | chrome.windows.create({ url: node.url })
39 | break
40 | case 'openInIncognitoWindow':
41 | chrome.windows.getCurrent(window => {
42 | if (window.incognito) {
43 | chrome.tabs.create({ url: node.url })
44 | } else {
45 | chrome.windows.create({ url: node.url, incognito: true })
46 | }
47 | })
48 | }
49 | }
50 |
51 | function executeDirectoryBehavior (node, button) {
52 | const behavior = getDirectoryBehavior(button)
53 | switch (behavior) {
54 | case 'toggle':
55 | toggleDirectory(node.id)
56 | break
57 | case 'openAllInCurrentWindow':
58 | node.children.forEach(child => {
59 | !child.children && chrome.tabs.create({ url: child.url })
60 | })
61 | break
62 | case 'openAllInNewWindow':
63 | chrome.extension.sendRequest({
64 | type: 'openAllInNewWindow',
65 | directory: node
66 | })
67 | break
68 | case 'openAllInNewIncognitoWindow':
69 | chrome.extension.sendRequest({
70 | type: 'openAllInNewIncognitoWindow',
71 | directory: node
72 | })
73 | break
74 | }
75 | }
76 |
77 | function getBookmarkBehavior (button) {
78 | switch (button) {
79 | case 'left': return get(bookmarkLeftBehavior)
80 | case 'middle': return get(bookmarkMiddleBehavior)
81 | case 'right': return get(bookmarkRightBehavior)
82 | }
83 | }
84 |
85 | function getDirectoryBehavior (button) {
86 | switch (button) {
87 | case 'left': return get(directoryLeftBehavior)
88 | case 'middle': return get(directoryMiddleBehavior)
89 | case 'right': return get(directoryRightBehavior)
90 | }
91 | }
92 |
93 | export { executeBehavior }
94 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require("webpack"),
2 | path = require("path"),
3 | fileSystem = require("fs"),
4 | env = require("./utils/env"),
5 | CleanWebpackPlugin = require("clean-webpack-plugin").CleanWebpackPlugin,
6 | CopyWebpackPlugin = require("copy-webpack-plugin"),
7 | HtmlWebpackPlugin = require("html-webpack-plugin"),
8 | WriteFilePlugin = require("write-file-webpack-plugin");
9 |
10 | // load the secrets
11 | var alias = {
12 | svelte: path.resolve('node_modules', 'svelte')
13 | };
14 |
15 | var secretsPath = path.join(__dirname, ("secrets." + env.NODE_ENV + ".js"));
16 |
17 | var fileExtensions = ["jpg", "jpeg", "png", "gif", "eot", "otf", "svg", "ttf", "woff", "woff2"];
18 |
19 | if (fileSystem.existsSync(secretsPath)) {
20 | alias["secrets"] = secretsPath;
21 | }
22 |
23 | var options = {
24 | mode: process.env.NODE_ENV || "development",
25 | entry: {
26 | popup: path.join(__dirname, "src", "js", "popup.js"),
27 | options: path.join(__dirname, "src", "js", "options.js"),
28 | background: path.join(__dirname, "src", "js", "background.js")
29 | },
30 | output: {
31 | path: path.join(__dirname, "build"),
32 | filename: "[name].bundle.js"
33 | },
34 | module: {
35 | rules: [
36 | {
37 | test: /\.svelte$/,
38 | exclude: /node_modules/,
39 | use: 'svelte-loader'
40 | },
41 | {
42 | test: /\.css$/,
43 | loader: "style-loader!css-loader",
44 | exclude: /node_modules/
45 | },
46 | {
47 | test: new RegExp('.(' + fileExtensions.join('|') + ')$'),
48 | loader: "file-loader?name=[name].[ext]",
49 | exclude: /node_modules/
50 | },
51 | {
52 | test: /\.html$/,
53 | loader: "html-loader",
54 | exclude: /node_modules/
55 | }
56 | ]
57 | },
58 | resolve: {
59 | alias: alias,
60 | extensions: ['.mjs', '.js', '.svelte'],
61 | mainFields: ['svelte', 'browser', 'module', 'main']
62 | },
63 | plugins: [
64 | // clean the build folder
65 | new CleanWebpackPlugin(),
66 | // expose and write the allowed env vars on the compiled bundle
67 | new webpack.EnvironmentPlugin(["NODE_ENV"]),
68 | new CopyWebpackPlugin([{
69 | from: "src/manifest.json",
70 | transform: function (content, path) {
71 | // generates the manifest file using the package.json informations
72 | return Buffer.from(JSON.stringify({
73 | description: process.env.npm_package_description,
74 | version: process.env.npm_package_version,
75 | ...JSON.parse(content.toString())
76 | }))
77 | }
78 | }, {
79 | from: 'src/_locales', to: '_locales'
80 | }]),
81 | new HtmlWebpackPlugin({
82 | template: path.join(__dirname, "src", "popup.html"),
83 | filename: "popup.html",
84 | chunks: ["popup"]
85 | }),
86 | new HtmlWebpackPlugin({
87 | template: path.join(__dirname, "src", "options.html"),
88 | filename: "options.html",
89 | chunks: ["options"]
90 | }),
91 | new HtmlWebpackPlugin({
92 | template: path.join(__dirname, "src", "background.html"),
93 | filename: "background.html",
94 | chunks: ["background"]
95 | }),
96 | new WriteFilePlugin()
97 | ]
98 | };
99 |
100 | if (env.NODE_ENV === "development") {
101 | options.devtool = "cheap-module-eval-source-map";
102 | }
103 |
104 | module.exports = options;
105 |
--------------------------------------------------------------------------------
/src/_locales/zh_CN/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "bookmarkTree": {
3 | "message": "ayaya 的书籤树"
4 | },
5 | "description": {
6 | "message": "提供一个可以显示书籤树, 并开启书籤的按钮."
7 | },
8 | "optionsPage": {
9 | "message": "书籤树设定选项"
10 | },
11 | "appearance": {
12 | "message": "外观"
13 | },
14 | "behaviors": {
15 | "message": "行为"
16 | },
17 | "misc": {
18 | "message": "杂项"
19 | },
20 | "size": {
21 | "message": "大小"
22 | },
23 | "width": {
24 | "message": "宽"
25 | },
26 | "height": {
27 | "message": "高"
28 | },
29 | "sizeHint": {
30 | "message": "最大限制为 800x600."
31 | },
32 | "font": {
33 | "message": "字型"
34 | },
35 | "fontFace": {
36 | "message": "字体"
37 | },
38 | "fontSize": {
39 | "message": "大小"
40 | },
41 | "fontSizeTips": {
42 | "message": "提示: 你可以使用 px, em, pt, 或 % 做为单位."
43 | },
44 | "customStyle": {
45 | "message": "自订样式"
46 | },
47 | "bookmarkBehaviors": {
48 | "message": "书籤行为"
49 | },
50 | "bookmarkLeftBehavior": {
51 | "message": "当按下左键时..."
52 | },
53 | "bookmarkMiddleBehavior": {
54 | "message": "当按下中键 (或者 Cmd/Ctrl + 左键) 时..."
55 | },
56 | "bookmarkRightBehavior": {
57 | "message": "当按下右键时..."
58 | },
59 | "openInNewTab": {
60 | "message": "开启於新分页"
61 | },
62 | "openInCurrentTab": {
63 | "message": "开启於目前分页"
64 | },
65 | "openInBackgroundTab": {
66 | "message": "开启於背景新分页"
67 | },
68 | "openInNewWindow": {
69 | "message": "开启於新视窗"
70 | },
71 | "openInIncognitoWindow": {
72 | "message": "开启於无痕视窗"
73 | },
74 | "directoryBehaviors": {
75 | "message": "目录行为"
76 | },
77 | "directoryLeftBehavior": {
78 | "message": "当按下左键时..."
79 | },
80 | "directoryMiddleBehavior": {
81 | "message": "当按下中键 (或者 Cmd/Ctrl + 左键) 时..."
82 | },
83 | "directoryRightBehavior": {
84 | "message": "当按下右键时..."
85 | },
86 | "toggle": {
87 | "message": "切换开关目录"
88 | },
89 | "openAllInCurrentWindow": {
90 | "message": "开启所有书籤於目前视窗"
91 | },
92 | "openAllInNewWindow": {
93 | "message": "开启所有书籤於新视窗"
94 | },
95 | "openAllInNewIncognitoWindow": {
96 | "message": "开启所有书籤於新无痕视窗"
97 | },
98 | "shortcuts": {
99 | "message": "快速键"
100 | },
101 | "shortcutsTip": {
102 | "message": "请到这里设定快速键 (请将该网址複製贴上到网址列)"
103 | },
104 | "openBookmarkTreeInNewTab": {
105 | "message": "开启书籤树於新分页"
106 | },
107 | "openBookmarkTreeInNewWindow": {
108 | "message": "开启书籤树於新视窗"
109 | },
110 | "ctrl": {
111 | "message": "Ctrl"
112 | },
113 | "alt": {
114 | "message": "Alt"
115 | },
116 | "meta": {
117 | "message": "Cmd"
118 | },
119 | "rememberOpenedDirectories": {
120 | "message": "记住开启的目录"
121 | },
122 | "hideScrollbar": {
123 | "message": "隐藏捲轴"
124 | },
125 | "closePopupAfterOpenBookmark": {
126 | "message": "开启视窗後关闭弹出视窗"
127 | },
128 | "moveDirectoriesToListTop": {
129 | "message": "移动所有目录到清单最顶端"
130 | },
131 | "disableSearchBar": {
132 | "message": "停用搜寻列"
133 | },
134 | "rootDirectory": {
135 | "message": "根目录"
136 | },
137 | "sortBy": {
138 | "message": "排序方式"
139 | },
140 | "index": {
141 | "message": "索引值"
142 | },
143 | "id": {
144 | "message": "序号"
145 | },
146 | "dateAdded": {
147 | "message": "加入日期"
148 | },
149 | "title": {
150 | "message": "标题"
151 | },
152 | "contact": {
153 | "message": "联络作者"
154 | },
155 | "aboutMe": {
156 | "message": "我是 ayaya, 台湾人. 我使用中文为主, 但您也可以用英文或日文联络我. :)"
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/_locales/zh_TW/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "bookmarkTree": {
3 | "message": "ayaya 的書籤樹"
4 | },
5 | "description": {
6 | "message": "提供一個可以顯示書籤樹, 並開啟書籤的按鈕."
7 | },
8 | "optionsPage": {
9 | "message": "書籤樹設定選項"
10 | },
11 | "appearance": {
12 | "message": "外觀"
13 | },
14 | "behaviors": {
15 | "message": "行為"
16 | },
17 | "misc": {
18 | "message": "雜項"
19 | },
20 | "size": {
21 | "message": "大小"
22 | },
23 | "width": {
24 | "message": "寬"
25 | },
26 | "height": {
27 | "message": "高"
28 | },
29 | "sizeHint": {
30 | "message": "最大限制為 800x600."
31 | },
32 | "font": {
33 | "message": "字型"
34 | },
35 | "fontFace": {
36 | "message": "字體"
37 | },
38 | "fontSize": {
39 | "message": "大小"
40 | },
41 | "fontSizeTips": {
42 | "message": "提示: 你可以使用 px, em, pt, 或 % 做為單位."
43 | },
44 | "customStyle": {
45 | "message": "自訂樣式"
46 | },
47 | "bookmarkBehaviors": {
48 | "message": "書籤行為"
49 | },
50 | "bookmarkLeftBehavior": {
51 | "message": "當按下左鍵時..."
52 | },
53 | "bookmarkMiddleBehavior": {
54 | "message": "當按下中鍵 (或者 Cmd/Ctrl + 左鍵) 時..."
55 | },
56 | "bookmarkRightBehavior": {
57 | "message": "當按下右鍵時..."
58 | },
59 | "openInNewTab": {
60 | "message": "開啟於新分頁"
61 | },
62 | "openInCurrentTab": {
63 | "message": "開啟於目前分頁"
64 | },
65 | "openInBackgroundTab": {
66 | "message": "開啟於背景新分頁"
67 | },
68 | "openInNewWindow": {
69 | "message": "開啟於新視窗"
70 | },
71 | "openInIncognitoWindow": {
72 | "message": "開啟於無痕視窗"
73 | },
74 | "directoryBehaviors": {
75 | "message": "目錄行為"
76 | },
77 | "directoryLeftBehavior": {
78 | "message": "當按下左鍵時..."
79 | },
80 | "directoryMiddleBehavior": {
81 | "message": "當按下中鍵 (或者 Cmd/Ctrl + 左鍵) 時..."
82 | },
83 | "directoryRightBehavior": {
84 | "message": "當按下右鍵時..."
85 | },
86 | "toggle": {
87 | "message": "切換開關目錄"
88 | },
89 | "openAllInCurrentWindow": {
90 | "message": "開啟所有書籤於目前視窗"
91 | },
92 | "openAllInNewWindow": {
93 | "message": "開啟所有書籤於新視窗"
94 | },
95 | "openAllInNewIncognitoWindow": {
96 | "message": "開啟所有書籤於新無痕視窗"
97 | },
98 | "shortcuts": {
99 | "message": "快速鍵"
100 | },
101 | "shortcutsTip": {
102 | "message": "請到這裡設定快速鍵 (請將該網址複製貼上到網址列)"
103 | },
104 | "openBookmarkTreeInNewTab": {
105 | "message": "開啟書籤樹於新分頁"
106 | },
107 | "openBookmarkTreeInNewWindow": {
108 | "message": "開啟書籤樹於新視窗"
109 | },
110 | "ctrl": {
111 | "message": "Ctrl"
112 | },
113 | "alt": {
114 | "message": "Alt"
115 | },
116 | "meta": {
117 | "message": "Cmd"
118 | },
119 | "rememberOpenedDirectories": {
120 | "message": "記住開啟的目錄"
121 | },
122 | "hideScrollbar": {
123 | "message": "隱藏捲軸"
124 | },
125 | "closePopupAfterOpenBookmark": {
126 | "message": "開啟視窗後關閉彈出視窗"
127 | },
128 | "moveDirectoriesToListTop": {
129 | "message": "移動所有目錄到清單最頂端"
130 | },
131 | "disableSearchBar": {
132 | "message": "停用搜尋列"
133 | },
134 | "rootDirectory": {
135 | "message": "根目錄"
136 | },
137 | "sortBy": {
138 | "message": "排序方式"
139 | },
140 | "index": {
141 | "message": "索引值"
142 | },
143 | "id": {
144 | "message": "序號"
145 | },
146 | "dateAdded": {
147 | "message": "加入日期"
148 | },
149 | "title": {
150 | "message": "標題"
151 | },
152 | "contact": {
153 | "message": "聯絡作者"
154 | },
155 | "aboutMe": {
156 | "message": "我是 ayaya, 台灣人. 我使用中文為主, 但您也可以用英文或日文聯絡我. :)"
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/_locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "bookmarkTree": {
3 | "message": "ayaya's Bookmark Tree"
4 | },
5 | "description": {
6 | "message": "Provides a button to show bookmark tree and open bookmark in new tab."
7 | },
8 | "optionsPage": {
9 | "message": "Bookmark Tree Options"
10 | },
11 | "appearance": {
12 | "message": "Appearance"
13 | },
14 | "behaviors": {
15 | "message": "Behaviors"
16 | },
17 | "misc": {
18 | "message": "Miscellaneous"
19 | },
20 | "size": {
21 | "message": "Size"
22 | },
23 | "width": {
24 | "message": "Width"
25 | },
26 | "height": {
27 | "message": "Height"
28 | },
29 | "sizeHint": {
30 | "message": "The max size is 800x600."
31 | },
32 | "font": {
33 | "message": "Font"
34 | },
35 | "fontFace": {
36 | "message": "Font face"
37 | },
38 | "fontSize": {
39 | "message": "Font size"
40 | },
41 | "fontSizeTips": {
42 | "message": "Tips: you can use px, em, pt, or % as unit."
43 | },
44 | "customStyle": {
45 | "message": "Custom style"
46 | },
47 | "bookmarkBehaviors": {
48 | "message": "Bookmark behaviors"
49 | },
50 | "bookmarkLeftBehavior": {
51 | "message": "On left clicked..."
52 | },
53 | "bookmarkMiddleBehavior": {
54 | "message": "On middle (or Cmd/Ctrl + left) clicked..."
55 | },
56 | "bookmarkRightBehavior": {
57 | "message": "On right clicked..."
58 | },
59 | "openInNewTab": {
60 | "message": "Open in new tab"
61 | },
62 | "openInCurrentTab": {
63 | "message": "Open in current tab"
64 | },
65 | "openInBackgroundTab": {
66 | "message": "Open in background tab"
67 | },
68 | "openInNewWindow": {
69 | "message": "Open in new window"
70 | },
71 | "openInIncognitoWindow": {
72 | "message": "Open in incognito window"
73 | },
74 | "directoryBehaviors": {
75 | "message": "Directory behaviors"
76 | },
77 | "directoryLeftBehavior": {
78 | "message": "On left clicked..."
79 | },
80 | "directoryMiddleBehavior": {
81 | "message": "On middle (or Cmd/Ctrl + left) clicked..."
82 | },
83 | "directoryRightBehavior": {
84 | "message": "On right clicked..."
85 | },
86 | "toggle": {
87 | "message": "Toggle"
88 | },
89 | "openAllInCurrentWindow": {
90 | "message": "Open all children in current window"
91 | },
92 | "openAllInNewWindow": {
93 | "message": "Open all children in new window"
94 | },
95 | "openAllInNewIncognitoWindow": {
96 | "message": "Open all children in new incognito window"
97 | },
98 | "shortcuts": {
99 | "message": "Shortcuts"
100 | },
101 | "shortcutsTip": {
102 | "message": "Following this URL to configure shortcuts (copy/paste it to the location bar)"
103 | },
104 | "openBookmarkTreeInNewTab": {
105 | "message": "Open BookmarkTree in new tab"
106 | },
107 | "openBookmarkTreeInNewWindow": {
108 | "message": "Open BookmarkTree in new window"
109 | },
110 | "ctrl": {
111 | "message": "Ctrl"
112 | },
113 | "alt": {
114 | "message": "Alt"
115 | },
116 | "meta": {
117 | "message": "Cmd"
118 | },
119 | "rememberOpenedDirectories": {
120 | "message": "Remember opened directories"
121 | },
122 | "hideScrollbar": {
123 | "message": "Hide scrollbar"
124 | },
125 | "closePopupAfterOpenBookmark": {
126 | "message": "Close popup after open bookmark"
127 | },
128 | "moveDirectoriesToListTop": {
129 | "message": "Move directories to list top"
130 | },
131 | "disableSearchBar": {
132 | "message": "Disable search bar"
133 | },
134 | "rootDirectory": {
135 | "message": "Root directory"
136 | },
137 | "sortBy": {
138 | "message": "Sort by"
139 | },
140 | "index": {
141 | "message": "Index"
142 | },
143 | "id": {
144 | "message": "Id"
145 | },
146 | "dateAdded": {
147 | "message": "Date added"
148 | },
149 | "title": {
150 | "message": "Title"
151 | },
152 | "contact": {
153 | "message": "Contact author"
154 | },
155 | "aboutMe": {
156 | "message": "I'm ayaya zhao, live in Taiwan. I mainly use Chinese, but you can also use English or Japanese to contact me. :)"
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/js/Options/Behaviors.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
32 |
33 |
34 |
35 |
38 |
46 |
47 |
48 |
49 |
52 |
60 |
61 |
62 |
63 |
64 |
65 |
68 |
75 |
76 |
77 |
78 |
81 |
88 |
89 |
90 |
91 |
94 |
101 |
102 |
--------------------------------------------------------------------------------
/src/js/store.js:
--------------------------------------------------------------------------------
1 | import { writable, derived, get } from 'svelte/store'
2 |
3 | // appearance
4 | function readWidth () {
5 | return Math.min(parseInt(localStorage.width) || 300, 800)
6 | }
7 |
8 | function readHeight () {
9 | return Math.min(parseInt(localStorage.height) || 400, 600)
10 | }
11 |
12 | const width = writable(readWidth())
13 |
14 | const height = writable(readHeight())
15 |
16 | const fontFace = writable(localStorage.fontFace || 'inherit')
17 |
18 | const fontSize = writable(localStorage.fontSize || '80%')
19 |
20 | const customStyle = writable(localStorage.customStyle || '')
21 |
22 | // behaviors
23 | if (localStorage.behaviors) { // for backward compatibility ><
24 | const legacyBehaviors = JSON.parse(localStorage.behaviors)
25 | if (!legacyBehaviors.bookmark) {
26 | legacyBehaviors.bookmark = {}
27 | }
28 | if (!legacyBehaviors.directory) {
29 | legacyBehaviors.directory = {}
30 | }
31 | localStorage.bookmarkLeftBehavior = legacyBehaviors.bookmark.left
32 | localStorage.bookmarkMiddleBehavior = legacyBehaviors.bookmark.middle
33 | localStorage.bookmarkRightBehavior = legacyBehaviors.bookmark.right
34 | localStorage.directoryLeftBehavior = legacyBehaviors.directory.left
35 | localStorage.directoryMiddleBehavior = legacyBehaviors.directory.middle
36 | localStorage.directoryRightBehavior = legacyBehaviors.directory.right
37 | localStorage.removeItem('behaviors')
38 | }
39 |
40 | const bookmarkLeftBehavior =
41 | writable(localStorage.bookmarkLeftBehavior || 'openInNewTab')
42 | const bookmarkMiddleBehavior =
43 | writable(localStorage.bookmarkMiddleBehavior || 'openInCurrentTab')
44 | const bookmarkRightBehavior =
45 | writable(localStorage.bookmarkRightBehavior || 'openInBackgroundTab')
46 | const directoryLeftBehavior =
47 | writable(localStorage.directoryLeftBehavior || 'toggle')
48 | const directoryMiddleBehavior =
49 | writable(localStorage.directoryMiddleBehavior || 'openAllInCurrentWindow')
50 | const directoryRightBehavior =
51 | writable(localStorage.directoryRightBehavior || 'openAllInNewWindow')
52 |
53 | // shortcuts
54 | if (localStorage.shortcuts) { // for backward compatibility ><
55 | const legacyShortcuts = JSON.parse(localStorage.shortcuts)
56 | if (!legacyShortcuts.openBookmarkTreeInNewTab) {
57 | legacyShortcuts.openBookmarkTreeInNewTab = {}
58 | }
59 | if (!legacyShortcuts.openBookmarkTreeInNewWindow) {
60 | legacyShortcuts.openBookmarkTreeInNewWindow = {}
61 | }
62 | localStorage.disableShortcuts = legacyShortcuts.disable
63 | localStorage.openBookmarkTreeInNewTab =
64 | JSON.stringify(legacyShortcuts.openBookmarkTreeInNewTab)
65 | localStorage.openBookmarkTreeInNewWindow =
66 | JSON.stringify(legacyShortcuts.openBookmarkTreeInNewWindow)
67 | localStorage.removeItem('shortcuts')
68 | }
69 |
70 | function readDisableShortcuts () {
71 | return localStorage.disableShortcuts == 'true'
72 | }
73 |
74 | function readOpenBookmarkTreeInNewTab () {
75 | return JSON.parse(localStorage.openBookmarkTreeInNewTab ||
76 | '{"modifier":"alt","key":"b"}')
77 | }
78 |
79 | function readOpenBookmarkTreeInNewWindow () {
80 | return JSON.parse(localStorage.openBookmarkTreeInNewWindow ||
81 | '{"modifier":"alt","key":"w"}')
82 | }
83 |
84 | // misc
85 | if (localStorage.rememberOpenedDirectory) { // for backward compatibility ><
86 | localStorage.rememberOpenedDirectories = localStorage.rememberOpenedDirectory
87 | localStorage.removeItem('rememberOpenedDirectory')
88 | }
89 | const rememberOpenedDirectories =
90 | writable(localStorage.rememberOpenedDirectories == 'true')
91 |
92 | const moveDirectoriesToListTop =
93 | writable(localStorage.moveDirectoriesToListTop == 'true')
94 |
95 | const disableSearchBar =
96 | writable(localStorage.disableSearchBar == 'true')
97 |
98 | const rootDirectory =
99 | writable(localStorage.rootDirectory || '0')
100 |
101 | const sortBy = writable(localStorage.sortBy || 'index')
102 |
103 | // node state
104 | const openingDirectories = writable(
105 | get(rememberOpenedDirectories) ?
106 | JSON.parse(localStorage.openingDirectories ||
107 | localStorage.openingDirectory || // for backward compatibility ><
108 | '{}') : {}
109 | )
110 |
111 | const openingDirectory = id =>
112 | derived(openingDirectories, $openingDirectories => $openingDirectories[id])
113 |
114 | const toggleDirectory = id => {
115 | const current = get(openingDirectories)
116 | openingDirectories.set({ ...current, [id]: !current[id] })
117 | }
118 | openingDirectories.subscribe(value => {
119 | if (get(rememberOpenedDirectories)) {
120 | localStorage.openingDirectories = JSON.stringify(value)
121 | }
122 | })
123 |
124 | const hoveringNode = writable()
125 |
126 | export {
127 | readWidth, readHeight,
128 | width, height, fontFace, fontSize, customStyle,
129 | bookmarkLeftBehavior, bookmarkMiddleBehavior, bookmarkRightBehavior,
130 | directoryLeftBehavior, directoryMiddleBehavior, directoryRightBehavior,
131 | rememberOpenedDirectories, moveDirectoriesToListTop, disableSearchBar,
132 | rootDirectory, sortBy, openingDirectory, toggleDirectory, hoveringNode
133 | }
134 |
--------------------------------------------------------------------------------