Switch to Typescript and webpack
This commit is contained in:
parent
e40a0810ff
commit
7db79afca2
15
.editorconfig
Normal file
15
.editorconfig
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[*]
|
||||||
|
charset=utf-8
|
||||||
|
end_of_line=lf
|
||||||
|
trim_trailing_whitespace=true
|
||||||
|
insert_final_newline=true
|
||||||
|
indent_style=space
|
||||||
|
indent_size=4
|
||||||
|
|
||||||
|
[{.babelrc,.stylelintrc,.eslintrc,jest.config,*.bowerrc,*.jsb3,*.jsb2,*.json,*.yaml,*.yml}]
|
||||||
|
indent_style=space
|
||||||
|
indent_size=2
|
||||||
|
|
||||||
|
[{*.js,*.vue,*.ts}]
|
||||||
|
indent_style=space
|
||||||
|
indent_size=2
|
28
.eslintrc.yaml
Normal file
28
.eslintrc.yaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
extends:
|
||||||
|
- standard
|
||||||
|
- plugin:import/recommended
|
||||||
|
- plugin:import/typescript # this line does the trick
|
||||||
|
|
||||||
|
# or configure manually:
|
||||||
|
plugins:
|
||||||
|
- import
|
||||||
|
- promise
|
||||||
|
- standard
|
||||||
|
- '@typescript-eslint'
|
||||||
|
|
||||||
|
parser: '@typescript-eslint/parser'
|
||||||
|
|
||||||
|
parserOptions:
|
||||||
|
sourceType: module
|
||||||
|
ecmaVersion: 6
|
||||||
|
|
||||||
|
env:
|
||||||
|
browser: true
|
||||||
|
node: true
|
||||||
|
|
||||||
|
rules:
|
||||||
|
comma-dangle: off
|
||||||
|
"import/order": "error"
|
||||||
|
"no-unused-vars": "off"
|
||||||
|
"@typescript-eslint/no-unused-vars": ["error"]
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
dist
|
6088
3CX_TAPI.user.js
6088
3CX_TAPI.user.js
File diff suppressed because one or more lines are too long
0
config/empty.js
Normal file
0
config/empty.js
Normal file
28
config/metadata.js
Normal file
28
config/metadata.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const pkg = require('../package.json')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: '3CX TAPI',
|
||||||
|
namespace: 'http://cp-solutions.at',
|
||||||
|
version: pkg.version,
|
||||||
|
author: pkg.author,
|
||||||
|
copyright: 'Copyright 2020 CP Solutions GmbH',
|
||||||
|
source: pkg.repository.url,
|
||||||
|
downloadURL: 'http://scootaloo.cp-austria.at/gitlist/3cx_tapi.git/raw/master/3CX_TAPI.user.js',
|
||||||
|
match: [
|
||||||
|
'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',
|
||||||
|
`https://cdn.jsdelivr.net/npm/axios@${pkg.dependencies.axios}/dist/axios.min.js`,
|
||||||
|
`https://cdn.jsdelivr.net/npm/axios-userscript-adapter@${pkg.dependencies['axios-userscript-adapter']}/dist/axiosGmxhrAdapter.min.js`
|
||||||
|
],
|
||||||
|
grant: [
|
||||||
|
'GM_xmlhttpRequest',
|
||||||
|
'GM.notification'
|
||||||
|
],
|
||||||
|
connect: [
|
||||||
|
'cpatapi.cpsrvweb2016.cp-austria.at'
|
||||||
|
],
|
||||||
|
'run-at': 'document-end'
|
||||||
|
}
|
59
config/webpack.config.base.js
Normal file
59
config/webpack.config.base.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const webpack = require('webpack')
|
||||||
|
|
||||||
|
const webpackConfig = {
|
||||||
|
node: {
|
||||||
|
Buffer: false
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js', '.ts']
|
||||||
|
},
|
||||||
|
// performance: {
|
||||||
|
// hints: false
|
||||||
|
// },
|
||||||
|
optimization: {
|
||||||
|
minimize: false
|
||||||
|
},
|
||||||
|
entry: './src/js/index.js',
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, '../dist')
|
||||||
|
},
|
||||||
|
externals: {
|
||||||
|
axios: 'axios',
|
||||||
|
'axios-userscript-adapter': 'axiosGmxhrAdapter'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
loader: 'eslint-loader'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.ts$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
loader: 'ts-loader'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.less$/,
|
||||||
|
loader: [
|
||||||
|
'style-loader',
|
||||||
|
'css-loader',
|
||||||
|
'less-loader', // 将 Less 编译为 CSS
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
loader: [
|
||||||
|
'style-loader',
|
||||||
|
'css-loader',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.HashedModuleIdsPlugin()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = webpackConfig
|
43
config/webpack.config.dev.js
Normal file
43
config/webpack.config.dev.js
Normal 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;
|
23
config/webpack.config.production.js
Normal file
23
config/webpack.config.production.js
Normal 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
|
8059
package-lock.json
generated
Normal file
8059
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
59
package.json
Normal file
59
package.json
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"name": "3cp-tapi",
|
||||||
|
"description": "Build your UserScript with webpack",
|
||||||
|
"version": "7.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Daniel Triendl",
|
||||||
|
"email": "d.triendl@cp-solutions.at"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"last 2 version",
|
||||||
|
"> 1%"
|
||||||
|
],
|
||||||
|
"eslintIgnore": [
|
||||||
|
"dist/*.js"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint src",
|
||||||
|
"preversion": "npm run lint",
|
||||||
|
"postversion": "git push --follow-tags",
|
||||||
|
"anylize": "npm_config_report=true npm run build",
|
||||||
|
"build": "webpack --mode production --config config/webpack.config.production.js",
|
||||||
|
"dev": "webpack --mode development --config config/webpack.config.dev.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "http://scootaloo.cp-austria.at/gitlist/3cx_tapi.git"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "0.20.0",
|
||||||
|
"axios-userscript-adapter": "0.0.7",
|
||||||
|
"chrono-node": "2.1.9",
|
||||||
|
"jquery": "3.5.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "4.3.0",
|
||||||
|
"@typescript-eslint/parser": "4.3.0",
|
||||||
|
"css-loader": "4.3.0",
|
||||||
|
"eslint": "7.10.0",
|
||||||
|
"eslint-config-standard": "14.1.1",
|
||||||
|
"eslint-loader": "4.0.2",
|
||||||
|
"eslint-plugin-import": "2.22.1",
|
||||||
|
"eslint-plugin-node": "11.1.0",
|
||||||
|
"eslint-plugin-promise": "4.2.1",
|
||||||
|
"eslint-plugin-standard": "4.0.1",
|
||||||
|
"less": "3.12.2",
|
||||||
|
"less-loader": "7.0.1",
|
||||||
|
"style-loader": "1.2.1",
|
||||||
|
"ts-loader": "8.0.4",
|
||||||
|
"typescript": "4.0.3",
|
||||||
|
"userscript-metadata-webpack-plugin": "0.0.6",
|
||||||
|
"webpack": "4.44.2",
|
||||||
|
"webpack-bundle-analyzer": "3.9.0",
|
||||||
|
"webpack-cli": "3.3.12",
|
||||||
|
"webpack-dev-server": "^3.11.0",
|
||||||
|
"webpack-livereload-plugin": "2.3.0",
|
||||||
|
"webpack-merge": "5.1.4"
|
||||||
|
}
|
||||||
|
}
|
53
readme.md
Normal file
53
readme.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# 3CX TAPI
|
||||||
|
|
||||||
|
Inject CPA TAPI functions into the 3CX Webclient
|
||||||
|
|
||||||
|
## dev
|
||||||
|
|
||||||
|
1. Allow Tampermonkey's access to local file URIs [tampermonkey/faq](https://tampermonkey.net/faq.php?ext=dhdg#Q204)
|
||||||
|
2. install deps with `npm i` or `npm ci`.
|
||||||
|
3. `npm run dev` to start your development.
|
||||||
|
4. open `webpack-userscript-template/dist/index.dev.user.js` in your Chrome and install it with your userscript manager.
|
||||||
|
|
||||||
|
this userscript's meta contains `// @require file://path/to/dist/index.prod.user.js`,
|
||||||
|
it will run the code in `index.prod.user.js`,
|
||||||
|
which take [src/js/index.js](./src/js/index.js) as entry point.
|
||||||
|
|
||||||
|
every times you edit your metadata, you'll have to install it again,
|
||||||
|
because Tampermonkey don't read it from dist every times.
|
||||||
|
|
||||||
|
5. edit [src/js/index.js](./src/js/index.js) with es6, you can even import css or less files. You can use scss if you like.
|
||||||
|
|
||||||
|
livereload is default enabled, use [this chrome extension](https://chrome.google.com/webstore/detail/jnihajbhpnppcggbcgedagnkighmdlei)
|
||||||
|
|
||||||
|
## dependencies
|
||||||
|
|
||||||
|
There are two ways to using a package on npm.
|
||||||
|
|
||||||
|
### UserScript way
|
||||||
|
|
||||||
|
Like original UserScript way, you will need to add them to your [user script metadata's require section](./config/metadata.js#L13-L17) , and exclude them in [config/webpack.config.base.js](./config/webpack.config.base.js#L21-L25)
|
||||||
|
|
||||||
|
### Webpack way
|
||||||
|
|
||||||
|
just install a package and import it in your js file. webpack will pack them with in your final production js file.
|
||||||
|
|
||||||
|
## build
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
`dist/index.prod.user.js` is the final script.
|
||||||
|
|
||||||
|
## distribution
|
||||||
|
|
||||||
|
```
|
||||||
|
cp dist/index.prod.user.js 3CX_TAPI.user.js
|
||||||
|
```
|
||||||
|
|
||||||
|
And commit 3CX_TAPI.user.js
|
||||||
|
|
||||||
|
## see also
|
||||||
|
|
||||||
|
Based on [webpack-userscript-template](https://github.com/Trim21/webpack-userscript-template/.
|
98
src/js/call-history.ts
Normal file
98
src/js/call-history.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
src/js/call-notification.ts
Normal file
31
src/js/call-notification.ts
Normal 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
13
src/js/debounce.js
Normal 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
3
src/js/decs.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
declare module 'axios-userscript-adapter'
|
||||||
|
|
||||||
|
declare const GM: any
|
19
src/js/index.js
Normal file
19
src/js/index.js
Normal 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
194
src/js/search.ts
Normal 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
7
src/js/tapi-contact.ts
Normal 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
33
src/js/utils.ts
Normal 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
|
||||||
|
}
|
10
tsconfig.json
Normal file
10
tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"module": "es6",
|
||||||
|
"target": "es6",
|
||||||
|
"allowJs": true,
|
||||||
|
"moduleResolution": "node"
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user