14 Commits

11 changed files with 8110 additions and 16100 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
const path = require('path')
const { merge } = require('webpack-merge')
const LiveReloadPlugin = require('webpack-livereload-plugin')
const UserScriptMetaDataPlugin = require('userscript-metadata-webpack-plugin')
const { UserScriptMetaDataPlugin } = require('userscript-metadata-webpack-plugin')
const metadata = require('./metadata.cjs')
const webpackConfig = require('./webpack.config.base.cjs')

View File

@ -1,5 +1,5 @@
const { merge } = require('webpack-merge')
const UserScriptMetaDataPlugin = require('userscript-metadata-webpack-plugin')
const { UserScriptMetaDataPlugin } = require('userscript-metadata-webpack-plugin')
const metadata = require('./metadata.cjs')
const webpackConfig = require('./webpack.config.base.cjs')

11846
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{
"name": "3cp-tapi",
"description": "Build your UserScript with webpack",
"version": "8.0.1",
"name": "3cx-tapi",
"description": "3CX CP Tapi and Projectmanager integration",
"version": "9.1.0",
"author": {
"name": "Daniel Triendl",
"email": "d.triendl@cp-solutions.at"
@ -24,34 +24,33 @@
},
"private": true,
"dependencies": {
"axios": "0.21.1",
"axios-userscript-adapter": "0.1.4",
"chrono-node": "^2.3.0"
"axios": "^1.4.0",
"axios-userscript-adapter": "^0.2.0-alpha.2",
"chrono-node": "^2.6.3"
},
"devDependencies": {
"@babel/core": "7.14.6",
"@babel/preset-env": "7.14.5",
"@typescript-eslint/eslint-plugin": "4.27.0",
"@typescript-eslint/parser": "4.27.0",
"babel-loader": "8.2.2",
"browserslist": "4.16.6",
"css-loader": "5.2.6",
"eslint": "7.29.0",
"eslint-config-standard": "16.0.3",
"eslint-plugin-import": "2.23.4",
"@babel/core": "^7.22.5",
"@babel/preset-env": "^7.22.5",
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
"babel-loader": "^9.1.2",
"browserslist": "^4.21.9",
"css-loader": "^6.8.1",
"eslint": "^8.43.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "5.1.0",
"eslint-plugin-standard": "4.1.0",
"less": "4.1.1",
"less-loader": "10.0.0",
"style-loader": "2.0.0",
"ts-loader": "9.2.3",
"typescript": "4.3.4",
"userscript-metadata-webpack-plugin": "0.1.0",
"webpack": "5.39.1",
"webpack-bundle-analyzer": "4.4.2",
"webpack-cli": "4.7.2",
"webpack-livereload-plugin": "3.0.1",
"webpack-merge": "5.8.0"
"eslint-plugin-promise": "^6.1.1",
"less": "4.1.3",
"less-loader": "^11.1.3",
"style-loader": "^3.3.3",
"ts-loader": "^9.4.3",
"typescript": "^5.1.3",
"userscript-metadata-webpack-plugin": "^0.4.0",
"webpack": "^5.88.0",
"webpack-bundle-analyzer": "^4.9.0",
"webpack-cli": "^5.1.4",
"webpack-livereload-plugin": "3.0.2",
"webpack-merge": "^5.9.0"
}
}

View File

@ -6,18 +6,12 @@ export class CallHistory {
private callerIds: { [number: string]: TapiContact } = {}
private updateCallHistoryEntry (call: HTMLElement, callerId: TapiContact) {
var span = call.querySelector('span')
this.showTimeManager(call, span.nextSibling.textContent.trim(), callerId)
var span = call.querySelector(':scope > span')
this.showTimeManager(call, call.querySelector('.date').textContent, callerId)
if (callerId && callerId.tD_NAME !== '') {
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)
span.textContent = callerId.tD_NAME + ' ' + callerId.tD_NUMBER
}
}
@ -28,10 +22,10 @@ export class CallHistory {
date = dateParts.groups.date
duration = dateParts.groups.duration
}
var parsedDate = chrono.de.parseDate(date)
if (!parsedDate) {
parsedDate = chrono.parseDate(date)
var parsedDate = chrono.parseDate(date)
var parsedDateDe = chrono.de.parseDate(date)
if (parsedDateDe) {
parsedDate = parsedDateDe
}
if (!parsedDate) {
return
@ -50,7 +44,7 @@ export class CallHistory {
var length = (parsedDuration.getHours() * 60 + parsedDuration.getMinutes()).toString()
var toolbar = call.querySelector('.wcToolbarTiles')
var toolbar = call.querySelector('call-history-options')
var href = 'domizil://PM/Zeitbuchung?'
if (callerId && callerId.tD_ID) {
href += 'KontaktId=' + callerId.tD_ID + '&'
@ -62,7 +56,7 @@ export class CallHistory {
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">' +
a.innerHTML = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 559.98 559.98">' +
'<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' +
@ -74,11 +68,14 @@ export class CallHistory {
' </g>' +
'</g>' +
'</svg>'
a.classList.add('btn');
a.classList.add('btn-plain');
toolbar.insertBefore(a, toolbar.firstChild)
}
public async showCallHistory (element: HTMLElement) {
var span = element.querySelector('span')
var span = element.querySelector(':scope > span')
var number = extractNumber(span.textContent)
if (!number) {
this.updateCallHistoryEntry(element, undefined)

View File

@ -3,7 +3,7 @@ import { axios, extractNumber } from './utils'
export class CallNotification {
public async showCallNotification (element: HTMLElement) {
var number = element.dataset.id
var number = element.querySelector('.callNumber').textContent
console.log('TAPI call notification', number)
number = extractNumber(number)

View File

@ -9,7 +9,7 @@ console.log('script start')
const search = new Search()
// eslint-disable-next-line no-undef
waitForKeyElements('div.nav-search', (element) => { search.createSearchWindow(element) }, true)
waitForKeyElements('ongoing-call-button', (element) => { search.createSearchWindow(element) }, false)
const callNotification = new CallNotification()
// eslint-disable-next-line no-undef
@ -21,4 +21,4 @@ waitForKeyElements('.call-history-list call', (element) => { callHistory.showCal
const status = new Status()
// eslint-disable-next-line no-undef
waitForKeyElements('#status-change', (element) => { status.showStatus(element) }, false)
waitForKeyElements('wc-account-menu', (element) => { status.showStatus(element) }, false)

39
src/search.css Normal file
View File

@ -0,0 +1,39 @@
.tapi-search-autocomplete {
/*the container must be positioned relative:*/
position: relative;
display: inline-block;
margin-right: 20px;
}
.tapi-search-autocomplete input {
border: 1px solid transparent;
background-color: #f1f1f1;
/*padding: 10px;*/
/*font-size: 16px;*/
}
.tapi-search-autocomplete input[type=text] {
background-color: #f1f1f1;
width: 100%;
}
.tapi-search-autocomplete-items {
position: absolute;
border: 1px solid #d4d4d4;
border-bottom: none;
border-top: none;
z-index: 99;
/*position the autocomplete items to be the same width as the container:*/
top: 100%;
left: 0;
right: 0;
}
.tapi-search-autocomplete-items div {
padding: 10px;
cursor: pointer;
background-color: #fff;
border-bottom: 1px solid #d4d4d4;
color: #000;
}
.tapi-search-autocomplete-items div:hover, .tapi-search-autocomplete-active {
/*when hovering an item:*/
background-color: #E7E6E6 !important;
}

View File

@ -1,3 +1,4 @@
import './search.css'
import { TapiContact } from './tapi-contact'
import { debounce } from './debounce'
import { axios, fireChangeEvents } from './utils'
@ -9,39 +10,29 @@ export class Search {
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-selected')
var items = document.getElementsByClassName('tapi-search-autocomplete-active')
if (items.length === 0) {
items = document.getElementsByClassName('tapi-search-result')
items = document.getElementsByClassName('tapi-search-autocomplete-item')
}
if (items.length > 0) {
this.dial((<HTMLElement>items[0]).dataset.tapiNumber)
} else {
this.dial((<HTMLInputElement>document.getElementById('tapiSearchInput')).value)
}
return false
}
var searchBox = document.createElement('div')
searchBox.classList.add('contact-search-box')
searchBox.classList.add('tapi-search-autocomplete')
searchBox.style.width = '200px'
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) }
@ -50,22 +41,15 @@ export class Search {
setTimeout(() => {
console.log('TAPI clear search results')
this.removeSearchResults()
}, 500)
}, 250)
}
searchWrapper.appendChild(search)
searchBox.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)
element.parentElement.insertBefore(form, element)
}
private removeSearchResults () {
var resultList = document.getElementById('tapiResults')
var resultList = document.getElementById('tapi-search-autocomplete-list')
if (resultList) {
resultList.parentNode.removeChild(resultList)
}
@ -74,12 +58,12 @@ export class Search {
private doSearchKeyDown (ev: KeyboardEvent) {
if (ev.key === 'ArrowUp') {
let items = document.getElementsByClassName('tapi-search-result-selected')
let items = document.getElementsByClassName('tapi-search-autocomplete-active')
if (items.length > 0) {
var prev = <Element>items[0].previousSibling
}
if (!prev) {
items = document.getElementsByClassName('tapi-search-result')
items = document.getElementsByClassName('tapi-search-autocomplete-item')
if (items.length > 0) {
prev = items[items.length - 1]
}
@ -89,12 +73,12 @@ export class Search {
prev.scrollIntoView(true)
}
} else if (ev.key === 'ArrowDown') {
let items = document.getElementsByClassName('tapi-search-result-selected')
let items = document.getElementsByClassName('tapi-search-autocomplete-active')
if (items.length > 0) {
var next = <Element>items[0].nextSibling
}
if (!next) {
items = document.getElementsByClassName('tapi-search-result')
items = document.getElementsByClassName('tapi-search-autocomplete-item')
if (items.length > 0) {
next = items[0]
}
@ -125,57 +109,43 @@ export class Search {
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)
var results = document.createElement('div');
results.setAttribute('id', 'tapi-search-autocomplete-list')
results.setAttribute('class', 'tapi-search-autocomplete-items')
document.getElementById('tapiSearchBox').appendChild(results)
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_MEDIUM + ': ' + contact.tD_NUMBER_TAPI))
resultText.appendChild(line2)
resultList.appendChild(li)
var item = document.createElement('div');
item.setAttribute('class', 'tapi-search-autocomplete-item')
item.appendChild(document.createTextNode(contact.tD_NAME))
item.appendChild(document.createTextNode(contact.tD_MEDIUM + ': ' + contact.tD_NUMBER_TAPI))
item.onclick = () => { this.dial(contact.tD_NUMBER_TAPI) }
item.onmouseover = () => { this.selectResult(item) }
item.dataset.tapiNumber = contact.tD_NUMBER_TAPI
results.appendChild(item);
})
}, 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')
private selectResult (item: Element) {
console.log('Select item', item)
var items = document.getElementsByClassName('tapi-search-autocomplete-active')
for (var i of items) {
i.classList.remove('tapi-search-autocomplete-active')
}
resultLi.classList.add('bg-light')
resultLi.classList.add('tapi-search-result-selected')
item.classList.add('tapi-search-autocomplete-active')
}
private dial (number: string) {
var searchInput = document.getElementsByName('searchByNumberInput')
if (searchInput.length > 0) {
(<HTMLInputElement>searchInput[0]).value = number
searchInput[0].focus()
console.log('TAPI Search dialing', number);
var searchInput = document.getElementById('dialpad-input');
(<HTMLInputElement>searchInput).value = number;
(<HTMLInputElement>searchInput).focus;
fireChangeEvents(searchInput);
fireChangeEvents(searchInput[0])
var toaster = document.querySelector('toaster-container');
if (window.getComputedStyle(toaster, null).display == 'none') {
document.getElementById('menuDialer').click();
}
}
}

View File

@ -18,54 +18,104 @@ export class Status {
this._statusOff = await GM.getValue('tapi-zc-off', 'menuAvailable');
console.log('tapi-zc-user', this._user, 'tapi-zc-enabled', this._enabled, 'tapi-zc-on', this._statusOn, 'tapi-zc-off', this._statusOff);
var div = document.createElement('div');
div.classList.add('tapi-dropdown');
this.checkStatus();
var button = document.createElement('button');
button.id = 'tapi-zc-button';
button.classList.add('btn');
button.classList.add('btn-default');
button.innerText = 'ZeitConsens';
button.onclick = () => {
document.getElementById('tapi-zc-dropdown').classList.toggle('show');
waitForKeyElements("wc-account-menu > div > ul", (element: HTMLElement) => { this.addZcStatusPopup(element) }, true);
}
div.appendChild(button);
private async checkStatus() {
if (this._enabled) {
try {
var response = await axios.get<ZcStatus>('http://cpatapi.cpsrvweb2016.cp-austria.at/availability/' + encodeURIComponent(this._user));
if (response.status == 200) {
var status = response.data;
if (this._currentStatus !== status.loggedIn) {
this._currentStatus = status.loggedIn;
console.log('New status, loggedIn', this._currentStatus);
(document.getElementsByTagName("wcavatar")[0] as HTMLAnchorElement).click();
setTimeout(() => {
var statusId = this._currentStatus ? this._statusOn : this._statusOff;
(document.getElementById(statusId) as HTMLSpanElement).click();
}, 1000);
}
}
} catch (error) {
console.log(error);
}
setTimeout(() => this.checkStatus(), 30000);
}
}
private addZcStatusPopup(element: HTMLElement) {
var divider = document.createElement('li');
divider.classList.add('divider');
divider.classList.add('dropdown-divider');
element.appendChild(divider);
var menu = document.createElement('li');
element.appendChild(menu);
var link = document.createElement('a');
link.id = 'tapi-zc-button';
link.innerText = 'ZeitConsens';
link.classList.add('dropdown-item');
link.classList.add('d-flex');
link.onclick = () => {
document.getElementById('zc-modal').classList.toggle('show');
}
menu.appendChild(link);
var html =
'<div class="form-group">' +
'<div role="document" class="modal-dialog">' +
' <div class="modal-content">' +
' <div class="modal-header">' +
' <h4 class="modal-title float-left">ZeitConsens Status</h4><button id="zc-btnClose" type="button" aria-label="Close" class="close float-right"><span aria-hidden="true">×</span></button>' +
' </div>' +
' <div class="modal-body">' +
' <div class="form-group">' +
' <label for="tapi-zc-user">Username</label>' +
' <input type="text" class="form-control" name="tapi-zc-user" id="tapi-zc-user">' +
'</div>' +
'<div class="form-group">' +
' </div>' +
' <div class="form-group">' +
' <label for="tapi-zc-on">Signed in</label>' +
' <select id="tapi-zc-on" class="form-control">' +
' <option value="menuAvailable">Available</option>' +
' <option value="menuOutofoffice">Do Not Disturb</option>' +
' <option value="menuCustom1">Verfügbar DW</option>' +
' </select>' +
'</div>' +
'<div class="form-group">' +
' </div>' +
' <div class="form-group">' +
' <label for="tapi-zc-off">Signed out</label>' +
' <select id="tapi-zc-off" class="form-control">' +
' <option value="menuAway">Away</option>' +
' <option value="menuOutofoffice">Do Not Disturb</option>' +
' </select>' +
'</div>' +
'<div class="checkbox">' +
' </div>' +
' <div class="checkbox">' +
' <label class="i-checks" for="tapi-zc-enabled">' +
' <input type="checkbox" id="tapi-zc-enabled">' +
' <i></i><span>Enabled</span>' +
'</label>'
' </label>'
' </div>';
' </div>' +
' <div class="modal-footer">' +
' <button id="zc-btnOk" type="button" class="btn btn-primary">OK </button>' +
' <button id="zc-btnCancel" type="button" class="btn btn-light">Cancel </button>' +
' </div>' +
' </div>' +
'</div>';
var modal = document.createElement('modal-container');
modal.id = 'zc-modal';
modal.classList.add('modal');
modal.classList.add('fade');
modal.innerHTML = html;
var body = document.getElementsByTagName('body')[0].appendChild(modal);
var dropdown = document.createElement('div');
dropdown.classList.add('tapi-dropdown-content');
dropdown.classList.add('panel-body');
dropdown.id = 'tapi-zc-dropdown';
dropdown.innerHTML = html;
div.appendChild(dropdown);
element.insertBefore(div, element.firstChild);
var btnClose = document.getElementById('zc-btnClose');
btnClose.onclick = () => {
document.getElementById('zc-modal').classList.toggle('show');
}
var zcUser = document.getElementById('tapi-zc-user') as HTMLInputElement;
zcUser.value = this._user;
@ -103,31 +153,5 @@ export class Status {
console.log('tapi-zc-off', this._statusOff);
this._currentStatus = undefined;
}
this.checkStatus();
}
private async checkStatus() {
if (this._enabled) {
try {
var response = await axios.get<ZcStatus>('http://cpatapi.cpsrvweb2016.cp-austria.at/availability/' + encodeURIComponent(this._user));
if (response.status == 200) {
var status = response.data;
if (this._currentStatus !== status.loggedIn) {
this._currentStatus = status.loggedIn;
console.log('New status, loggedIn', this._currentStatus);
(document.getElementsByClassName("current-status")[0] as HTMLAnchorElement).click();
setTimeout(() => {
var statusId = this._currentStatus ? this._statusOn : this._statusOff;
(document.getElementById(statusId) as HTMLAnchorElement).click();
}, 1000);
}
}
} catch (error) {
console.log(error);
}
setTimeout(() => this.checkStatus(), 30000);
}
}
}