Refactor options

This commit is contained in:
Manuel Bouza
2020-06-12 16:08:01 +02:00
parent 6a05157bd9
commit 88765e5c9a
8 changed files with 116 additions and 74 deletions

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
14

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
&__host-overrides {
padding-bottom: 1rem;
text-align: center;
}
&-btn {
cursor: pointer;
text-decoration: underline;
}
font-weight: normal;
}
}
}

View File

@@ -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 &quot;Integrationen&quot;.
</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 &&
this.servicesHostOverrideList.map((remoteService) => (
<div className="form-group" key={remoteService.name}>
<label>Host URL: {remoteService.name}</label>
{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.onChangeHostOverrides}
onChange={this.handleChangeHostOverrides}
/>
</div>
</div>
))}
<hr />
</div>
)}
<button className="moco-bx-btn" onClick={this.handleSubmit}>
OK
</button>

View File

@@ -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(),

View File

@@ -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)

View File

@@ -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),