Compare commits

..

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

28 changed files with 13149 additions and 11660 deletions

View File

@ -1,9 +0,0 @@
module.exports = function (api) {
api.cache(true)
const presets = ['@babel/preset-env']
return {
presets,
}
}

View File

@ -1,3 +0,0 @@
> 1%
not IE 11
not dead

File diff suppressed because one or more lines are too long

View File

@ -5,12 +5,12 @@ module.exports = {
namespace: 'http://cp-solutions.at', namespace: 'http://cp-solutions.at',
version: pkg.version, version: pkg.version,
author: pkg.author, author: pkg.author,
copyright: 'Copyright 2021 CP Solutions GmbH', copyright: 'Copyright 2020 CP Solutions GmbH',
source: pkg.repository.url, source: pkg.repository.url,
downloadURL: 'https://source.cp-austria.at/git/CPATRD/3cx_tapi/raw/branch/master/3CX_TAPI.user.js', downloadURL: 'http://scootaloo.cp-austria.at/gitlist/3cx_tapi.git/raw/master/3CX_TAPI.user.js',
match: [ match: [
'https://192.168.0.154:5001/*', 'https://192.168.0.154:5001/webclient*',
'https://cpsolution.my3cx.at:5001/*' 'https://cpsolution.my3cx.at:5001/webclient*'
], ],
require: [ require: [
'https://cdn.jsdelivr.net/gh/CoeJoder/waitForKeyElements.js@v1.2/waitForKeyElements.js', 'https://cdn.jsdelivr.net/gh/CoeJoder/waitForKeyElements.js@v1.2/waitForKeyElements.js',
@ -18,10 +18,8 @@ module.exports = {
`https://cdn.jsdelivr.net/npm/axios-userscript-adapter@${pkg.dependencies['axios-userscript-adapter']}/dist/axiosGmxhrAdapter.min.js` `https://cdn.jsdelivr.net/npm/axios-userscript-adapter@${pkg.dependencies['axios-userscript-adapter']}/dist/axiosGmxhrAdapter.min.js`
], ],
grant: [ grant: [
'GM.xmlHttpRequest', 'GM_xmlhttpRequest',
'GM.notification', 'GM.notification'
'GM.getValue',
'GM.setValue'
], ],
connect: [ connect: [
'cpatapi.cpsrvweb2016.cp-austria.at' 'cpatapi.cpsrvweb2016.cp-austria.at'

View File

@ -1,36 +1,42 @@
const path = require('path') const path = require('path')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') const webpack = require('webpack')
const webpackConfig = { const webpackConfig = {
node: {
Buffer: false
},
resolve: { resolve: {
extensions: ['.js', '.ts'] extensions: ['.js', '.ts']
}, },
// performance: {
// hints: false
// },
optimization: { optimization: {
minimize: false, minimize: false
moduleIds: 'named',
}, },
entry: './src/index.js', entry: './src/js/index.js',
output: { output: {
path: path.resolve(__dirname, '../dist') path: path.resolve(__dirname, '../dist')
}, },
externals: { externals: {
jquery: '$', axios: 'axios',
'axios-userscript-adapter': 'axiosGmxhrAdapter'
}, },
module: { module: {
rules: [ rules: [
{ {
use: {
loader: 'babel-loader',
},
test: /\.js$/, test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader'
}, },
{ {
test: /\.ts$/, test: /\.ts$/,
exclude: /node_modules/,
loader: 'ts-loader' loader: 'ts-loader'
}, },
{ {
test: /\.less$/, test: /\.less$/,
use: [ loader: [
'style-loader', 'style-loader',
'css-loader', 'css-loader',
'less-loader', // 将 Less 编译为 CSS 'less-loader', // 将 Less 编译为 CSS
@ -38,14 +44,16 @@ const webpackConfig = {
}, },
{ {
test: /\.css$/, test: /\.css$/,
use: [ loader: [
'style-loader', 'style-loader',
'css-loader', 'css-loader',
] ]
} }
] ]
}, },
plugins: process.env.npm_config_report ? [new BundleAnalyzerPlugin()] : [], plugins: [
new webpack.HashedModuleIdsPlugin()
]
} }
module.exports = webpackConfig module.exports = webpackConfig

View File

@ -1,37 +0,0 @@
const path = require('path')
const { merge } = require('webpack-merge')
const LiveReloadPlugin = require('webpack-livereload-plugin')
const { UserScriptMetaDataPlugin } = require('userscript-metadata-webpack-plugin')
const metadata = require('./metadata.cjs')
const webpackConfig = require('./webpack.config.base.cjs')
metadata.require.push(
'file://' + path.resolve(__dirname, '../dist/index.prod.user.js')
)
const cfg = merge(webpackConfig, {
entry: {
prod: webpackConfig.entry,
dev: path.resolve(__dirname, './empty.cjs'),
},
output: {
filename: 'index.[name].user.js',
path: path.resolve(__dirname, '../dist'),
},
devtool: 'inline-source-map',
watch: true,
watchOptions: {
ignored: /node_modules/,
},
plugins: [
new LiveReloadPlugin({
delay: 500,
}),
new UserScriptMetaDataPlugin({
metadata,
}),
],
})
module.exports = cfg

View File

@ -0,0 +1,43 @@
const path = require("path");
const { merge } = require("webpack-merge");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
const LiveReloadPlugin = require("webpack-livereload-plugin");
const UserScriptMetaDataPlugin = require("userscript-metadata-webpack-plugin");
const metadata = require("./metadata");
const webpackConfig = require("./webpack.config.base");
metadata.require.push(
"file://" + path.resolve(__dirname, "../dist/index.prod.user.js")
);
const cfg = merge(webpackConfig, {
entry: {
prod: webpackConfig.entry,
dev: path.resolve(__dirname, "./empty.js"),
},
output: {
filename: "index.[name].user.js",
path: path.resolve(__dirname, "../dist"),
},
devtool: "inline-source-map",
watch: true,
watchOptions: {
ignored: /node_modules/,
},
plugins: [
new LiveReloadPlugin({
delay: 500,
}),
new UserScriptMetaDataPlugin({
metadata,
}),
],
});
if (process.env.npm_config_report) {
cfg.plugins.push(new BundleAnalyzerPlugin());
}
module.exports = cfg;

View File

@ -1,19 +0,0 @@
const { merge } = require('webpack-merge')
const { UserScriptMetaDataPlugin } = require('userscript-metadata-webpack-plugin')
const metadata = require('./metadata.cjs')
const webpackConfig = require('./webpack.config.base.cjs')
const cfg = merge(webpackConfig, {
mode: 'production',
output: {
filename: metadata.name + '.prod.user.js',
},
plugins: [
new UserScriptMetaDataPlugin({
metadata,
}),
],
})
module.exports = cfg

View File

@ -0,0 +1,23 @@
const { merge } = require("webpack-merge");
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const UserScriptMetaDataPlugin = require('userscript-metadata-webpack-plugin')
const metadata = require('./metadata')
const webpackConfig = require('./webpack.config.base')
const cfg = merge({}, webpackConfig, {
output: {
filename: 'index.prod.user.js'
},
plugins: [
new UserScriptMetaDataPlugin({
metadata
})
]
})
if (process.env.npm_config_report) {
cfg.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = cfg

13302
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,55 +1,58 @@
{ {
"name": "3cx-tapi", "name": "3cp-tapi",
"description": "3CX CP Tapi and Projectmanager integration", "description": "Build your UserScript with webpack",
"version": "9.2.2", "version": "7.0.3",
"author": { "author": {
"name": "Daniel Triendl", "name": "Daniel Triendl",
"email": "d.triendl@cp-solutions.at" "email": "d.triendl@cp-solutions.at"
}, },
"browserslist": [
"last 2 version",
"> 1%"
],
"eslintIgnore": [ "eslintIgnore": [
"dist/*.js", "dist/*.js"
"node_modules"
], ],
"scripts": { "scripts": {
"lint": "eslint --ext .ts,.js src", "lint": "eslint src",
"preversion": "npm run lint", "preversion": "npm run lint",
"postversion": "git push --follow-tags", "postversion": "git push --follow-tags",
"anylize": "npm_config_report=true npm run build", "anylize": "npm_config_report=true npm run build",
"build": "webpack --mode production --config config/webpack.config.production.cjs", "build": "webpack --mode production --config config/webpack.config.production.js",
"dev": "webpack --mode development --config config/webpack.config.dev.cjs" "dev": "webpack --mode development --config config/webpack.config.dev.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://source.cp-austria.at/git/CPATRD/3cx_tapi.git" "url": "http://scootaloo.cp-austria.at/gitlist/3cx_tapi.git"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"chrono-node": "^2.7.7", "axios": "0.20.0",
"@trim21/gm-fetch": "^0.1.16" "axios-userscript-adapter": "0.0.7",
"chrono-node": "2.1.9"
}, },
"devDependencies": { "devDependencies": {
"@types/greasemonkey": "^4.0.7", "@typescript-eslint/eslint-plugin": "4.3.0",
"@babel/core": "^7.25.8", "@typescript-eslint/parser": "4.3.0",
"@babel/preset-env": "^7.25.8", "css-loader": "4.3.0",
"@typescript-eslint/eslint-plugin": "^8.8.1", "eslint": "7.10.0",
"@typescript-eslint/parser": "^8.8.1", "eslint-config-standard": "14.1.1",
"babel-loader": "^9.2.1", "eslint-loader": "4.0.2",
"browserslist": "^4.21.9", "eslint-plugin-import": "2.22.1",
"css-loader": "^7.1.2",
"eslint": "^9.12.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-node": "11.1.0", "eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "^7.1.0S", "eslint-plugin-promise": "4.2.1",
"less": "4.2.0", "eslint-plugin-standard": "4.0.1",
"less-loader": "^12.2.0", "less": "3.12.2",
"style-loader": "^4.0.0", "less-loader": "7.0.1",
"ts-loader": "^9.5.1", "style-loader": "1.2.1",
"typescript": "^5.6.3", "ts-loader": "8.0.4",
"userscript-metadata-webpack-plugin": "^0.4.0", "typescript": "4.0.3",
"webpack": "^5.95.0", "userscript-metadata-webpack-plugin": "0.0.6",
"webpack-bundle-analyzer": "^4.10.2", "webpack": "4.44.2",
"webpack-cli": "^5.1.4", "webpack-bundle-analyzer": "3.9.0",
"webpack-livereload-plugin": "3.0.2", "webpack-cli": "3.3.12",
"webpack-merge": "^6.0.1" "webpack-dev-server": "^3.11.0",
"webpack-livereload-plugin": "2.3.0",
"webpack-merge": "5.1.4"
} }
} }

View File

@ -43,7 +43,7 @@ npm run build
## distribution ## distribution
``` ```
cp "dist/3CX TAPI.prod.user.js" 3CX_TAPI.user.js cp dist/index.prod.user.js 3CX_TAPI.user.js
``` ```
And commit 3CX_TAPI.user.js And commit 3CX_TAPI.user.js

View File

@ -1,18 +1,23 @@
import * as chrono from 'chrono-node' import * as chrono from 'chrono-node'
import { TapiContact } from './tapi-contact' import { TapiContact } from './tapi-contact'
import { extractNumber } from './utils' import { axios, extractNumber } from './utils'
import GM_fetch from '@trim21/gm-fetch'
export class CallHistory { export class CallHistory {
private callerIds: { [number: string]: TapiContact } = {} private callerIds: { [number: string]: TapiContact } = {}
private updateCallHistoryEntry (call: HTMLElement, callerId: TapiContact) { private updateCallHistoryEntry (call: HTMLElement, callerId: TapiContact) {
var span = call.querySelector(':scope > span') var span = call.querySelector('span')
this.showTimeManager(call, call.querySelector('.date').textContent, callerId) this.showTimeManager(call, span.nextSibling.textContent.trim(), callerId)
if (callerId && callerId.tD_NAME !== '') { if (callerId && callerId.tD_NAME !== '') {
var text = span.textContent var text = span.textContent
span.textContent = callerId.tD_NAME + ' ' + callerId.tD_NUMBER 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)
} }
} }
@ -23,10 +28,10 @@ export class CallHistory {
date = dateParts.groups.date date = dateParts.groups.date
duration = dateParts.groups.duration duration = dateParts.groups.duration
} }
var parsedDate = chrono.parseDate(date)
var parsedDateDe = chrono.de.parseDate(date) var parsedDate = chrono.de.parseDate(date)
if (parsedDateDe) { if (!parsedDate) {
parsedDate = parsedDateDe parsedDate = chrono.parseDate(date)
} }
if (!parsedDate) { if (!parsedDate) {
return return
@ -45,7 +50,7 @@ export class CallHistory {
var length = (parsedDuration.getHours() * 60 + parsedDuration.getMinutes()).toString() var length = (parsedDuration.getHours() * 60 + parsedDuration.getMinutes()).toString()
var toolbar = call.querySelector('call-history-options') var toolbar = call.querySelector('.wcToolbarTiles')
var href = 'domizil://PM/Zeitbuchung?' var href = 'domizil://PM/Zeitbuchung?'
if (callerId && callerId.tD_ID) { if (callerId && callerId.tD_ID) {
href += 'KontaktId=' + callerId.tD_ID + '&' href += 'KontaktId=' + callerId.tD_ID + '&'
@ -57,7 +62,7 @@ export class CallHistory {
a.onclick = () => { a.onclick = () => {
window.open(href) window.open(href)
} }
a.innerHTML = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 559.98 559.98">' + 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>' +
' <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' + ' <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' +
@ -69,14 +74,11 @@ export class CallHistory {
' </g>' + ' </g>' +
'</g>' + '</g>' +
'</svg>' '</svg>'
a.classList.add('btn');
a.classList.add('btn-plain');
toolbar.insertBefore(a, toolbar.firstChild) toolbar.insertBefore(a, toolbar.firstChild)
} }
public async showCallHistory (element: HTMLElement) { public async showCallHistory (element: HTMLElement) {
var span = element.querySelector(':scope > span') var span = element.querySelector('span')
var number = extractNumber(span.textContent) var number = extractNumber(span.textContent)
if (!number) { if (!number) {
this.updateCallHistoryEntry(element, undefined) this.updateCallHistoryEntry(element, undefined)
@ -86,10 +88,10 @@ export class CallHistory {
if (this.callerIds[number] !== undefined) { if (this.callerIds[number] !== undefined) {
this.updateCallHistoryEntry(element, this.callerIds[number]) this.updateCallHistoryEntry(element, this.callerIds[number])
} else { } 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: '' } var callerId: TapiContact = { tD_NAME: '' }
if (response.status === 200) { if (response.status === 200) {
callerId = await response.json() as TapiContact callerId = response.data
} }
console.log('TAPI call histroy callerid response', number, response, callerId) console.log('TAPI call histroy callerid response', number, response, callerId)
this.callerIds[number] = callerId this.callerIds[number] = callerId

View File

@ -1,10 +1,9 @@
import GM_fetch from '@trim21/gm-fetch'
import { TapiContact } from './tapi-contact' import { TapiContact } from './tapi-contact'
import { extractNumber } from './utils' import { axios, extractNumber } from './utils'
export class CallNotification { export class CallNotification {
public async showCallNotification (element: HTMLElement) { public async showCallNotification (element: HTMLElement) {
var number = element.querySelector('.callNumber').textContent var number = element.dataset.id
console.log('TAPI call notification', number) console.log('TAPI call notification', number)
number = extractNumber(number) number = extractNumber(number)
@ -14,19 +13,19 @@ export class CallNotification {
} }
console.log('TAPI searching callerid for', number) 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) console.log('TAPI callerid response', response)
var notification = { var notification = {
text: number text: number
} }
if (response.status === 200) { if (response.status === 200) {
var callerId = await response.json() as TapiContact var callerId = response.data
if (callerId) { if (callerId) {
notification.text = callerId.tD_NAME + '\r\n' + number + ' (' + callerId.tD_MEDIUM + ')' notification.text = callerId.tD_NAME + '\r\n' + number + ' (' + callerId.tD_MEDIUM + ')'
} }
} }
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
GM.notification(notification.text, 'TAPI Anruf') GM.notification(notification)
} }
} }

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

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

View File

@ -3,13 +3,12 @@ import * as chrono from 'chrono-node'
import { CallHistory } from './call-history' import { CallHistory } from './call-history'
import { CallNotification } from './call-notification' import { CallNotification } from './call-notification'
import { Search } from './search' import { Search } from './search'
import { Status } from './status'
console.log('script start') console.log('script start')
const search = new Search() const search = new Search()
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
waitForKeyElements('ongoing-call-button', (element) => { search.createSearchWindow(element) }, false) waitForKeyElements('div.nav-search', (element) => { search.createSearchWindow(element) }, true)
const callNotification = new CallNotification() const callNotification = new CallNotification()
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
@ -18,7 +17,3 @@ waitForKeyElements('call-view', (element) => { callNotification.showCallNotifica
const callHistory = new CallHistory() const callHistory = new CallHistory()
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
waitForKeyElements('.call-history-list call', (element) => { callHistory.showCallHistory(element) }, false) waitForKeyElements('.call-history-list call', (element) => { callHistory.showCallHistory(element) }, false)
const status = new Status()
// eslint-disable-next-line no-undef
waitForKeyElements('wc-account-menu', (element) => { status.showStatus(element) }, false)

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

@ -0,0 +1,197 @@
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-selected')
if (items.length === 0) {
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_MEDIUM + ': ' + 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)
}
}

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]{4,})/.exec(s)
if (!match) {
return undefined
}
var number = match[1]
if (number.startsWith('+')) {
number = number.replace('+', '00')
}
return number
}

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,153 +0,0 @@
import './search.css'
import { TapiContact } from './tapi-contact'
import { debounce } from './debounce'
import { fireChangeEvents } from './utils'
import GM_fetch from '@trim21/gm-fetch'
export class Search {
private currentSearchText = ''
public createSearchWindow (element: HTMLElement) {
console.log('Create TAPI Search')
var form = document.createElement('form')
form.onsubmit = () => {
var items = document.getElementsByClassName('tapi-search-autocomplete-active')
if (items.length === 0) {
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('tapi-search-autocomplete')
searchBox.style.width = '200px'
searchBox.id = 'tapiSearchBox'
form.appendChild(searchBox)
var search = document.createElement('input')
search.id = 'tapiSearchInput'
search.autocomplete = 'off'
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()
}, 250)
}
searchBox.appendChild(search)
element.parentElement.insertBefore(form, element)
}
private removeSearchResults () {
var resultList = document.getElementById('tapi-search-autocomplete-list')
if (resultList) {
resultList.parentNode.removeChild(resultList)
}
this.currentSearchText = ''
}
private doSearchKeyDown (ev: KeyboardEvent) {
if (ev.key === 'ArrowUp') {
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-autocomplete-item')
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-autocomplete-active')
if (items.length > 0) {
var next = <Element>items[0].nextSibling
}
if (!next) {
items = document.getElementsByClassName('tapi-search-autocomplete-item')
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 GM_fetch('http://cpatapi.cpsrvweb2016.cp-austria.at/search?query=' + encodeURIComponent(searchText))
console.log('TAPI Search response', response)
var contacts = await response.json() as TapiContact[]
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)
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);
})
}, 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')
}
item.classList.add('tapi-search-autocomplete-active')
}
private dial (number: string) {
console.log('TAPI Search dialing', number);
var searchInput = document.getElementById('dialpad-input');
(<HTMLInputElement>searchInput).value = number;
(<HTMLInputElement>searchInput).focus;
fireChangeEvents(searchInput);
var toaster = document.querySelector('toaster-container');
if (window.getComputedStyle(toaster, null).display == 'none') {
document.getElementById('menuDialer').click();
}
}
}

View File

@ -1,19 +0,0 @@
.tapi-dropdown {
position: relative;
display: inline-block;
}
.tapi-dropdown-content {
display: none;
position: absolute;
min-width: 200px;
overflow: auto;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
color: #000;
background-color: #fff;
}
.show {
display: block;
}

View File

@ -1,159 +0,0 @@
import './status.css';
import { ZcStatus } from './zc-status';
import GM_fetch from "@trim21/gm-fetch";
declare function waitForKeyElements(selectorOrFunction: any, callback: any, waitOnce: boolean): any;
export class Status {
private _user: string;
private _enabled = false;
private _statusOn = 'menuAvailable';
private _statusOff = 'menuAway';
private _currentStatus: boolean = undefined;
public async showStatus(element: HTMLElement) {
this._user = await GM.getValue('tapi-zc-user', '');
this._enabled = await GM.getValue('tapi-zc-enabled', false);
this._statusOn = await GM.getValue('tapi-zc-on', 'menuAvailable');
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);
this.checkStatus();
waitForKeyElements("wc-account-menu > div > ul", (element: HTMLElement) => { this.addZcStatusPopup(element) }, true);
}
private async checkStatus() {
if (this._enabled) {
try {
var response = await GM_fetch('http://cpatapi.cpsrvweb2016.cp-austria.at/availability/' + encodeURIComponent(this._user));
if (response.status == 200) {
var status = await response.json() as ZcStatus;
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();
setTimeout(() => {
var statusId = this._currentStatus ? this._statusOn : this._statusOff;
console.log('Clicking status', statusId);
(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 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>' +
' </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">' +
' <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">' +
' <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">' +
' <label class="i-checks" for="tapi-zc-enabled">' +
' <input type="checkbox" id="tapi-zc-enabled">' +
' <i></i><span>Enabled</span>' +
' </label>' +
' </div>' +
' </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');
modal.id = 'zc-modal';
modal.classList.add('modal');
modal.classList.add('fade');
modal.innerHTML = html;
document.getElementsByTagName('body')[0].appendChild(modal);
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;
zcUser.onchange = () => {
this._user = zcUser.value;
GM.setValue('tapi-zc-user', this._user);
console.log('tapi-zc-user', this._user);
this._currentStatus = undefined;
}
var zcEnabled = document.getElementById('tapi-zc-enabled') as HTMLInputElement;
zcEnabled.checked = this._enabled;
zcEnabled.onchange = () => {
this._enabled = zcEnabled.checked;
GM.setValue('tapi-zc-enabled', this._enabled);
console.log('tapi-zc-enabled', this._enabled);
this._currentStatus = undefined;
this.checkStatus();
}
var zcOn = document.getElementById('tapi-zc-on') as HTMLSelectElement;
zcOn.value = this._statusOn;
zcOn.onchange = () => {
this._statusOn = zcOn.value;
GM.setValue('tapi-zc-on', this._statusOn);
console.log('tapi-zc-on', this._statusOn);
this._currentStatus = undefined;
}
var zcOff = document.getElementById('tapi-zc-off') as HTMLSelectElement;
zcOff.value = this._statusOff;
zcOff.onchange = () => {
this._statusOff = zcOff.value;
GM.setValue('tapi-zc-off', this._statusOff);
console.log('tapi-zc-off', this._statusOff);
this._currentStatus = undefined;
}
}
}

View File

@ -1,29 +0,0 @@
export function extractNumber (s: string) {
var match = /(\+?[0-9]{4,})/.exec(s)
if (!match) {
return undefined
}
var number = match[1]
if (number.startsWith('+')) {
number = number.replace('+', '00')
}
return number
}
export function 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)
}

View File

@ -1,4 +0,0 @@
export class ZcStatus {
public user: string;
public loggedIn: boolean;
}

View File

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