Refactor options
This commit is contained in:
@@ -48,3 +48,17 @@ button.moco-bx-btn {
|
|||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.moco-bx-btn__secondary {
|
||||||
|
color: $blue;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: $blue;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -63,8 +63,13 @@ input {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: #eeeeee;
|
background-color: #eeeeee;
|
||||||
border: 1px solid #cccccc;
|
border: 1px solid #cccccc;
|
||||||
border-left: none;
|
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
|
&--right {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
&--left {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,11 @@
|
|||||||
margin: 1rem 0 2rem;
|
margin: 1rem 0 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin: 1rem 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
@@ -39,15 +44,10 @@
|
|||||||
color: $red;
|
color: $red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.moco-bx-override-hosts {
|
&__host-overrides {
|
||||||
&-container {
|
padding-bottom: 1rem;
|
||||||
padding: 0 20px;
|
text-align: center;
|
||||||
text-align: center;
|
font-weight: normal;
|
||||||
}
|
|
||||||
&-btn {
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,24 @@ import { observer } from "mobx-react"
|
|||||||
import { isChrome, getSettings, setStorage } from "utils/browser"
|
import { isChrome, getSettings, setStorage } from "utils/browser"
|
||||||
import ApiClient from "api/Client"
|
import ApiClient from "api/Client"
|
||||||
import remoteServices from "../remoteServices"
|
import remoteServices from "../remoteServices"
|
||||||
import { pipe, prop, map, sortedUniqBy, filter } from "lodash/fp"
|
import { pipe, toPairs, fromPairs, prop, map, sortedUniqBy, filter } from "lodash/fp"
|
||||||
|
|
||||||
|
function upperCaseFirstLetter(input) {
|
||||||
|
return input[0].toUpperCase() + input.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePathFromUrl(url) {
|
||||||
|
return url.replace(/(\.[a-z]+)\/.*$/, "$1")
|
||||||
|
}
|
||||||
|
|
||||||
|
const overridableRemoveServices = pipe(
|
||||||
|
filter(prop("allowHostOverride")),
|
||||||
|
map((remoteService) => ({
|
||||||
|
name: remoteService.name,
|
||||||
|
host: remoteService.host,
|
||||||
|
})),
|
||||||
|
sortedUniqBy("name"),
|
||||||
|
)(remoteServices)
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class Options extends Component {
|
class Options extends Component {
|
||||||
@@ -13,33 +30,22 @@ class Options extends Component {
|
|||||||
@observable hostOverrides = {}
|
@observable hostOverrides = {}
|
||||||
@observable errorMessage = null
|
@observable errorMessage = null
|
||||||
@observable isSuccess = false
|
@observable isSuccess = false
|
||||||
@observable servicesHostOverrideList = []
|
|
||||||
@observable showHostOverrideOptions = false
|
@observable showHostOverrideOptions = false
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.servicesHostOverrideList = pipe(
|
|
||||||
filter(prop("allowHostOverride")),
|
|
||||||
map((remoteService) => ({
|
|
||||||
name: remoteService.name,
|
|
||||||
host: remoteService.host,
|
|
||||||
})),
|
|
||||||
sortedUniqBy("name"),
|
|
||||||
)(remoteServices)
|
|
||||||
|
|
||||||
getSettings(false).then((storeData) => {
|
getSettings(false).then((storeData) => {
|
||||||
this.subdomain = storeData.subdomain || ""
|
this.subdomain = storeData.subdomain || ""
|
||||||
this.apiKey = storeData.apiKey || ""
|
this.apiKey = storeData.apiKey || ""
|
||||||
this.hostOverrides = storeData.hostOverrides || {}
|
this.hostOverrides = storeData.hostOverrides
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = (event) => {
|
handleChange = (event) => {
|
||||||
this[event.target.name] = event.target.value.trim()
|
this[event.target.name] = event.target.value.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeHostOverrides = (event) => {
|
handleChangeHostOverrides = (event) => {
|
||||||
// ensure to remove path (and trailing slash) from URL, as this can cause problems otherwise
|
this.hostOverrides[event.target.name] = event.target.value.trim()
|
||||||
this.hostOverrides[event.target.name] = this.removePathFromUrl(event.target.value.trim())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleHostOverrideOptions = () => {
|
toggleHostOverrideOptions = () => {
|
||||||
@@ -54,7 +60,11 @@ class Options extends Component {
|
|||||||
subdomain: this.subdomain,
|
subdomain: this.subdomain,
|
||||||
apiKey: this.apiKey,
|
apiKey: this.apiKey,
|
||||||
settingTimeTrackingHHMM: false,
|
settingTimeTrackingHHMM: false,
|
||||||
hostOverrides: this.hostOverrides,
|
hostOverrides: pipe(
|
||||||
|
toPairs,
|
||||||
|
map(([key, url]) => [key, removePathFromUrl(url)]),
|
||||||
|
fromPairs,
|
||||||
|
)(this.hostOverrides),
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
const { version } = chrome.runtime.getManifest()
|
const { version } = chrome.runtime.getManifest()
|
||||||
const apiClient = new ApiClient({
|
const apiClient = new ApiClient({
|
||||||
@@ -77,10 +87,6 @@ class Options extends Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
removePathFromUrl = (url) => {
|
|
||||||
return url.replace(/(\.[a-z]+)\/.*$/, "$1")
|
|
||||||
}
|
|
||||||
|
|
||||||
handleInputKeyDown = (event) => {
|
handleInputKeyDown = (event) => {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
this.handleSubmit()
|
this.handleSubmit()
|
||||||
@@ -105,9 +111,9 @@ class Options extends Component {
|
|||||||
name="subdomain"
|
name="subdomain"
|
||||||
value={this.subdomain}
|
value={this.subdomain}
|
||||||
onKeyDown={this.handleInputKeyDown}
|
onKeyDown={this.handleInputKeyDown}
|
||||||
onChange={this.onChange}
|
onChange={this.handleChange}
|
||||||
/>
|
/>
|
||||||
<span className="input-group-addon">.mocoapp.com</span>
|
<span className="input-group-addon input-group-addon--right">.mocoapp.com</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
@@ -117,33 +123,44 @@ class Options extends Component {
|
|||||||
name="apiKey"
|
name="apiKey"
|
||||||
value={this.apiKey}
|
value={this.apiKey}
|
||||||
onKeyDown={this.handleInputKeyDown}
|
onKeyDown={this.handleInputKeyDown}
|
||||||
onChange={this.onChange}
|
onChange={this.handleChange}
|
||||||
/>
|
/>
|
||||||
<p className="text-muted">
|
<p className="text-muted">
|
||||||
Den API-Schlüssel findest du in deinem Profil unter "Integrationen".
|
Den API-Schlüssel findest du in deinem Profil unter "Integrationen".
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
{!this.showHostOverrideOptions && (
|
{!this.showHostOverrideOptions && (
|
||||||
<div className="moco-bx-override-hosts-container">
|
<div className="moco-bx-options__host-overrides">
|
||||||
<span className="moco-bx-override-hosts-btn" onClick={this.toggleHostOverrideOptions}>Zeige Optionen zum Überschreiben<br />der Service Hosts</span>
|
<a href="#" className="moco-bx-btn__secondary" onClick={this.toggleHostOverrideOptions}>
|
||||||
|
URLs für Dienste anpassen?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{this.showHostOverrideOptions && (
|
||||||
|
<div style={{ marginBottom: "1rem" }}>
|
||||||
|
<h3>URLs für Dienste</h3>
|
||||||
|
{overridableRemoveServices.map((remoteService) => (
|
||||||
|
<div className="form-group" key={remoteService.name} style={{ margin: "0.5rem 0" }}>
|
||||||
|
<div className="input-group">
|
||||||
|
<span
|
||||||
|
className="input-group-addon input-group-addon--left"
|
||||||
|
style={{ display: "inline-block", width: "70px", textAlign: "left" }}
|
||||||
|
>
|
||||||
|
{upperCaseFirstLetter(remoteService.name)}
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name={remoteService.name}
|
||||||
|
value={this.hostOverrides[remoteService.name] || ""}
|
||||||
|
placeholder={remoteService.host}
|
||||||
|
onKeyDown={this.handleInputKeyDown}
|
||||||
|
onChange={this.handleChangeHostOverrides}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{this.showHostOverrideOptions &&
|
|
||||||
this.servicesHostOverrideList.map((remoteService) => (
|
|
||||||
<div className="form-group" key={remoteService.name}>
|
|
||||||
<label>Host URL: {remoteService.name}</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name={remoteService.name}
|
|
||||||
value={this.hostOverrides[remoteService.name] || ""}
|
|
||||||
placeholder={remoteService.host}
|
|
||||||
onKeyDown={this.handleInputKeyDown}
|
|
||||||
onChange={this.onChangeHostOverrides}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<hr />
|
|
||||||
<button className="moco-bx-btn" onClick={this.handleSubmit}>
|
<button className="moco-bx-btn" onClick={this.handleSubmit}>
|
||||||
OK
|
OK
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
const projectRegex = /\[([\w-]+)\]/
|
const projectRegex = /\[([\w-]+)\]/
|
||||||
|
|
||||||
const projectIdentifierBySelector = selector => document =>
|
const projectIdentifierBySelector = (selector) => (document) =>
|
||||||
document
|
document.querySelector(selector)?.textContent?.trim()?.match(projectRegex)?.[1]
|
||||||
.querySelector(selector)
|
|
||||||
?.textContent?.trim()
|
|
||||||
?.match(projectRegex)?.[1]
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
asana: {
|
asana: {
|
||||||
@@ -14,7 +11,7 @@ export default {
|
|||||||
[/^__HOST__\/0\/([^/]+)\/(\d+)/, ["domainUserId", "id"]],
|
[/^__HOST__\/0\/([^/]+)\/(\d+)/, ["domainUserId", "id"]],
|
||||||
[/^__HOST__\/0\/search\/([^/]+)\/(\d+)/, ["domainUserId", "id"]],
|
[/^__HOST__\/0\/search\/([^/]+)\/(\d+)/, ["domainUserId", "id"]],
|
||||||
],
|
],
|
||||||
description: document =>
|
description: (document) =>
|
||||||
document.querySelector(".ItemRow--highlighted textarea")?.textContent?.trim() ||
|
document.querySelector(".ItemRow--highlighted textarea")?.textContent?.trim() ||
|
||||||
document.querySelector(".ItemRow--focused textarea")?.textContent?.trim() ||
|
document.querySelector(".ItemRow--focused textarea")?.textContent?.trim() ||
|
||||||
document.querySelector(".SingleTaskPane textarea")?.textContent?.trim() ||
|
document.querySelector(".SingleTaskPane textarea")?.textContent?.trim() ||
|
||||||
@@ -28,7 +25,7 @@ export default {
|
|||||||
host: "https://github.com",
|
host: "https://github.com",
|
||||||
urlPatterns: ["__HOST__/:org/:repo/pull/:id(/:tab)"],
|
urlPatterns: ["__HOST__/:org/:repo/pull/:id(/:tab)"],
|
||||||
id: (document, service, { org, repo, id }) => [service.key, org, repo, id].join("."),
|
id: (document, service, { org, repo, id }) => [service.key, org, repo, id].join("."),
|
||||||
description: document => document.querySelector(".js-issue-title")?.textContent?.trim(),
|
description: (document) => document.querySelector(".js-issue-title")?.textContent?.trim(),
|
||||||
projectId: projectIdentifierBySelector(".js-issue-title"),
|
projectId: projectIdentifierBySelector(".js-issue-title"),
|
||||||
allowHostOverride: true,
|
allowHostOverride: true,
|
||||||
},
|
},
|
||||||
@@ -73,12 +70,12 @@ export default {
|
|||||||
name: "meistertask",
|
name: "meistertask",
|
||||||
host: "https://www.meistertask.com",
|
host: "https://www.meistertask.com",
|
||||||
urlPatterns: ["/app/task/:id/:slug"],
|
urlPatterns: ["/app/task/:id/:slug"],
|
||||||
description: document => {
|
description: (document) => {
|
||||||
const json = document.getElementById("mt-toggl-data")?.dataset?.togglJson || "{}"
|
const json = document.getElementById("mt-toggl-data")?.dataset?.togglJson || "{}"
|
||||||
const data = JSON.parse(json)
|
const data = JSON.parse(json)
|
||||||
return data.taskName
|
return data.taskName
|
||||||
},
|
},
|
||||||
projectId: document => {
|
projectId: (document) => {
|
||||||
const json = document.getElementById("mt-toggl-data")?.dataset?.togglJson || "{}"
|
const json = document.getElementById("mt-toggl-data")?.dataset?.togglJson || "{}"
|
||||||
const data = JSON.parse(json)
|
const data = JSON.parse(json)
|
||||||
const match = data.taskName?.match(projectRegex) || data.projectName?.match(projectRegex)
|
const match = data.taskName?.match(projectRegex) || data.projectName?.match(projectRegex)
|
||||||
@@ -93,7 +90,7 @@ export default {
|
|||||||
urlPatterns: ["__HOST__/c/:id/:title"],
|
urlPatterns: ["__HOST__/c/:id/:title"],
|
||||||
description: (document, service, { title }) =>
|
description: (document, service, { title }) =>
|
||||||
document.querySelector(".js-title-helper")?.textContent?.trim() || title,
|
document.querySelector(".js-title-helper")?.textContent?.trim() || title,
|
||||||
projectId: document =>
|
projectId: (document) =>
|
||||||
projectIdentifierBySelector(".js-title-helper")(document) ||
|
projectIdentifierBySelector(".js-title-helper")(document) ||
|
||||||
projectIdentifierBySelector(".js-board-editing-target")(document),
|
projectIdentifierBySelector(".js-board-editing-target")(document),
|
||||||
allowHostOverride: false,
|
allowHostOverride: false,
|
||||||
@@ -103,7 +100,7 @@ export default {
|
|||||||
name: "youtrack",
|
name: "youtrack",
|
||||||
host: "https://:org.myjetbrains.com",
|
host: "https://:org.myjetbrains.com",
|
||||||
urlPatterns: ["__HOST__/youtrack/issue/:id"],
|
urlPatterns: ["__HOST__/youtrack/issue/:id"],
|
||||||
description: document => document.querySelector("yt-issue-body h1")?.textContent?.trim(),
|
description: (document) => document.querySelector("yt-issue-body h1")?.textContent?.trim(),
|
||||||
projectId: projectIdentifierBySelector("yt-issue-body h1"),
|
projectId: projectIdentifierBySelector("yt-issue-body h1"),
|
||||||
allowHostOverride: true,
|
allowHostOverride: true,
|
||||||
},
|
},
|
||||||
@@ -120,7 +117,7 @@ export default {
|
|||||||
queryParams: {
|
queryParams: {
|
||||||
id: ["t", "ot"],
|
id: ["t", "ot"],
|
||||||
},
|
},
|
||||||
description: document => document.querySelector(".title-field-ghost")?.textContent?.trim(),
|
description: (document) => document.querySelector(".title-field-ghost")?.textContent?.trim(),
|
||||||
projectId: projectIdentifierBySelector(".header-title__main"),
|
projectId: projectIdentifierBySelector(".header-title__main"),
|
||||||
allowHostOverride: false,
|
allowHostOverride: false,
|
||||||
},
|
},
|
||||||
@@ -129,7 +126,7 @@ export default {
|
|||||||
name: "wunderlist",
|
name: "wunderlist",
|
||||||
host: "https://www.wunderlist.com",
|
host: "https://www.wunderlist.com",
|
||||||
urlPatterns: ["__HOST__/(webapp)#/tasks/:id(/*)"],
|
urlPatterns: ["__HOST__/(webapp)#/tasks/:id(/*)"],
|
||||||
description: document =>
|
description: (document) =>
|
||||||
document
|
document
|
||||||
.querySelector(".taskItem.selected .taskItem-titleWrapper-title")
|
.querySelector(".taskItem.selected .taskItem-titleWrapper-title")
|
||||||
?.textContent?.trim(),
|
?.textContent?.trim(),
|
||||||
|
|||||||
@@ -4,32 +4,40 @@ export const isChrome = () => typeof browser === "undefined" && chrome
|
|||||||
export const isFirefox = () => typeof browser !== "undefined" && chrome
|
export const isFirefox = () => typeof browser !== "undefined" && chrome
|
||||||
|
|
||||||
const DEFAULT_SUBDOMAIN = "unset"
|
const DEFAULT_SUBDOMAIN = "unset"
|
||||||
|
const DEFAULT_HOST_OVERRIDES = {
|
||||||
|
github: "https://github.com",
|
||||||
|
gitlab: "https://gitlab.com",
|
||||||
|
jira: "https://:org.atlassian.net",
|
||||||
|
youtrack: "https://:org.myjetbrains.com",
|
||||||
|
}
|
||||||
|
|
||||||
export const getSettings = (withDefaultSubdomain = true) => {
|
export const getSettings = (withDefaultSubdomain = true) => {
|
||||||
const keys = ["subdomain", "apiKey", "settingTimeTrackingHHMM", "hostOverrides"]
|
const keys = ["subdomain", "apiKey", "settingTimeTrackingHHMM", "hostOverrides"]
|
||||||
const { version } = chrome.runtime.getManifest()
|
const { version } = chrome.runtime.getManifest()
|
||||||
if (isChrome()) {
|
if (isChrome()) {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
chrome.storage.sync.get(keys, data => {
|
chrome.storage.sync.get(keys, (data) => {
|
||||||
if (withDefaultSubdomain) {
|
if (withDefaultSubdomain) {
|
||||||
data.subdomain = data.subdomain || DEFAULT_SUBDOMAIN
|
data.subdomain = data.subdomain || DEFAULT_SUBDOMAIN
|
||||||
}
|
}
|
||||||
|
data.hostOverrides = { ...DEFAULT_HOST_OVERRIDES, ...(data.hostOverrides || {}) }
|
||||||
resolve({ ...data, version })
|
resolve({ ...data, version })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return browser.storage.sync.get(keys).then(data => {
|
return browser.storage.sync.get(keys).then((data) => {
|
||||||
if (withDefaultSubdomain) {
|
if (withDefaultSubdomain) {
|
||||||
data.subdomain = data.subdomain || DEFAULT_SUBDOMAIN
|
data.subdomain = data.subdomain || DEFAULT_SUBDOMAIN
|
||||||
}
|
}
|
||||||
|
data.hostOverrides = { ...DEFAULT_HOST_OVERRIDES, ...(data.hostOverrides || {}) }
|
||||||
return { ...data, version }
|
return { ...data, version }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setStorage = items => {
|
export const setStorage = (items) => {
|
||||||
if (isChrome()) {
|
if (isChrome()) {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
chrome.storage.sync.set(items, resolve)
|
chrome.storage.sync.set(items, resolve)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -37,9 +45,9 @@ export const setStorage = items => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const queryTabs = queryInfo => {
|
export const queryTabs = (queryInfo) => {
|
||||||
if (isChrome()) {
|
if (isChrome()) {
|
||||||
return new Promise(resolve => chrome.tabs.query(queryInfo, resolve))
|
return new Promise((resolve) => chrome.tabs.query(queryInfo, resolve))
|
||||||
} else {
|
} else {
|
||||||
return browser.tabs.query(queryInfo)
|
return browser.tabs.query(queryInfo)
|
||||||
}
|
}
|
||||||
@@ -49,4 +57,4 @@ export const getCurrentTab = () => {
|
|||||||
return queryTabs({ currentWindow: true, active: true }).then(head)
|
return queryTabs({ currentWindow: true, active: true }).then(head)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isBrowserTab = tab => /^(?:chrome|about):/.test(tab.url)
|
export const isBrowserTab = (tab) => /^(?:chrome|about):/.test(tab.url)
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ module.exports = (env) => {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new CleanWebpackPlugin({
|
new CleanWebpackPlugin({
|
||||||
cleanAfterEveryBuildPatterns: ["!manifest.json"],
|
cleanAfterEveryBuildPatterns: ["!manifest.json", "!*.html"],
|
||||||
}),
|
}),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
"process.env.NODE_ENV": JSON.stringify(env.NODE_ENV),
|
"process.env.NODE_ENV": JSON.stringify(env.NODE_ENV),
|
||||||
|
|||||||
Reference in New Issue
Block a user