Compare commits

..

No commits in common. "master" and "v9.0.0" have entirely different histories.

16 changed files with 16489 additions and 8141 deletions

File diff suppressed because it is too large Load Diff

View File

@ -9,8 +9,8 @@ module.exports = {
source: pkg.repository.url,
downloadURL: 'https://source.cp-austria.at/git/CPATRD/3cx_tapi/raw/branch/master/3CX_TAPI.user.js',
match: [
'https://192.168.0.154:5001/*',
'https://cpsolution.my3cx.at:5001/*'
'https://192.168.0.154:5001/webclient*',
'https://cpsolution.my3cx.at:5001/webclient*'
],
require: [
'https://cdn.jsdelivr.net/gh/CoeJoder/waitForKeyElements.js@v1.2/waitForKeyElements.js',

View File

@ -15,6 +15,8 @@ const webpackConfig = {
},
externals: {
jquery: '$',
axios: 'axios',
'axios-userscript-adapter': 'axiosGmxhrAdapter'
},
module: {
rules: [

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

12579
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{
"name": "3cx-tapi",
"description": "3CX CP Tapi and Projectmanager integration",
"version": "9.2.2",
"version": "9.0.0",
"author": {
"name": "Daniel Triendl",
"email": "d.triendl@cp-solutions.at"
@ -24,32 +24,34 @@
},
"private": true,
"dependencies": {
"chrono-node": "^2.7.7",
"@trim21/gm-fetch": "^0.1.16"
"axios": "0.21.1",
"axios-userscript-adapter": "0.1.4",
"chrono-node": "^2.3.0"
},
"devDependencies": {
"@types/greasemonkey": "^4.0.7",
"@babel/core": "^7.25.8",
"@babel/preset-env": "^7.25.8",
"@typescript-eslint/eslint-plugin": "^8.8.1",
"@typescript-eslint/parser": "^8.8.1",
"babel-loader": "^9.2.1",
"browserslist": "^4.21.9",
"css-loader": "^7.1.2",
"eslint": "^9.12.0",
"eslint-plugin-import": "^2.31.0",
"@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",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "^7.1.0S",
"less": "4.2.0",
"less-loader": "^12.2.0",
"style-loader": "^4.0.0",
"ts-loader": "^9.5.1",
"typescript": "^5.6.3",
"userscript-metadata-webpack-plugin": "^0.4.0",
"webpack": "^5.95.0",
"webpack-bundle-analyzer": "^4.10.2",
"webpack-cli": "^5.1.4",
"webpack-livereload-plugin": "3.0.2",
"webpack-merge": "^6.0.1"
"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"
}
}

View File

@ -1,7 +1,6 @@
import * as chrono from 'chrono-node'
import { TapiContact } from './tapi-contact'
import { extractNumber } from './utils'
import GM_fetch from '@trim21/gm-fetch'
import { axios, extractNumber } from './utils'
export class CallHistory {
private callerIds: { [number: string]: TapiContact } = {}
@ -23,10 +22,9 @@ export class CallHistory {
date = dateParts.groups.date
duration = dateParts.groups.duration
}
var parsedDate = chrono.parseDate(date)
var parsedDateDe = chrono.de.parseDate(date)
if (parsedDateDe) {
parsedDate = parsedDateDe
var parsedDate = chrono.de.parseDate(date)
if (!parsedDate) {
parsedDate = chrono.parseDate(date)
}
if (!parsedDate) {
return
@ -86,10 +84,10 @@ export class CallHistory {
if (this.callerIds[number] !== undefined) {
this.updateCallHistoryEntry(element, this.callerIds[number])
} else {
var response = await GM_fetch('http://cpatapi.cpsrvweb2016.cp-austria.at/callerid/' + encodeURIComponent(number))
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 = await response.json() as TapiContact
callerId = response.data
}
console.log('TAPI call histroy callerid response', number, response, callerId)
this.callerIds[number] = callerId

View File

@ -1,6 +1,5 @@
import GM_fetch from '@trim21/gm-fetch'
import { TapiContact } from './tapi-contact'
import { extractNumber } from './utils'
import { axios, extractNumber } from './utils'
export class CallNotification {
public async showCallNotification (element: HTMLElement) {
@ -14,19 +13,19 @@ export class CallNotification {
}
console.log('TAPI searching callerid for', number)
var response = await GM_fetch('http://cpatapi.cpsrvweb2016.cp-austria.at/callerid/' + encodeURIComponent(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 = await response.json() as TapiContact
var callerId = response.data
if (callerId) {
notification.text = callerId.tD_NAME + '\r\n' + number + ' (' + callerId.tD_MEDIUM + ')'
}
}
// eslint-disable-next-line no-undef
GM.notification(notification.text, 'TAPI Anruf')
GM.notification(notification)
}
}

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

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

View File

@ -9,7 +9,7 @@ console.log('script start')
const search = new Search()
// eslint-disable-next-line no-undef
waitForKeyElements('ongoing-call-button', (element) => { search.createSearchWindow(element) }, false)
waitForKeyElements('ongoing-call-button', (element) => { search.createSearchWindow(element) }, true)
const callNotification = new CallNotification()
// eslint-disable-next-line no-undef

View File

@ -1,42 +0,0 @@
.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 p {
margin: 0;
}
.tapi-search-autocomplete-items div:hover, .tapi-search-autocomplete-active {
/*when hovering an item:*/
background-color: #E7E6E6 !important;
}

View File

@ -1,8 +1,6 @@
import './search.css'
import { TapiContact } from './tapi-contact'
import { debounce } from './debounce'
import { fireChangeEvents } from './utils'
import GM_fetch from '@trim21/gm-fetch'
import { axios, fireChangeEvents } from './utils'
export class Search {
private currentSearchText = ''
@ -11,29 +9,39 @@ 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-autocomplete-active')
var items = document.getElementsByClassName('tapi-search-result-selected')
if (items.length === 0) {
items = document.getElementsByClassName('tapi-search-autocomplete-item')
items = document.getElementsByClassName('tapi-search-result')
}
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('tapi-search-autocomplete')
searchBox.style.width = '200px'
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) }
@ -42,15 +50,22 @@ export class Search {
setTimeout(() => {
console.log('TAPI clear search results')
this.removeSearchResults()
}, 250)
}, 500)
}
searchBox.appendChild(search)
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.parentElement.insertBefore(form, element)
}
private removeSearchResults () {
var resultList = document.getElementById('tapi-search-autocomplete-list')
var resultList = document.getElementById('tapiResults')
if (resultList) {
resultList.parentNode.removeChild(resultList)
}
@ -59,12 +74,12 @@ export class Search {
private doSearchKeyDown (ev: KeyboardEvent) {
if (ev.key === 'ArrowUp') {
let items = document.getElementsByClassName('tapi-search-autocomplete-active')
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-autocomplete-item')
items = document.getElementsByClassName('tapi-search-result')
if (items.length > 0) {
prev = items[items.length - 1]
}
@ -74,12 +89,12 @@ export class Search {
prev.scrollIntoView(true)
}
} else if (ev.key === 'ArrowDown') {
let items = document.getElementsByClassName('tapi-search-autocomplete-active')
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-autocomplete-item')
items = document.getElementsByClassName('tapi-search-result')
if (items.length > 0) {
next = items[0]
}
@ -103,39 +118,55 @@ export class Search {
return
}
console.log('Searching TAPI')
var response = await GM_fetch('http://cpatapi.cpsrvweb2016.cp-austria.at/search?query=' + encodeURIComponent(searchText))
var response = await axios.get<TapiContact[]>('http://cpatapi.cpsrvweb2016.cp-austria.at/search?query=' + encodeURIComponent(searchText))
console.log('TAPI Search response', response)
var contacts = await response.json() as TapiContact[]
var contacts = response.data
console.log('TAPI Contacts', contacts)
this.removeSearchResults()
this.currentSearchText = searchText
var results = document.createElement('div');
results.setAttribute('id', 'tapi-search-autocomplete-list')
results.setAttribute('class', 'tapi-search-autocomplete-items')
document.getElementById('tapiSearchBox').appendChild(results)
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 item = document.createElement('div');
item.setAttribute('class', 'tapi-search-autocomplete-item')
var p = document.createElement('p')
p.innerHTML = contact.tD_NAME + '<br>' + contact.tD_MEDIUM + ': ' + contact.tD_NUMBER_TAPI
item.appendChild(p)
item.onclick = () => { this.dial(contact.tD_NUMBER_TAPI) }
item.onmouseover = () => { this.selectResult(item) }
item.dataset.tapiNumber = contact.tD_NUMBER_TAPI
results.appendChild(item);
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)
})
}, 200)
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')
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')
}
item.classList.add('tapi-search-autocomplete-active')
resultLi.classList.add('bg-light')
resultLi.classList.add('tapi-search-result-selected')
}
private dial (number: string) {

View File

@ -1,6 +1,6 @@
import './status.css';
import { axios } from './utils';
import { ZcStatus } from './zc-status';
import GM_fetch from "@trim21/gm-fetch";
declare function waitForKeyElements(selectorOrFunction: any, callback: any, waitOnce: boolean): any;
@ -26,18 +26,16 @@ export class Status {
private async checkStatus() {
if (this._enabled) {
try {
var response = await GM_fetch('http://cpatapi.cpsrvweb2016.cp-austria.at/availability/' + encodeURIComponent(this._user));
var response = await axios.get<ZcStatus>('http://cpatapi.cpsrvweb2016.cp-austria.at/availability/' + encodeURIComponent(this._user));
if (response.status == 200) {
var status = await response.json() as ZcStatus;
var status = response.data;
if (this._currentStatus !== status.loggedIn) {
this._currentStatus = status.loggedIn;
console.log('New status, loggedIn', this._currentStatus);
var accMenu = document.getElementsByTagName("wc-account-menu")[0];
var avatar = accMenu.getElementsByTagName("app-avatar")[0] as HTMLAnchorElement;
avatar.click();
(document.getElementsByTagName("wcavatar")[0] as HTMLAnchorElement).click();
setTimeout(() => {
var statusId = this._currentStatus ? this._statusOn : this._statusOff;
console.log('Clicking status', statusId);
(document.getElementById(statusId) as HTMLSpanElement).click();
}, 1000);
}
@ -72,7 +70,7 @@ export class Status {
'<div role="document" class="modal-dialog">' +
' <div class="modal-content">' +
' <div class="modal-header">' +
' <h4 class="modal-title">ZeitConsens Status</h4><button id="zc-btnClose" type="button" aria-label="Close" class="btn-close" data-qa="modal-cross"></button>' +
' <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">' +
@ -98,13 +96,13 @@ export class Status {
' <label class="i-checks" for="tapi-zc-enabled">' +
' <input type="checkbox" id="tapi-zc-enabled">' +
' <i></i><span>Enabled</span>' +
' </label>' +
' </div>' +
' </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 class="modal-footer">' +
//' <button id="zc-btnOk" type="button" class="btn btn-primary" data-qa="modal-ok">OK </button>' +
//' <button id="zc-btnCancel" type="button" class="btn btn-border" data-qa="modal-close">Cancel </button>' +
//' </div>' +
' </div>' +
'</div>';
var modal = document.createElement('modal-container');
@ -112,7 +110,7 @@ export class Status {
modal.classList.add('modal');
modal.classList.add('fade');
modal.innerHTML = html;
document.getElementsByTagName('body')[0].appendChild(modal);
var body = document.getElementsByTagName('body')[0].appendChild(modal);
var btnClose = document.getElementById('zc-btnClose');
btnClose.onclick = () => {

View File

@ -1,3 +1,23 @@
/**
* @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]{4,})/.exec(s)
if (!match) {

View File

@ -2,8 +2,8 @@
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "ESNext",
"target": "ES2022",
"module": "es6",
"target": "es6",
"allowJs": true,
"moduleResolution": "node"
}