Switch to Typescript and webpack

This commit is contained in:
Daniel Triendl
2020-11-04 22:59:32 +01:00
parent e40a0810ff
commit 7db79afca2
21 changed files with 14701 additions and 310 deletions

98
src/js/call-history.ts Normal file
View File

@ -0,0 +1,98 @@
import * as chrono from 'chrono-node'
import { TapiContact } from './tapi-contact'
import { axios, extractNumber } from './utils'
export class CallHistory {
private callerIds: { [number: string]: TapiContact } = {}
private updateCallHistoryEntry (call: HTMLElement, callerId: TapiContact) {
if (callerId.tD_NAME !== '') {
var span = call.querySelector('span')
this.showTimeManager(call, span.nextSibling.textContent, callerId)
var text = span.textContent
span.textContent = callerId.tD_NAME
var br = document.createElement('br')
var span2 = document.createElement('span')
span2.style.fontSize = 'small'
span2.textContent = text
span.parentNode.insertBefore(br, span.nextSibling)
span.parentNode.insertBefore(span2, span.nextSibling)
}
}
private showTimeManager (call: HTMLElement, time: string, callerId: TapiContact) {
if (!callerId.tD_ID) {
return
}
var timeParts = time.split(/, /)
var duration = '00:00:00'
if (timeParts.length >= 1) {
time = timeParts[0]
}
if (timeParts.length >= 2) {
duration = timeParts[1]
}
var parsedDate = chrono.de.parseDate(time)
if (!parsedDate) {
parsedDate = chrono.parseDate(time)
}
if (!parsedDate) {
return
}
var parsedDuration = chrono.parseDate(duration)
console.log('TAPI call history time:', time, 'parsedDate:', parsedDate, 'duration:', duration, 'parsedDuration:', parsedDuration)
var connect = parsedDate.getFullYear().toString() +
(parsedDate.getMonth() + 1).toString().padStart(2, '0') + // (January gives 0)
parsedDate.getDate().toString().padStart(2, '0') +
parsedDate.getHours().toString().padStart(2, '0') +
parsedDate.getMinutes().toString().padStart(2, '0')
var length = (parsedDuration.getHours() * 60 + parsedDuration.getMinutes()).toString()
var toolbar = call.querySelector('.wcToolbarTiles')
var a = document.createElement('a')
var href = 'domizil://PM/Zeitbuchung?KontaktId=' + callerId.tD_ID + '&connect=' + connect + '&length=' + length
a.dataset.domizilLink = href
a.onclick = () => {
window.open(href)
}
a.innerHTML = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 559.98 559.98" width="20" height="20">' +
'<g>' +
' <g>' +
' <path d="M279.99,0C125.601,0,0,125.601,0,279.99c0,154.39,125.601,279.99,279.99,279.99c154.39,0,279.99-125.601,279.99-279.99' +
' C559.98,125.601,434.38,0,279.99,0z M279.99,498.78c-120.644,0-218.79-98.146-218.79-218.79' +
' c0-120.638,98.146-218.79,218.79-218.79s218.79,98.152,218.79,218.79C498.78,400.634,400.634,498.78,279.99,498.78z"/>' +
' <path d="M304.226,280.326V162.976c0-13.103-10.618-23.721-23.716-23.721c-13.102,0-23.721,10.618-23.721,23.721v124.928' +
' c0,0.373,0.092,0.723,0.11,1.096c-0.312,6.45,1.91,12.999,6.836,17.926l88.343,88.336c9.266,9.266,24.284,9.266,33.543,0' +
' c9.26-9.266,9.266-24.284,0-33.544L304.226,280.326z"/>' +
' </g>' +
'</g>' +
'</svg>'
toolbar.insertBefore(a, toolbar.firstChild)
}
public async showCallHistory (element: HTMLElement) {
var span = element.querySelector('span')
var number = extractNumber(span.textContent)
if (!number) {
return
}
if (this.callerIds[number] !== undefined) {
this.updateCallHistoryEntry(element, this.callerIds[number])
} else {
var response = await axios.get<TapiContact>('http://cpatapi.cpsrvweb2016.cp-austria.at/callerid/' + encodeURIComponent(number))
var callerId: TapiContact = { tD_NAME: '' }
if (response.status === 200) {
callerId = response.data
}
console.log('TAPI call histroy callerid response', number, response, callerId)
this.callerIds[number] = callerId
this.updateCallHistoryEntry(element, callerId)
}
}
}

View File

@ -0,0 +1,31 @@
import { TapiContact } from './tapi-contact'
import { axios, extractNumber } from './utils'
export class CallNotification {
public async showCallNotification (element: HTMLElement) {
var number = element.dataset.id
console.log('TAPI call notification', number)
number = extractNumber(number)
if (!number) {
console.log('TAPI callerid no number found')
return
}
console.log('TAPI searching callerid for', number)
var response = await axios.get<TapiContact>('http://cpatapi.cpsrvweb2016.cp-austria.at/callerid/' + encodeURIComponent(number))
console.log('TAPI callerid response', response)
var notification = {
text: number
}
if (response.status === 200) {
var callerId = response.data
if (callerId) {
notification.text = callerId.tD_NAME + '\r\n' + number
}
}
// eslint-disable-next-line no-undef
GM.notification(notification)
}
}

13
src/js/debounce.js Normal file
View File

@ -0,0 +1,13 @@
export function debounce (func, wait) {
let timeout
return function executedFunction (...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}

3
src/js/decs.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
declare module 'axios-userscript-adapter'
declare const GM: any

19
src/js/index.js Normal file
View File

@ -0,0 +1,19 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as chrono from 'chrono-node'
import { CallHistory } from './call-history'
import { CallNotification } from './call-notification'
import { Search } from './search'
console.log('script start')
const search = new Search()
// eslint-disable-next-line no-undef
waitForKeyElements('div.nav-search', (element) => { search.createSearchWindow(element) }, true)
const callNotification = new CallNotification()
// eslint-disable-next-line no-undef
waitForKeyElements('call-view', (element) => { callNotification.showCallNotification(element) }, false)
const callHistory = new CallHistory()
// eslint-disable-next-line no-undef
waitForKeyElements('.call-history-list call', (element) => { callHistory.showCallHistory(element) }, false)

194
src/js/search.ts Normal file
View File

@ -0,0 +1,194 @@
import { TapiContact } from './tapi-contact'
import { debounce } from './debounce'
import { axios } from './utils'
export class Search {
private currentSearchText = ''
public createSearchWindow (element: HTMLElement) {
console.log('Create TAPI Search')
var form = document.createElement('form')
form.style.width = '200px'
form.style.float = 'right'
form.style.marginRight = '20px'
form.onsubmit = () => {
var items = document.getElementsByClassName('tapi-search-result')
if (items.length > 0) {
this.dial((<HTMLElement>items[0]).dataset.tapiNumber)
}
return false
}
var searchBox = document.createElement('div')
searchBox.classList.add('contact-search-box')
searchBox.id = 'tapiSearchBox'
form.appendChild(searchBox)
var searchWrapper = document.createElement('div')
searchWrapper.classList.add('search-input-wrapper')
searchWrapper.style.position = 'relative'
searchBox.appendChild(searchWrapper)
var search = document.createElement('input')
search.id = 'tapiSearchInput'
search.autocomplete = 'off'
search.classList.add('padder')
search.classList.add('rounded')
search.classList.add('bg-light')
search.classList.add('no-border')
search.classList.add('contact-search-box')
search.placeholder = 'TAPI Suche'
search.onfocus = () => { this.doSearch() }
search.onkeydown = (ev) => { this.doSearchKeyDown(ev) }
search.onblur = () => {
console.log('TAPI Search exit', this)
setTimeout(() => {
console.log('TAPI clear search results')
this.removeSearchResults()
}, 500)
}
searchWrapper.appendChild(search)
var icon = document.createElement('span')
icon.classList.add('fa')
icon.classList.add('fa-search')
icon.classList.add('form-control-feedback')
icon.style.color = 'grey'
searchWrapper.appendChild(icon)
element.appendChild(form)
}
private removeSearchResults () {
var resultList = document.getElementById('tapiResults')
if (resultList) {
resultList.parentNode.removeChild(resultList)
}
this.currentSearchText = ''
}
private doSearchKeyDown (ev: KeyboardEvent) {
if (ev.key === 'ArrowUp') {
let items = document.getElementsByClassName('tapi-search-result-selected')
if (items.length > 0) {
var prev = <Element>items[0].previousSibling
}
if (!prev) {
items = document.getElementsByClassName('tapi-search-result')
if (items.length > 0) {
prev = items[items.length - 1]
}
}
if (prev) {
this.selectResult(prev)
prev.scrollIntoView(true)
}
} else if (ev.key === 'ArrowDown') {
let items = document.getElementsByClassName('tapi-search-result-selected')
if (items.length > 0) {
var next = <Element>items[0].nextSibling
}
if (!next) {
items = document.getElementsByClassName('tapi-search-result')
if (items.length > 0) {
next = items[0]
}
}
if (next) {
this.selectResult(next)
next.scrollIntoView(false)
}
} else {
this.doSearch()
}
}
private doSearch = debounce(async () => {
var search = <HTMLInputElement>document.getElementById('tapiSearchInput')
var searchText = search.value.trim()
if (searchText === '') {
this.removeSearchResults()
return
} else if (searchText === this.currentSearchText) {
return
}
console.log('Searching TAPI')
var response = await axios.get<TapiContact[]>('http://cpatapi.cpsrvweb2016.cp-austria.at/search?query=' + encodeURIComponent(searchText))
console.log('TAPI Search response', response)
var contacts = response.data
console.log('TAPI Contacts', contacts)
this.removeSearchResults()
this.currentSearchText = searchText
var resultList = document.createElement('ul')
resultList.id = 'tapiResults'
resultList.classList.add('search-nav-absolute')
resultList.classList.add('search-nav-ul')
document.getElementById('tapiSearchBox').appendChild(resultList)
resultList.innerHTML = ''
contacts.forEach(contact => {
var li = document.createElement('li')
li.classList.add('tapi-search-result')
li.classList.add('search-result')
li.classList.add('pointer')
li.onmouseover = () => { this.selectResult(li) }
li.dataset.tapiNumber = contact.tD_NUMBER_TAPI
li.onclick = () => { this.dial(contact.tD_NUMBER_TAPI) }
li.style.listStyle = 'outside none none' // display: flex; align-items: center;
var resultText = document.createElement('div')
resultText.classList.add('search-result-txt')
li.appendChild(resultText)
var line1 = document.createElement('div')
line1.appendChild(document.createTextNode(contact.tD_NAME))
resultText.appendChild(line1)
var line2 = document.createElement('div')
line2.appendChild(document.createTextNode(contact.tD_NUMBER_TAPI))
resultText.appendChild(line2)
resultList.appendChild(li)
})
}, 200)
private selectResult (resultLi: Element) {
var items = document.getElementsByClassName('tapi-search-result')
for (var item of items) {
item.classList.remove('bg-light')
item.classList.remove('tapi-search-result-selected')
}
resultLi.classList.add('bg-light')
resultLi.classList.add('tapi-search-result-selected')
}
private dial (number: string) {
var searchInput = document.getElementsByName('searchByNumberInput')
if (searchInput.length > 0) {
(<HTMLInputElement>searchInput[0]).value = number
searchInput[0].focus()
this.fireChangeEvents(searchInput[0])
}
}
private fireChangeEvents (element: Element) {
var changeEvent = null
changeEvent = document.createEvent('HTMLEvents')
changeEvent.initEvent('input', true, true)
element.dispatchEvent(changeEvent)
console.debug('input event dispatched for element: ' + element.id)
changeEvent = document.createEvent('HTMLEvents')
changeEvent.initEvent('keyup', true, true)
element.dispatchEvent(changeEvent)
console.debug('keyup event dispatched for element: ' + element.id)
changeEvent = document.createEvent('HTMLEvents')
changeEvent.initEvent('change', true, true)
element.dispatchEvent(changeEvent)
console.debug('change event dispatched for element: ' + element.id)
}
}

7
src/js/tapi-contact.ts Normal file
View File

@ -0,0 +1,7 @@
/* eslint-disable camelcase */
export interface TapiContact {
tD_ID?: string;
tD_NAME: string;
tD_NUMBER?: string;
tD_NUMBER_TAPI?: string;
}

33
src/js/utils.ts Normal file
View File

@ -0,0 +1,33 @@
/**
* @typedef {Object} AxiosResponse
* @property {Object} data
* @property {Object} headers
* @property {Object} config
* @property {Object} request
* @property {number} code
* @property {string} statusText
*/
/**
* @typedef {Object} AxiosError
* @property {AxiosResponse} response
*/
import axios from 'axios'
import adapter from 'axios-userscript-adapter'
axios.defaults.adapter = adapter
export { axios }
export function extractNumber (s: string) {
var match = /(\+?[0-9]+)/.exec(s)
if (!match) {
return undefined
}
var number = match[1]
if (number.startsWith('+')) {
number = number.replace('+', '00')
}
return number
}