diff --git a/README.md b/README.md
index 4fde9a5..e98cba8 100644
--- a/README.md
+++ b/README.md
@@ -48,6 +48,11 @@ Octotree uses [GitHub API](https://developer.github.com/v3/) to retrieve reposit
When that happens, Octotree will ask for your [GitHub personal access token](https://help.github.com/articles/creating-an-access-token-for-command-line-use). If you don't already have one, [create one](https://github.com/settings/tokens/new), then copy and paste it into the textbox. Note that the minimal scopes that should be granted are `public_repo` and `repo` (if you need access to private repositories).
+#### Bitbucket
+Octotree uses [Bitbucket API](https://confluence.atlassian.com/bitbucket/repositories-endpoint-1-0-296092719.html) to retrieve repository metadata. By defualt, Octotree will ask for your [Bitbucket App password](https://confluence.atlassian.com/bitbucket/app-passwords-828781300.html). If you don't already have one, [create one](https://bitbucket.org/account/admin/app-passwords) (the minimal requirement is `Repositories`'s `Read` permission), then copy and paste it into the textbox.
+
+Note that Octotree extract your username from your current page by default for calling Bitbucket API. If fail to extract, Octotree will ask you for a token update, then you just need to prepend your username to the token, separated by a colon, i.e. `USERNAME:TOKEN`.
+
### Enterprise URLs
By default, Octotree only works on `github.com`. To support enterprise version (Chrome and Opera only), you must grant Octotree sufficient permissions. Follow these steps to do so:
diff --git a/gulpfile.babel.js b/gulpfile.babel.js
index 161f2c6..7dd2088 100644
--- a/gulpfile.babel.js
+++ b/gulpfile.babel.js
@@ -173,6 +173,7 @@ function buildJs(overrides, ctx) {
'./tmp/template.js',
'./src/constants.js',
'./src/adapters/adapter.js',
+ './src/adapters/bitbucket.js',
'./src/adapters/github.js',
'./src/view.help.js',
'./src/view.error.js',
diff --git a/src/adapters/adapter.js b/src/adapters/adapter.js
index c2aef9a..fc88d8b 100644
--- a/src/adapters/adapter.js
+++ b/src/adapters/adapter.js
@@ -76,7 +76,7 @@ class Adapter {
// encodes but retains the slashes, see #274
const encodedPath = path.split('/').map(encodeURIComponent).join('/')
item.a_attr = {
- href: `/${repo.username}/${repo.reponame}/${type}/${repo.branch}/${encodedPath}`
+ href: this._getItemHref(repo, type, path)
}
}
else if (type === 'commit') {
@@ -273,7 +273,7 @@ class Adapter {
*/
downloadFile(path, fileName) {
const link = document.createElement('a')
- link.setAttribute('href', path.replace(/\/blob\//, '/raw/'))
+ link.setAttribute('href', path.replace(/\/blob\/|\/src\//, '/raw/'))
link.setAttribute('download', fileName)
link.click()
}
@@ -295,4 +295,95 @@ class Adapter {
_getSubmodules(tree, opts, cb) {
throw new Error('Not implemented')
}
+
+ /**
+ * Returns item's href value.
+ * @api protected
+ */
+ _getItemHref(repo, type, encodedPath) {
+ return `/${repo.username}/${repo.reponame}/${type}/${repo.branch}/${encodedPath}`
+ }
+}
+
+
+class PjaxAdapter extends Adapter {
+ constructor() {
+ super(['jquery.pjax.js'])
+
+ $.pjax.defaults.timeout = 0 // no timeout
+ $(document)
+ .on('pjax:send', () => $(document).trigger(EVENT.REQ_START))
+ .on('pjax:end', () => $(document).trigger(EVENT.REQ_END))
+ }
+
+
+ // @override
+ // @param {Object} opts - {pjaxContainer: the specified pjax container}
+ // @api public
+ init($sidebar, opts) {
+ super.init($sidebar)
+
+ opts = opts || {}
+ const pjaxContainer = opts.pjaxContainer
+
+ if (!window.MutationObserver) return
+
+ // Some host switch pages using pjax. This observer detects if the pjax container
+ // has been updated with new contents and trigger layout.
+ const pageChangeObserver = new window.MutationObserver(() => {
+ // Trigger location change, can't just relayout as Octotree might need to
+ // hide/show depending on whether the current page is a code page or not.
+ return $(document).trigger(EVENT.LOC_CHANGE)
+ })
+
+ if (pjaxContainer) {
+ pageChangeObserver.observe(pjaxContainer, {
+ childList: true,
+ })
+ }
+ else { // Fall back if DOM has been changed
+ let firstLoad = true, href, hash
+
+ function detectLocChange() {
+ if (location.href !== href || location.hash !== hash) {
+ href = location.href
+ hash = location.hash
+
+ // If this is the first time this is called, no need to notify change as
+ // Octotree does its own initialization after loading options.
+ if (firstLoad) {
+ firstLoad = false
+ }
+ else {
+ setTimeout(() => {
+ $(document).trigger(EVENT.LOC_CHANGE)
+ }, 300) // Wait a bit for pjax DOM change
+ }
+ }
+ setTimeout(detectLocChange, 200)
+ }
+
+ detectLocChange()
+ }
+ }
+
+
+ // @override
+ // @param {Object} opts - {$pjax_container: jQuery object}
+ // @api public
+ selectFile(path, opts) {
+ opts = opts || {}
+ const $pjaxContainer = opts.$pjaxContainer
+
+ if ($pjaxContainer.length) {
+ $.pjax({
+ // needs full path for pjax to work with Firefox as per cross-domain-content setting
+ url: location.protocol + '//' + location.host + path,
+ container: $pjaxContainer
+ })
+ }
+ else { // falls back
+ super.selectFile(path)
+ }
+ }
}
diff --git a/src/adapters/bitbucket.js b/src/adapters/bitbucket.js
new file mode 100644
index 0000000..f00be00
--- /dev/null
+++ b/src/adapters/bitbucket.js
@@ -0,0 +1,212 @@
+const BB_RESERVED_USER_NAMES = [
+ 'account', 'dashboard', 'integrations', 'product',
+ 'repo', 'snippets', 'support', 'whats-new'
+]
+const BB_RESERVED_REPO_NAMES = []
+const BB_RESERVED_TYPES = ['raw']
+const BB_404_SEL = '#error.404'
+const BB_PJAX_CONTAINER_SEL = '#source-container'
+
+class Bitbucket extends PjaxAdapter {
+
+ constructor() {
+ super(['jquery.pjax.js'])
+ }
+
+ // @override
+ init($sidebar) {
+ const pjaxContainer = $(BB_PJAX_CONTAINER_SEL)[0]
+ super.init($sidebar, {'pjaxContainer': pjaxContainer})
+ }
+
+ // @override
+ getCssClass() {
+ return 'octotree_bitbucket_sidebar'
+ }
+
+ // @override
+ getCreateTokenUrl() {
+ return `${location.protocol}//${location.host}/account/admin/app-passwords/new`
+ }
+
+ // @override
+ updateLayout(togglerVisible, sidebarVisible, sidebarWidth) {
+ $('.octotree_toggle').css('right', sidebarVisible ? '' : -44)
+ $('.aui-header').css('padding-left', sidebarVisible ? '' : 56)
+ $('html').css('margin-left', sidebarVisible ? sidebarWidth : '')
+ }
+
+ // @override
+ getRepoFromPath(showInNonCodePage, currentRepo, token, cb) {
+
+ // 404 page, skip
+ if ($(BB_404_SEL).length) {
+ return cb()
+ }
+
+ // (username)/(reponame)[/(type)]
+ const match = window.location.pathname.match(/([^\/]+)\/([^\/]+)(?:\/([^\/]+))?/)
+ if (!match) {
+ return cb()
+ }
+
+ const username = match[1]
+ const reponame = match[2]
+ const type = match[3]
+
+ // Not a repository, skip
+ if (~BB_RESERVED_USER_NAMES.indexOf(username) ||
+ ~BB_RESERVED_REPO_NAMES.indexOf(reponame) ||
+ ~BB_RESERVED_TYPES.indexOf(type)) {
+ return cb()
+ }
+
+ // Skip non-code page unless showInNonCodePage is true
+ // with Bitbucket /username/repo is non-code page
+ if (!showInNonCodePage &&
+ (!type || (type && type !== 'src'))) {
+ return cb()
+ }
+
+ // Get branch by inspecting page, quite fragile so provide multiple fallbacks
+ const BB_BRANCH_SEL_1 = '.branch-dialog-trigger'
+
+ const branch =
+ // Code page
+ $(BB_BRANCH_SEL_1).attr('title') ||
+ // Assume same with previously
+ (currentRepo.username === username && currentRepo.reponame === reponame && currentRepo.branch) ||
+ // Default from cache
+ this._defaultBranch[username + '/' + reponame]
+
+ const repo = {username: username, reponame: reponame, branch: branch}
+
+ if (repo.branch) {
+ cb(null, repo)
+ }
+ else {
+ this._get('/main-branch', {repo, token}, (err, data) => {
+ if (err) return cb(err)
+ repo.branch = this._defaultBranch[username + '/' + reponame] = data.name || 'master'
+ cb(null, repo)
+ })
+ }
+ }
+
+ // @override
+ selectFile(path) {
+ const $pjaxContainer = $(BB_PJAX_CONTAINER_SEL)
+ super.selectFile(path, {'$pjaxContainer': $pjaxContainer})
+ }
+
+ // @override
+ loadCodeTree(opts, cb) {
+ opts.path = opts.node.path
+ this._loadCodeTree(opts, (item) => {
+ if (!item.type) {
+ item.type = 'blob'
+ }
+ }, cb)
+ }
+
+ // @override
+ _getTree(path, opts, cb) {
+ this._get(`/src/${opts.repo.branch}/${path}`, opts, (err, res) => {
+ if (err) return cb(err)
+ const directories = res.directories.map((dir) => ({path: dir, type: 'tree'}))
+ res.files.forEach((file) => {
+ if (file.path.startsWith(res.path)) {
+ file.path = file.path.substring(res.path.length)
+ }
+ })
+ const tree = res.files.concat(directories)
+ cb(null, tree)
+ })
+ }
+
+ // @override
+ _getSubmodules(tree, opts, cb) {
+ if (opts.repo.submodules) {
+ return this._getSubmodulesInCurrentPath(tree, opts, cb)
+ }
+
+ const item = tree.filter((item) => /^\.gitmodules$/i.test(item.path))[0]
+ if (!item) return cb()
+
+ this._get(`/src/${opts.encodedBranch}/${item.path}`, opts, (err, res) => {
+ if (err) return cb(err)
+ // Memoize submodules so that they will be inserted into the tree later.
+ opts.repo.submodules = parseGitmodules(res.data)
+ this._getSubmodulesInCurrentPath(tree, opts, cb)
+ })
+ }
+
+ // @override
+ _getSubmodulesInCurrentPath(tree, opts, cb) {
+ const currentPath = opts.path
+ const isInCurrentPath = currentPath
+ ? (path) => path.startsWith(`${currentPath}/`)
+ : (path) => path.indexOf('/') === -1
+
+ const submodules = opts.repo.submodules
+ const submodulesInCurrentPath = {}
+ Object.keys(submodules).filter(isInCurrentPath).forEach((key) => {
+ submodulesInCurrentPath[key] = submodules[key]
+ })
+
+ // Insert submodules in current path into the tree because submodules can not
+ // be retrieved with Bitbucket API but can only by reading .gitmodules.
+ Object.keys(submodulesInCurrentPath).forEach((path) => {
+ if (currentPath) {
+ // `currentPath` is prefixed to `path`, so delete it.
+ path = path.substring(currentPath.length + 1)
+ }
+ tree.push({path: path, type: 'commit'})
+ })
+ cb(null, submodulesInCurrentPath)
+ }
+
+ // @override
+ _get(path, opts, cb) {
+ const host = location.protocol + '//' + 'api.bitbucket.org/1.0'
+ const url = `${host}/repositories/${opts.repo.username}/${opts.repo.reponame}${path || ''}`
+ const cfg = { url, method: 'GET', cache: false }
+
+ if (opts.token) {
+ // Bitbucket App passwords can be used only for Basic Authentication.
+ // Get username of logged-in user.
+ let username = null, token = null
+
+ // Or get username by spliting token.
+ if (opts.token.includes(':')) {
+ const result = opts.token.split(':')
+ username = result[0], token = result[1]
+ }
+ else {
+ const currentUser = JSON.parse($('body').attr('data-current-user'))
+ if (!currentUser || !currentUser.username) {
+ return cb({
+ error: 'Error: Invalid token',
+ message: `Cannot retrieve your user name from the current page.
+ Please update the token setting to prepend your user
+ name to the token, separated by a colon, i.e. USERNAME:TOKEN`,
+ needAuth: true
+ })
+ }
+ username = currentUser.username, token = opts.token
+ }
+ cfg.headers = { Authorization: 'Basic ' + btoa(username + ':' + token) }
+ }
+
+ $.ajax(cfg)
+ .done((data) => cb(null, data))
+ .fail((jqXHR) => {
+ this._handleError(jqXHR, cb)
+ })
+ }
+
+ // @override
+ _getItemHref(repo, type, encodedPath) {
+ return `/${repo.username}/${repo.reponame}/src/${repo.branch}/${encodedPath}`
+ }
+}
diff --git a/src/adapters/bitbucket.less b/src/adapters/bitbucket.less
new file mode 100644
index 0000000..cd2a873
--- /dev/null
+++ b/src/adapters/bitbucket.less
@@ -0,0 +1,164 @@
+.octotree-show {
+ .octotree_bitbucket_only {
+ display: none;
+ }
+
+ .octotree_bitbucket_sidebar {
+ a.octotree_toggle {
+ top: 9px;
+ right: 5px;
+ &:not(.octotree_loading) > span:after {
+ content: data-uri('image/svg+xml;charset=UTF-8', './octicons/chevron-left.svg');
+ }
+ &:not(.octotree_loading):hover > span:after {
+ content: data-uri('image/svg+xml;charset=UTF-8', './octicons/chevron-left-hover.svg');
+ }
+ }
+ }
+
+ body.split-diff .container {
+ padding-left: 0;
+ }
+}
+
+.octotree_bitbucket_sidebar {
+ .btn {
+ box-sizing: border-box;
+ background: #f5f5f5;
+ border: 1px solid #ccc;
+ border-radius: 3.01px;
+ color: #333;
+ cursor: pointer;
+ display: inline-block;
+ font-family: inherit;
+ font-size: 14px;
+ font-variant: normal;
+ font-weight: 400;
+ height: 2.14285714em;
+ line-height: 1.42857143;
+ margin: 0;
+ padding: 4px 10px;
+ text-decoration: none;
+ vertical-align: baseline;
+ white-space: nowrap;
+ }
+
+ .btn:hover {
+ background: #e9e9e9;
+ border-color: #999;
+ color: #000;
+ text-decoration: none;
+ }
+
+ padding-top: 49px;
+ background-color: #f5f5f5;
+
+ .octotree_bitbucket_only {
+ display: block;
+ }
+
+ .octotree_views {
+ .octotree_view {
+ .octotree_view_header {
+ height: 49px;
+ background-color: #f3f3f3;
+ background-image: linear-gradient(#f9f9f9, #f3f3f3);
+ background-repeat: repeat-x;
+ border-bottom: 1px solid #e5e5e5;
+ }
+
+ .octotree_help {
+ & > span:before {
+ content: data-uri('image/svg+xml;charset=UTF-8', './octicons/question.svg');
+ }
+ }
+ }
+
+ .octotree_treeview {
+ .octotree_header_repo {
+ font-size: 13px;
+ }
+ .octotree_header_repo:before {
+ font-family: 'Atlassian Icons';
+ content: '\f135';
+ color: #707070;
+ }
+ .octotree_header_branch {
+ font-size: 11px;
+ }
+ .octotree_header_branch:before {
+ font-family: 'Atlassian Icons';
+ content: '\f127';
+ color: #707070;
+ }
+ .jstree-icon.tree:before {
+ content: '\f131';
+ color: #707070;
+ }
+ .jstree-icon.blob:before {
+ content: '\f12e';
+ color: #707070;
+ }
+ .jstree-node.jstree-leaf:hover {
+ .jstree-icon.blob {
+ margin-top: 3px;
+ }
+ .jstree-icon.blob:before {
+ content: data-uri('image/svg+xml;charset=UTF-8', './octicons/cloud-download.svg');
+ }
+ }
+ .jstree-icon.commit:before {
+ content: '\f139';
+ color: #707070;
+ }
+ .jstree-anchor {
+ color: #3572b0 !important;
+ & > span {
+ color: black;
+ }
+ }
+ .jstree-default {
+ .jstree-wholerow-hovered {
+ background: #e6e6e6;
+ }
+ .jstree-wholerow-clicked {
+ background: #e6e6e6;
+ }
+ }
+ .jstree-icon.tree, .jstree-icon.blob, .jstree-icon.commit {
+ font: normal normal 16px 'Atlassian Icons';
+ }
+ }
+ }
+
+ a.octotree_toggle, a.octotree_opts {
+ color: black !important;
+
+ &:hover, &.selected {
+ color: #4183C4 !important;
+ }
+ }
+
+ a.octotree_opts {
+ top: 16px;
+ right: 38px;
+ width: 14px;
+ height: 16px;
+ background: data-uri('image/svg+xml;charset=UTF-8', './octicons/gear.svg');
+ &:hover {
+ background: data-uri('image/svg+xml;charset=UTF-8', './octicons/gear-hover.svg');
+ }
+ }
+
+ a.octotree_toggle {
+ top: 5px;
+ right: -44px;
+
+ &:not(.octotree_loading) > span:after {
+ content: data-uri('image/svg+xml;charset=UTF-8', './octicons/chevron-right.svg');
+ }
+ &:not(.octotree_loading):hover > span:after {
+ content: data-uri('image/svg+xml;charset=UTF-8', './octicons/chevron-right-hover.svg');
+ }
+ }
+}
diff --git a/src/adapters/github.js b/src/adapters/github.js
index 37dd400..d94e863 100644
--- a/src/adapters/github.js
+++ b/src/adapters/github.js
@@ -16,22 +16,16 @@ const GH_PJAX_CONTAINER_SEL = '#js-repo-pjax-container, .context-loader-containe
const GH_CONTAINERS = '.container, .container-responsive'
const GH_RAW_CONTENT = 'body > pre'
-class GitHub extends Adapter {
+class GitHub extends PjaxAdapter {
constructor() {
super(['jquery.pjax.js'])
-
- $.pjax.defaults.timeout = 0 // no timeout
- $(document)
- .on('pjax:send', () => $(document).trigger(EVENT.REQ_START))
- .on('pjax:end', () => $(document).trigger(EVENT.REQ_END))
}
// @override
init($sidebar) {
- super.init($sidebar)
-
- if (!window.MutationObserver) return
+ const pjaxContainer = $(GH_PJAX_CONTAINER_SEL)[0]
+ super.init($sidebar, {'pjaxContainer': pjaxContainer})
// Fix #151 by detecting when page layout is updated.
// In this case, split-diff page has a wider layout, so need to recompute margin.
@@ -50,46 +44,6 @@ class GitHub extends Adapter {
attributeFilter: ['class'],
attributeOldValue: true
})
-
- // GitHub switch pages using pjax. This observer detects if the pjax container
- // has been updated with new contents and trigger layout.
- const pageChangeObserver = new window.MutationObserver(() => {
- // Trigger location change, can't just relayout as Octotree might need to
- // hide/show depending on whether the current page is a code page or not.
- return $(document).trigger(EVENT.LOC_CHANGE)
- })
-
- const pjaxContainer = $(GH_PJAX_CONTAINER_SEL)[0]
-
- if (pjaxContainer) {
- pageChangeObserver.observe(pjaxContainer, {
- childList: true,
- })
- }
- else { // Fall back if DOM has been changed
- let firstLoad = true, href, hash
-
- function detectLocChange() {
- if (location.href !== href || location.hash !== hash) {
- href = location.href
- hash = location.hash
-
- // If this is the first time this is called, no need to notify change as
- // Octotree does its own initialization after loading options.
- if (firstLoad) {
- firstLoad = false
- }
- else {
- setTimeout(() => {
- $(document).trigger(EVENT.LOC_CHANGE)
- }, 300) // Wait a bit for pjax DOM change
- }
- }
- setTimeout(detectLocChange, 200)
- }
-
- detectLocChange()
- }
}
// @override
@@ -180,17 +134,7 @@ class GitHub extends Adapter {
// @override
selectFile(path) {
const $pjaxContainer = $(GH_PJAX_CONTAINER_SEL)
-
- if ($pjaxContainer.length) {
- $.pjax({
- // needs full path for pjax to work with Firefox as per cross-domain-content setting
- url: location.protocol + '//' + location.host + path,
- container: $pjaxContainer
- })
- }
- else { // falls back
- super.selectFile(path)
- }
+ super.selectFile(path, {'$pjaxContainer': $pjaxContainer})
}
// @override
diff --git a/src/config/chrome/manifest.json b/src/config/chrome/manifest.json
index 716e9d3..ce4fe80 100755
--- a/src/config/chrome/manifest.json
+++ b/src/config/chrome/manifest.json
@@ -9,6 +9,7 @@
"128": "icons/icon128.png"
},
"permissions": [
+ "https://bitbucket.org/*",
"https://github.com/*",
"storage"
],
diff --git a/src/config/firefox/firefox.js b/src/config/firefox/firefox.js
index f58e022..96fc06e 100644
--- a/src/config/firefox/firefox.js
+++ b/src/config/firefox/firefox.js
@@ -2,7 +2,7 @@ const data = require('sdk/self').data
const pageMod = require('sdk/page-mod')
pageMod.PageMod({
- include: ['https://github.com/*'],
+ include: ['https://bitbucket.org/*', 'https://github.com/*'],
contentScriptFile : [data.url('jquery.js'),
data.url('jquery-ui.js'),
data.url('jstree.js'),
diff --git a/src/config/firefox/package.json b/src/config/firefox/package.json
index cd7a416..101c088 100644
--- a/src/config/firefox/package.json
+++ b/src/config/firefox/package.json
@@ -12,7 +12,9 @@
"version": "$VERSION",
"permissions": {
"cross-domain-content": [
+ "https://api.bitbucket.org",
"https://api.github.com",
+ "https://bitbucket.org",
"https://github.com"
],
"private-browsing": true,
diff --git a/src/config/safari/Info.plist b/src/config/safari/Info.plist
index 61a0b04..8620fe5 100755
--- a/src/config/safari/Info.plist
+++ b/src/config/safari/Info.plist
@@ -39,6 +39,7 @@
Whitelist
+ https://bitbucket.org/*
https://github.com/*
@@ -52,6 +53,7 @@
Allowed Domains
+ bitbucket.org
github.com
Include Secure Pages
diff --git a/src/octotree.js b/src/octotree.js
index 56a19c4..2554f0a 100755
--- a/src/octotree.js
+++ b/src/octotree.js
@@ -11,7 +11,20 @@ $(document).ready(() => {
}
function createAdapter() {
- return new GitHub(store)
+ const normalizeUrl = (url) => url.replace(/(.*?:\/\/[^/]+)(.*)/, '$1')
+
+ const githubUrls = store.get(STORE.GHEURLS).split(/\n/)
+ .map(normalizeUrl)
+ .concat('https://github.com')
+
+ const bitbucketUrls = ['https://bitbucket.org']
+ const currentUrl = `${location.protocol}//${location.host}`
+
+ if (~githubUrls.indexOf(currentUrl)) {
+ return new GitHub(store)
+ } else if (~bitbucketUrls.indexOf(currentUrl)) {
+ return new Bitbucket(store)
+ }
}
function loadExtension() {
@@ -110,7 +123,8 @@ $(document).ready(() => {
}
if (isSidebarVisible()) {
- const repoChanged = JSON.stringify(repo) !== JSON.stringify(currRepo)
+ const replacer = ['username', 'reponame', 'branch']
+ const repoChanged = JSON.stringify(repo, replacer) !== JSON.stringify(currRepo, replacer)
if (repoChanged || reload === true) {
$document.trigger(EVENT.REQ_START)
diff --git a/src/styles/octotree.less b/src/styles/octotree.less
index ea0cb1e..6a638e9 100644
--- a/src/styles/octotree.less
+++ b/src/styles/octotree.less
@@ -1,2 +1,3 @@
@import "base";
+@import "../adapters/bitbucket";
@import "../adapters/github";
diff --git a/src/view.options.js b/src/view.options.js
index 8fe9920..d631183 100644
--- a/src/view.options.js
+++ b/src/view.options.js
@@ -49,18 +49,20 @@ class OptionsView {
*/
// @ifdef CHROME
const $ta = this.$view.find('[data-store$=EURLS]').filter(':visible')
- const storeKey = $ta.data('store')
- const urls = $ta.val().split(/\n/).filter((url) => url !== '')
+ if ($ta.length > 0) {
+ const storeKey = $ta.data('store')
+ const urls = $ta.val().split(/\n/).filter((url) => url !== '')
- if (urls.length > 0) {
- chrome.runtime.sendMessage({type: 'requestPermissions', urls: urls}, (granted) => {
- if (!granted) {
- // permissions not granted (by user or error), reset value
- $ta.val(this.store.get(STORE[storeKey]))
- }
- this._saveOptions()
- })
- return
+ if (urls.length > 0) {
+ chrome.runtime.sendMessage({type: 'requestPermissions', urls: urls}, (granted) => {
+ if (!granted) {
+ // permissions not granted (by user or error), reset value
+ $ta.val(this.store.get(STORE[storeKey]))
+ }
+ this._saveOptions()
+ })
+ return
+ }
}
// @endif