From 677c88e88ca759e3375e0cab98283f48a14a280e Mon Sep 17 00:00:00 2001 From: huangjx Date: Sat, 26 Feb 2022 11:47:23 +0800 Subject: [PATCH] added typescript files converted from js files --- package.json | 3 + src/App.vue | 6 +- src/actions/buttonActions.js | 2 +- src/lib/clipboard.ts | 64 ++++++++++++++ src/lib/contentTable.ts | 162 +++++++++++++++++++++++++++++++++++ src/lib/eventHandler.ts | 40 +++++++++ src/lib/http.ts | 79 +++++++++++++++++ src/lib/msg.ts | 77 +++++++++++++++++ src/lib/settingsForm.ts | 101 ++++++++++++++++++++++ src/lib/tooltip.ts | 50 +++++++++++ src/utils/helper.js | 8 +- tsconfig.json | 18 ++++ webpack.app.js | 5 ++ 13 files changed, 607 insertions(+), 8 deletions(-) create mode 100644 src/lib/clipboard.ts create mode 100644 src/lib/contentTable.ts create mode 100644 src/lib/eventHandler.ts create mode 100644 src/lib/http.ts create mode 100644 src/lib/msg.ts create mode 100644 src/lib/settingsForm.ts create mode 100644 src/lib/tooltip.ts create mode 100644 tsconfig.json diff --git a/package.json b/package.json index df6b9d7..440d86e 100755 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "stylelint:fix": "stylelint src --fix" }, "dependencies": { + "chalk": "^5.0.0", "toastr": "^2.1.4", "v-tooltip": "^4.0.0-alpha.1" }, @@ -59,6 +60,8 @@ "svgo-loader": "^3.0.0", "tippy.js": "^6.3.2", "toastify-js": "^1.11.1", + "ts-loader": "^9.2.6", + "typescript": "^4.5.5", "url-loader": "^4.1.1", "validator": "^13.6.0", "vue": "^3.2.20", diff --git a/src/App.vue b/src/App.vue index 95ac77f..1116b03 100644 --- a/src/App.vue +++ b/src/App.vue @@ -15,7 +15,7 @@ import toggleButton from "./components/toggleButton"; import helper from "./utils/helper"; import { translate as t, translatePlural as n } from "@nextcloud/l10n"; import Http from "./lib/http"; -import nctable from "./lib/ncTable"; +import contentTable from "./lib/contentTable"; const successCallback = (data, element) => { if (!data) { @@ -83,7 +83,7 @@ export default { return; } helper.enabledPolling = 0; - nctable.getInstance().loading(); + contentTable.getInstance().loading(); let url = formWrapper.getAttribute("action"); Http.getInstance(url) @@ -91,7 +91,7 @@ export default { .setHandler(function (data) { if (data && data.title) { vm.$data.loading = 0; - const tableInst = nctable.getInstance(data.title, data.row); + const tableInst = contentTable.getInstance(data.title, data.row); tableInst.actionLink = false; tableInst.rowClass = "table-row-search"; tableInst.create(); diff --git a/src/actions/buttonActions.js b/src/actions/buttonActions.js index d228b2b..fd9c50b 100644 --- a/src/actions/buttonActions.js +++ b/src/actions/buttonActions.js @@ -1,7 +1,7 @@ import Http from '../lib/http' import helper from '../utils/helper' import eventHandler from '../lib/eventHandler' -import Clipboard from '../utils/clipboard' +import Clipboard from '../lib/clipboard' import '../css/clipboard.scss'; const buttonHandler = (event, type) => { diff --git a/src/lib/clipboard.ts b/src/lib/clipboard.ts new file mode 100644 index 0000000..0c0cbaa --- /dev/null +++ b/src/lib/clipboard.ts @@ -0,0 +1,64 @@ +import Tooltip from "./tooltip"; + +class Clipboard { + text: string; + element: string | Element | HTMLElement; + + constructor(element:string | Element | HTMLElement, text:string) { + this.element = typeof element == 'object' ? element : document.querySelector(element); + this.text = text || this.element.getAttribute("data-text"); + } + + _copy(text:string) { + let textArea = document.createElement("textarea"); + textArea.value = text; + + textArea.style.top = "0"; + textArea.style.left = "0"; + textArea.style.position = "fixed"; + + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + let result; + try { + result = document.execCommand('copy'); + //console.log('copied using exceCommand'); + + } catch (err) { + console.error('failed to copy', err); + result = false; + } finally { + document.body.removeChild(textArea); + } + if (result) { + this.ShowMsg("Copied!"); + } + } + + ShowMsg(msg:string) { + let tip = new Tooltip(this.element, msg); + let html = tip.create('copy-alert').html(); + document.body.appendChild(html); + const callback = (element:Element) => { + element.remove() + } + setTimeout(() => { + callback(html) + }, 1000); + } + + Copy() { + if (!navigator.clipboard) { + return this._copy(this.text); + } + return navigator.clipboard.writeText(this.text).then(() => { + this.ShowMsg("Copied!"); + }, function (err) { + console.error('failed to copy text: ', err); + }); + } + +} + +export default Clipboard; \ No newline at end of file diff --git a/src/lib/contentTable.ts b/src/lib/contentTable.ts new file mode 100644 index 0000000..620c189 --- /dev/null +++ b/src/lib/contentTable.ts @@ -0,0 +1,162 @@ +import helper from '../utils/helper' +interface Map { + [key: string]: string | {} | Array +} +type rowData = Array + +class contentTable { + actionLink: boolean = true; + bodyClass: string = "ncdownloader-table-data"; + rowClass: string = "table-row"; + headingClass: string = "table-heading"; + cellClass: string = "table-cell"; + //this is the parent element the table is going to append to + tableContainer: string = 'ncdownloader-table-wrapper'; + numRow: number; + table: HTMLElement; + rows: rowData + heading: Array + actionButtons: Array<{}> + + constructor(heading: Array, rows: rowData) { + this.table = document.getElementById(this.tableContainer) as HTMLElement; + if (heading && rows) { + this.table.innerHTML = ''; + this.rows = rows; + this.heading = heading; + } + } + static getInstance(heading: Array, rows: rowData) { + return new contentTable(heading, rows); + } + create(): contentTable { + let thead = this.createHeading() + let tbody = this.createRow(); + this.table.appendChild(thead); + this.table.appendChild(tbody); + return this; + } + clear() { + this.table.innerHTML = ''; + } + loading() { + let htmlStr = '
Loading...
' + this.table.innerHTML = htmlStr; + return this; + } + noData() { + this.clear(); + let div = document.createElement('div'); + div.classList.add("no-items"); + div.appendChild(document.createTextNode(helper.t('No items'))); + this.table.appendChild(div); + } + createHeading(prefix = "table-heading") { + let thead = document.createElement("section"); + thead.classList.add(this.headingClass); + let headRow = document.createElement("header"); + headRow.classList.add(this.rowClass); + thead.classList.add(this.headingClass); + this.heading.forEach(name => { + let rowItem = document.createElement("div"); + rowItem.classList.add(prefix + "-" + name.toLowerCase()); + rowItem.classList.add(this.cellClass); + let text = document.createTextNode(helper.t(helper.ucfirst(name))); + rowItem.appendChild(text); + headRow.appendChild(rowItem); + }) + thead.appendChild(headRow); + return thead; + } + createRow() { + let tbody = document.createElement("section"); + tbody.classList.add(this.bodyClass); + tbody.classList.add("table-body"); + let row; + for (const element of this.rows) { + if (element === null) { + continue; + } + row = document.createElement("div"); + row.classList.add(this.rowClass); + let text; + for (let key in element) { + if (key.substring(0, 4) == 'data') { + let name = key.replace("_", "-"); + if (typeof element[key] == "string") { + row.setAttribute(name, (element[key])); + row.setAttribute("id", (element[key])); + } + continue; + } + let rowItem = document.createElement("div"); + rowItem.classList.add(this.cellClass); + if (key === 'actions' && Array.isArray(element[key])) { + let tmp = element[key] as Array; + rowItem.classList.add([this.cellClass, "action-item"].join("-")); + let container = document.createElement("div"); + container.classList.add("button-container"); + tmp.forEach(value => { + if (!value.name) { + return; + } + let data = value.data || ''; + container.appendChild(this.createActionButton(value.name, value.path, data)); + }) + rowItem.appendChild(container); + row.appendChild(rowItem); + } else if (Array.isArray(element[key])) { + let child = element[key] as any[]; + let div; + child.forEach(ele => { + div = document.createElement('div'); + if (helper.isHtml(ele)) { + div.innerHTML = ele; + } else { + text = document.createTextNode(ele); + div.appendChild(text); + } + rowItem.appendChild(div); + }) + rowItem.setAttribute("id", [this.cellClass, key].join("-")); + row.appendChild(rowItem); + continue; + } else if (typeof element[key] === "string") { + text = document.createTextNode(element[key] as string); + rowItem.appendChild(text); + rowItem.setAttribute("id", [this.cellClass, key].join("-")); + row.appendChild(rowItem); + } + } + tbody.appendChild(row); + } + return tbody; + + } + + createActionButton(name: string, path: string, data: string) { + let button = document.createElement("button"); + button.classList.add("icon-" + name); + button.setAttribute("path", path); + button.setAttribute("data", data); + if (name == 'refresh') { + name = helper.t('Redownload'); + } + button.setAttribute("data-tippy-content", helper.ucfirst(name)); + button.setAttribute("title", helper.ucfirst(name)); + return button; + } + + createActionCell(cell: HTMLElement) { + let div = document.createElement("div"); + let button = document.createElement("button"); + button.classList.add("icon-more", "action-button"); + button.setAttribute("id", "action-links-button"); + div.classList.add("action-item"); + div.appendChild(button); + //div.appendChild(actionLinks); + cell.appendChild(div); + } +} + +export default contentTable; \ No newline at end of file diff --git a/src/lib/eventHandler.ts b/src/lib/eventHandler.ts new file mode 100644 index 0000000..bebabcd --- /dev/null +++ b/src/lib/eventHandler.ts @@ -0,0 +1,40 @@ +type callback = (event: any) => void; +type target = string | Element | HTMLElement + +const eventHandler = { + add: function (eventType: string, target: target, selector: string | callback | Element, callback?: callback) { + if (typeof selector === 'function' && !callback) { + callback = selector; + selector = target; + } + if (typeof target === 'object') { + + target.addEventListener(eventType, function (e) { + callback.call(target, e); + }); + return; + } + let el = document.querySelector(target); + if (!el) { + return; + } + el.addEventListener(eventType, function (e) { + let element = e.target as HTMLElement; + if (element === this && selector === target) { + callback.call(element, e); + return; + } + for (; element && element != this; element = element.parentElement) { + if (typeof selector === "string" && element.matches(selector)) { + callback.call(element, e); + break; + } + } + }); + }, + remove: function (element: target, eventType: string, callback: callback) { + + (element).removeEventListener(eventType, callback); + } +} +export default eventHandler; diff --git a/src/lib/http.ts b/src/lib/http.ts new file mode 100644 index 0000000..c1e2e7a --- /dev/null +++ b/src/lib/http.ts @@ -0,0 +1,79 @@ +type httpData = { + [key: string]: any +} +type httpMethod = "POST" | "HEAD" | "GET"; +type handler = (data: any) => void; +const Http = class { + data: httpData; + url: string; + method: httpMethod; + dataType: string; + xhr: XMLHttpRequest; + handler: handler; + errorHandler: handler; + + constructor(url: string) { + this.url = url; + this.method = 'POST'; + this.data = null; + this.dataType = 'application/json'; + this.xhr = new XMLHttpRequest(); + } + static getInstance(url: string) { + return new Http(url); + } + setData(data: httpData) { + this.data = data + return this + } + setDataType(value: string) { + this.dataType = value; + } + send() { + let token = this.getToken(); + this.xhr.open(this.method, this.url); + this.xhr.setRequestHeader('requesttoken', token) + this.xhr.setRequestHeader('OCS-APIREQUEST', 'true') + if (this.dataType) + this.xhr.setRequestHeader('Content-Type', this.dataType); + let callback = this.handler; + this.xhr.onload = () => { + if (typeof callback === 'function') + callback(JSON.parse(this.xhr.response)); + } + this.xhr.onerror = this.errorHandler; + this.xhr.send(JSON.stringify(this.data)); + } + getToken() { + return document.getElementsByTagName('head')[0].getAttribute('data-requesttoken') + } + setUrl(url: string) { + this.url = url + return this + } + setMethod(method: httpMethod) { + this.method = method + return this + } + setHandler(handler: handler) { + this.handler = handler || function (data) { }; + return this; + } + setErrorHandler(handler: handler) { + this.errorHandler = handler + return this; + } + upload(file: File) { + const fd = new FormData(); + this.xhr.open(this.method, this.url, true); + let callback = this.handler; + this.xhr.onload = () => { + if (typeof callback === 'function') + callback(JSON.parse(this.xhr.response)); + } + fd.append('torrentfile', file); + return this.xhr.send(fd); + } +} + +export default Http \ No newline at end of file diff --git a/src/lib/msg.ts b/src/lib/msg.ts new file mode 100644 index 0000000..81e084d --- /dev/null +++ b/src/lib/msg.ts @@ -0,0 +1,77 @@ + +import { translate as t } from '@nextcloud/l10n' +type Response = { + data: { message: string }; + status: string; +} +export default { + startSaving(selector: string) { + this.startAction(selector, t('core', 'Saving …')) + }, + + startAction(selector: string, message: string) { + let el = document.querySelector(selector) as HTMLElement; + el.style.removeProperty("display") + el.textContent = message; + }, + + finishedSaving(selector: string, response: Response) { + this.finishedAction(selector, response) + }, + + finishedAction(selector: string, response: Response) { + if (response.status === 'success') { + this.finishedSuccess(selector, response.data.message) + } else { + this.finishedError(selector, response.data.message) + } + }, + + finishedSuccess(selector: string, message: string) { + let el = document.querySelector(selector); + el.textContent = message; + if (el.classList.contains("error")) el.classList.remove("error"); + el.classList.add("success"); + this.fadeOut(el); + }, + + finishedError(selector: string, message: string) { + let el = document.querySelector(selector); + el.textContent = message; + if (el.classList.contains("success")) el.classList.remove("success"); + el.classList.add("error"); + }, + fadeIn(element: HTMLElement, duration = 1000) { + (function increment() { + element.style.opacity = String(0); + element.style.removeProperty("display") + let opacity = parseFloat(element.style.opacity); + if (opacity !== 1) { + setTimeout(() => { + opacity += 0.1 + increment(); + }, duration / 10); + } + })(); + }, + + fadeOut(element: HTMLElement, duration = 1000) { + let opacity = parseFloat(element.style.opacity) || 1; + (function decrement() { + if ((opacity -= 0.1) < 0) { + element.style.display = 'none' + element.style.removeProperty('opacity'); + } else { + setTimeout(() => { + decrement(); + }, duration / 10); + } + })(); + }, + show(el: HTMLElement) { + el.style.display = ''; + }, + hide(el: HTMLElement) { + el.style.display = 'none'; + } +} \ No newline at end of file diff --git a/src/lib/settingsForm.ts b/src/lib/settingsForm.ts new file mode 100644 index 0000000..bf39a12 --- /dev/null +++ b/src/lib/settingsForm.ts @@ -0,0 +1,101 @@ +type dataItems = { + name: string; + value: any; + id: string; + type?: "text" | "button" | "radio" | "checkbox"; + placeholder?: string; +} +type data = Array + +class settingsForm { + parent = "custom-aria2-settings-container"; + constructor() { + + } + static getInstance() { + return new this(); + } + setParent(selector: string) { + this.parent = selector; + return this; + } + create(parent: HTMLElement, element: dataItems) { + let label = this._createLabel(element.name, element.id) + let input = this._createInput(element); + //let saveBtn = this._createSaveBtn(element.id); + let cancelBtn = this._createCancelBtn("has-content"); + let container = this._createContainer(element.id); + [label, input, cancelBtn].forEach(ele => { + container.appendChild(ele); + }) + + return parent.prepend(container); + } + + createCustomInput(keyId:string, valueId:string) { + let div = this._createContainer(keyId + "-container") + let items:dataItems = { + id:keyId, + name:'', + value:'' + } + div.appendChild(this._createInput(items)); + items.id = valueId + div.appendChild(this._createInput(items)); + div.appendChild(this._createCancelBtn()); + return div; + } + + _createContainer(id: string) { + let div = document.createElement("div"); + div.classList.add(id); + return div; + } + _createCancelBtn(className = '') { + let button = document.createElement("button"); + if (className) + button.classList.add(className); + //button.setAttribute("type",'button') + button.classList.add("icon-close"); + return button; + } + _createSaveBtn(id: string) { + let button = document.createElement("input"); + button.setAttribute('type', 'button'); + button.setAttribute('value', 'save'); + button.setAttribute("data-rel", id + "-container"); + return button; + } + _createLabel(name: string, id: string) { + name = name.replace('_', '-'); + let label = document.createElement("lable"); + label.setAttribute("for", id); + let text = document.createTextNode(name); + label.appendChild(text); + return label; + } + _createInput(data: dataItems) { + let input = document.createElement('input'); + let type = data.type || "text"; + let placeholder = data.placeholder || 'Leave empty if no value needed'; + let value = data.value || ''; + input.setAttribute('type', type); + input.setAttribute('id', data.id); + input.setAttribute("name", data.name || data.id); + if (type === 'text') { + input.setAttribute('value', value); + input.setAttribute('placeholder', placeholder); + } + input.classList.add('form-input-' + type); + return input; + } + + render(data: data) { + let parent = document.getElementById(this.parent) + for (const element of data) { + this.create(parent, element) + } + } +} + +export default settingsForm \ No newline at end of file diff --git a/src/lib/tooltip.ts b/src/lib/tooltip.ts new file mode 100644 index 0000000..27a948e --- /dev/null +++ b/src/lib/tooltip.ts @@ -0,0 +1,50 @@ +class Tooltip { + id = "ncdownloader-tooltip"; + messageNode: HTMLDivElement; + style = { + display: '', + position: '' + }; + text: string; + element: string | Element | HTMLElement; + + constructor(element: string | HTMLElement | Element, text: string) { + this.element = typeof element == 'object' ? element : document.querySelector(element); + this.style = { + position: 'fixed', + display: 'block', + } + this.text = text || this.element.getAttribute("data-text"); + } + create(id: string) { + this.messageNode = document.createElement("div"); + this.messageNode.classList.add(this.id); + this.messageNode.setAttribute("id", this.id); + this.messageNode.style.display = this.style.display; + this.messageNode.style.position = this.style.position; + this.messageNode.style.zIndex = "10000"; + let div = document.createElement('div'); + div.setAttribute("id", id); + let text = document.createTextNode(this.text); + div.appendChild(text); + this.messageNode.appendChild(div); + this.setPosition(); + return this; + } + render() { + document.body.appendChild(this.messageNode); + } + html() { + return this.messageNode; + } + setPosition(bottomMargin: number = 20, leftMargin: number = 0) { + let element = this.element as Element; + let rect = element.getBoundingClientRect(); + let top = (rect['top'] + bottomMargin) + "px"; + let left = (rect['left'] - leftMargin) + "px"; + this.messageNode.style.top = top; + this.messageNode.style.left = left + } +} + +export default Tooltip; \ No newline at end of file diff --git a/src/utils/helper.js b/src/utils/helper.js index 3b84f82..9463425 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -4,7 +4,7 @@ import { import Toastify from 'toastify-js' import "toastify-js/src/toastify.css" import { translate as t, translatePlural as n } from '@nextcloud/l10n' -import nctable from '../lib/ncTable'; +import contentTable from '../lib/contentTable'; import Http from '../lib/http' const helper = { @@ -120,9 +120,9 @@ const helper = { let url = helper.generateUrl(path); Http.getInstance(url).setHandler(function (data) { if (data && data.row) { - nctable.getInstance(data.title, data.row).create(); + contentTable.getInstance(data.title, data.row).create(); } else { - nctable.getInstance().noData(); + contentTable.getInstance().noData(); } if (data.counter) helper.updateCounter(data.counter); @@ -190,7 +190,7 @@ const helper = { }, showDownload() { helper.showElement('download'); - nctable.getInstance().clear(); + contentTable.getInstance().clear(); helper.enabledPolling = 0; }, hideDownload() { diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..522ff8f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "outDir": "./dist/", + "noImplicitAny": true, + "module": "es6", + "target": "es5", + "jsx": "react", + "allowJs": true, + "moduleResolution": "node" + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "**/*.spec.ts" + ] +} \ No newline at end of file diff --git a/webpack.app.js b/webpack.app.js index 01f2bbc..78b5547 100644 --- a/webpack.app.js +++ b/webpack.app.js @@ -49,6 +49,11 @@ module.exports = { use: 'vue-loader' }, /*{ test: /\.css$/, use: ['vue-style-loader', 'css-loader'] },*/ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, ] }, resolve: {