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