tgo-widget-miniprogram 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -0
- package/miniprogram_dist/adapters/request.js +38 -0
- package/miniprogram_dist/adapters/storage.js +42 -0
- package/miniprogram_dist/adapters/systemInfo.js +22 -0
- package/miniprogram_dist/chat/index.js +164 -0
- package/miniprogram_dist/chat/index.json +7 -0
- package/miniprogram_dist/chat/index.wxml +48 -0
- package/miniprogram_dist/chat/index.wxss +92 -0
- package/miniprogram_dist/components/json-render-element/index.js +374 -0
- package/miniprogram_dist/components/json-render-element/index.json +6 -0
- package/miniprogram_dist/components/json-render-element/index.wxml +218 -0
- package/miniprogram_dist/components/json-render-element/index.wxss +450 -0
- package/miniprogram_dist/components/json-render-message/index.js +89 -0
- package/miniprogram_dist/components/json-render-message/index.json +7 -0
- package/miniprogram_dist/components/json-render-message/index.wxml +25 -0
- package/miniprogram_dist/components/json-render-message/index.wxss +26 -0
- package/miniprogram_dist/components/json-render-surface/index.js +116 -0
- package/miniprogram_dist/components/json-render-surface/index.json +6 -0
- package/miniprogram_dist/components/json-render-surface/index.wxml +10 -0
- package/miniprogram_dist/components/json-render-surface/index.wxss +6 -0
- package/miniprogram_dist/components/markdown-text/index.js +23 -0
- package/miniprogram_dist/components/markdown-text/index.json +3 -0
- package/miniprogram_dist/components/markdown-text/index.wxml +1 -0
- package/miniprogram_dist/components/markdown-text/index.wxss +6 -0
- package/miniprogram_dist/components/message-bubble/index.js +12 -0
- package/miniprogram_dist/components/message-bubble/index.json +3 -0
- package/miniprogram_dist/components/message-bubble/index.wxml +3 -0
- package/miniprogram_dist/components/message-bubble/index.wxss +17 -0
- package/miniprogram_dist/components/message-input/index.js +76 -0
- package/miniprogram_dist/components/message-input/index.json +3 -0
- package/miniprogram_dist/components/message-input/index.wxml +28 -0
- package/miniprogram_dist/components/message-input/index.wxss +56 -0
- package/miniprogram_dist/components/message-list/index.js +113 -0
- package/miniprogram_dist/components/message-list/index.json +9 -0
- package/miniprogram_dist/components/message-list/index.wxml +108 -0
- package/miniprogram_dist/components/message-list/index.wxss +113 -0
- package/miniprogram_dist/components/system-message/index.js +8 -0
- package/miniprogram_dist/components/system-message/index.json +3 -0
- package/miniprogram_dist/components/system-message/index.wxml +3 -0
- package/miniprogram_dist/components/system-message/index.wxss +15 -0
- package/miniprogram_dist/core/chatStore.js +758 -0
- package/miniprogram_dist/core/i18n.js +66 -0
- package/miniprogram_dist/core/platformStore.js +86 -0
- package/miniprogram_dist/core/types.js +192 -0
- package/miniprogram_dist/services/chat.js +67 -0
- package/miniprogram_dist/services/messageHistory.js +46 -0
- package/miniprogram_dist/services/platform.js +27 -0
- package/miniprogram_dist/services/upload.js +74 -0
- package/miniprogram_dist/services/visitor.js +67 -0
- package/miniprogram_dist/services/wukongim.js +183 -0
- package/miniprogram_dist/utils/jsonRender.js +158 -0
- package/miniprogram_dist/utils/markdown.js +31 -0
- package/miniprogram_dist/utils/time.js +85 -0
- package/miniprogram_dist/utils/uid.js +11 -0
- package/package.json +37 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WuKongIM EasyJSSDK wrapper for miniprogram
|
|
3
|
+
* easyjssdk v1.1.0+ natively supports WeChat MiniProgram WebSocket
|
|
4
|
+
*/
|
|
5
|
+
var WKIM = require('easyjssdk').WKIM
|
|
6
|
+
var WKIMEvent = require('easyjssdk').WKIMEvent
|
|
7
|
+
var adapter = require('../adapters/request')
|
|
8
|
+
|
|
9
|
+
function WuKongIMService() {
|
|
10
|
+
this._inited = false
|
|
11
|
+
this._im = null
|
|
12
|
+
this._cfg = null
|
|
13
|
+
this._uid = null
|
|
14
|
+
this._bound = false
|
|
15
|
+
this._hConnect = null
|
|
16
|
+
this._hDisconnect = null
|
|
17
|
+
this._hError = null
|
|
18
|
+
this._hMessage = null
|
|
19
|
+
this._hCustom = null
|
|
20
|
+
this._msgListeners = []
|
|
21
|
+
this._statusListeners = []
|
|
22
|
+
this._customListeners = []
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
WuKongIMService.prototype.isReady = function () {
|
|
26
|
+
return this._inited && !!this._im && !!this._cfg
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
WuKongIMService.prototype.getUid = function () {
|
|
30
|
+
return this._uid
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
WuKongIMService.prototype.init = function (opts) {
|
|
34
|
+
var self = this
|
|
35
|
+
this._cfg = Object.assign({}, opts, { channelType: opts.channelType || 'person' })
|
|
36
|
+
this._uid = opts.uid
|
|
37
|
+
|
|
38
|
+
return this._fetchRouteWsAddr(opts.apiBase, opts.uid, 10000).then(function (wsAddr) {
|
|
39
|
+
if (self._im && self._bound) {
|
|
40
|
+
try { self._unbindInternalEvents() } catch (e) {}
|
|
41
|
+
}
|
|
42
|
+
self._im = WKIM.init(wsAddr, { uid: opts.uid, token: opts.token || '' })
|
|
43
|
+
self._bindInternalEvents()
|
|
44
|
+
self._inited = true
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
WuKongIMService.prototype._bindInternalEvents = function () {
|
|
49
|
+
if (!this._im || this._bound) return
|
|
50
|
+
var self = this
|
|
51
|
+
|
|
52
|
+
this._hConnect = function (result) { self._emitStatus('connected', result) }
|
|
53
|
+
this._hDisconnect = function (info) { self._emitStatus('disconnected', info) }
|
|
54
|
+
this._hError = function (err) { self._emitStatus('error', err) }
|
|
55
|
+
this._hMessage = function (message) { self._emitMessage(message) }
|
|
56
|
+
this._hCustom = function (ev) { self._emitCustom(ev) }
|
|
57
|
+
|
|
58
|
+
this._im.on(WKIMEvent.Connect, this._hConnect)
|
|
59
|
+
this._im.on(WKIMEvent.Disconnect, this._hDisconnect)
|
|
60
|
+
this._im.on(WKIMEvent.Error, this._hError)
|
|
61
|
+
this._im.on(WKIMEvent.Message, this._hMessage)
|
|
62
|
+
this._im.on(WKIMEvent.CustomEvent, this._hCustom)
|
|
63
|
+
this._bound = true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
WuKongIMService.prototype._unbindInternalEvents = function () {
|
|
67
|
+
if (!this._im || !this._bound) return
|
|
68
|
+
if (this._hConnect) this._im.off(WKIMEvent.Connect, this._hConnect)
|
|
69
|
+
if (this._hDisconnect) this._im.off(WKIMEvent.Disconnect, this._hDisconnect)
|
|
70
|
+
if (this._hError) this._im.off(WKIMEvent.Error, this._hError)
|
|
71
|
+
if (this._hMessage) this._im.off(WKIMEvent.Message, this._hMessage)
|
|
72
|
+
if (this._hCustom) this._im.off(WKIMEvent.CustomEvent, this._hCustom)
|
|
73
|
+
this._hConnect = this._hDisconnect = this._hError = this._hMessage = this._hCustom = null
|
|
74
|
+
this._bound = false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
WuKongIMService.prototype._emitMessage = function (m) {
|
|
78
|
+
this._msgListeners.forEach(function (fn) { try { fn(m) } catch (e) {} })
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
WuKongIMService.prototype._emitStatus = function (s, info) {
|
|
82
|
+
this._statusListeners.forEach(function (fn) { try { fn(s, info) } catch (e) {} })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
WuKongIMService.prototype._emitCustom = function (e) {
|
|
86
|
+
this._customListeners.forEach(function (fn) { try { fn(e) } catch (e2) {} })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
WuKongIMService.prototype.onMessage = function (cb) {
|
|
90
|
+
this._msgListeners.push(cb)
|
|
91
|
+
var self = this
|
|
92
|
+
return function () {
|
|
93
|
+
var idx = self._msgListeners.indexOf(cb)
|
|
94
|
+
if (idx >= 0) self._msgListeners.splice(idx, 1)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
WuKongIMService.prototype.onStatus = function (cb) {
|
|
99
|
+
this._statusListeners.push(cb)
|
|
100
|
+
var self = this
|
|
101
|
+
return function () {
|
|
102
|
+
var idx = self._statusListeners.indexOf(cb)
|
|
103
|
+
if (idx >= 0) self._statusListeners.splice(idx, 1)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
WuKongIMService.prototype.onCustom = function (cb) {
|
|
108
|
+
this._customListeners.push(cb)
|
|
109
|
+
var self = this
|
|
110
|
+
return function () {
|
|
111
|
+
var idx = self._customListeners.indexOf(cb)
|
|
112
|
+
if (idx >= 0) self._customListeners.splice(idx, 1)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
WuKongIMService.prototype.connect = function () {
|
|
117
|
+
if (!this._im) throw new Error('WuKongIMService not initialized')
|
|
118
|
+
this._emitStatus('connecting')
|
|
119
|
+
return this._im.connect()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
WuKongIMService.prototype.disconnect = function () {
|
|
123
|
+
if (!this._im) return Promise.resolve()
|
|
124
|
+
if (typeof this._im.disconnect === 'function') {
|
|
125
|
+
try { return this._im.disconnect() } catch (e) { return Promise.resolve() }
|
|
126
|
+
}
|
|
127
|
+
return Promise.resolve()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
WuKongIMService.prototype._fetchRouteWsAddr = function (apiBase, uid, timeoutMs) {
|
|
131
|
+
var base = apiBase.endsWith('/') ? apiBase : apiBase + '/'
|
|
132
|
+
var url = base + 'v1/wukongim/route?uid=' + encodeURIComponent(uid)
|
|
133
|
+
|
|
134
|
+
return adapter.request({
|
|
135
|
+
url: url,
|
|
136
|
+
method: 'GET',
|
|
137
|
+
timeout: timeoutMs || 10000
|
|
138
|
+
}).then(function (res) {
|
|
139
|
+
if (!res.ok) throw new Error('route HTTP ' + res.status)
|
|
140
|
+
return res.json().then(function (data) {
|
|
141
|
+
// Priority 1: wss_addr
|
|
142
|
+
if (data && data.wss_addr && typeof data.wss_addr === 'string' && data.wss_addr.trim()) {
|
|
143
|
+
return data.wss_addr.trim()
|
|
144
|
+
}
|
|
145
|
+
// Priority 2: ws_addr fallbacks
|
|
146
|
+
var addr = data.ws_addr || data.ws || data.ws_url || data.wsAddr || data.websocket
|
|
147
|
+
if (!addr && data.wss) addr = data.wss
|
|
148
|
+
if (!addr || typeof addr !== 'string') throw new Error('missing ws address')
|
|
149
|
+
var wsAddr = String(addr)
|
|
150
|
+
if (/^http(s)?:/i.test(wsAddr)) wsAddr = wsAddr.replace(/^http/i, 'ws')
|
|
151
|
+
return wsAddr
|
|
152
|
+
})
|
|
153
|
+
}).catch(function (e) {
|
|
154
|
+
var msg = e && e.message ? String(e.message) : String(e)
|
|
155
|
+
throw new Error('[WuKongIM] route fetch failed: ' + msg)
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
WuKongIMService.prototype.sendText = function (text, opts) {
|
|
160
|
+
if (!this._im || !this._cfg) throw new Error('WuKongIMService not ready')
|
|
161
|
+
opts = opts || {}
|
|
162
|
+
var to = opts.to || this._cfg.target
|
|
163
|
+
var payload = { type: 1, content: text }
|
|
164
|
+
return this._im.send(to, opts.channelType || 251, payload, {
|
|
165
|
+
clientMsgNo: opts.clientMsgNo,
|
|
166
|
+
header: opts.header
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
WuKongIMService.prototype.sendPayload = function (payload, opts) {
|
|
171
|
+
if (!this._im || !this._cfg) throw new Error('WuKongIMService not ready')
|
|
172
|
+
opts = opts || {}
|
|
173
|
+
var to = opts.to || this._cfg.target
|
|
174
|
+
return this._im.send(to, opts.channelType || 251, payload, {
|
|
175
|
+
clientMsgNo: opts.clientMsgNo,
|
|
176
|
+
header: opts.header
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Singleton
|
|
181
|
+
var instance = new WuKongIMService()
|
|
182
|
+
|
|
183
|
+
module.exports = instance
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* json-render utilities for miniprogram
|
|
3
|
+
* Ported from @json-render/react (buildSpecFromParts, groupParts)
|
|
4
|
+
* and tgo-widget-app JSONRenderSurface (formatActionMessage, collectActionNames)
|
|
5
|
+
*/
|
|
6
|
+
var core = require('@json-render/core')
|
|
7
|
+
|
|
8
|
+
// Re-export core functions
|
|
9
|
+
var createMixedStreamParser = core.createMixedStreamParser
|
|
10
|
+
var createStateStore = core.createStateStore
|
|
11
|
+
var applySpecPatch = core.applySpecPatch
|
|
12
|
+
var nestedToFlat = core.nestedToFlat
|
|
13
|
+
|
|
14
|
+
var SPEC_DATA_PART_TYPE = 'data-' + (core.SPEC_DATA_PART || 'spec')
|
|
15
|
+
|
|
16
|
+
// ---- buildSpecFromParts ----
|
|
17
|
+
// Ported from @json-render/react buildSpecFromParts
|
|
18
|
+
function isSpecDataPart(data) {
|
|
19
|
+
if (typeof data !== 'object' || data === null) return false
|
|
20
|
+
switch (data.type) {
|
|
21
|
+
case 'patch':
|
|
22
|
+
return typeof data.patch === 'object' && data.patch !== null
|
|
23
|
+
case 'flat':
|
|
24
|
+
case 'nested':
|
|
25
|
+
return typeof data.spec === 'object' && data.spec !== null
|
|
26
|
+
default:
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildSpecFromParts(parts) {
|
|
32
|
+
var spec = { root: '', elements: {} }
|
|
33
|
+
var hasSpec = false
|
|
34
|
+
for (var i = 0; i < parts.length; i++) {
|
|
35
|
+
var part = parts[i]
|
|
36
|
+
if (part.type === SPEC_DATA_PART_TYPE) {
|
|
37
|
+
if (!isSpecDataPart(part.data)) continue
|
|
38
|
+
var payload = part.data
|
|
39
|
+
if (payload.type === 'patch') {
|
|
40
|
+
hasSpec = true
|
|
41
|
+
applySpecPatch(spec, payload.patch)
|
|
42
|
+
} else if (payload.type === 'flat') {
|
|
43
|
+
hasSpec = true
|
|
44
|
+
Object.assign(spec, payload.spec)
|
|
45
|
+
} else if (payload.type === 'nested') {
|
|
46
|
+
hasSpec = true
|
|
47
|
+
var flat = nestedToFlat(payload.spec)
|
|
48
|
+
Object.assign(spec, flat)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return hasSpec ? spec : null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---- groupParts ----
|
|
56
|
+
// Ported from JSONRenderMessage.tsx groupParts
|
|
57
|
+
function groupParts(parts) {
|
|
58
|
+
var groups = []
|
|
59
|
+
for (var i = 0; i < parts.length; i++) {
|
|
60
|
+
var part = parts[i]
|
|
61
|
+
if (part.type === 'text') {
|
|
62
|
+
var last = groups.length > 0 ? groups[groups.length - 1] : null
|
|
63
|
+
if (last && last.type === 'text') {
|
|
64
|
+
last.text += part.text || ''
|
|
65
|
+
} else {
|
|
66
|
+
groups.push({ type: 'text', text: part.text || '' })
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
var lastG = groups.length > 0 ? groups[groups.length - 1] : null
|
|
70
|
+
if (lastG && lastG.type === 'spec') {
|
|
71
|
+
lastG.parts.push(part)
|
|
72
|
+
} else {
|
|
73
|
+
groups.push({ type: 'spec', parts: [part] })
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return groups
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---- collectActionNames ----
|
|
81
|
+
// Ported from JSONRenderSurface.tsx
|
|
82
|
+
var BUILTIN_ACTIONS = { setState: 1, pushState: 1, removeState: 1, validateForm: 1 }
|
|
83
|
+
|
|
84
|
+
function collectActionNames(spec) {
|
|
85
|
+
if (!spec) return []
|
|
86
|
+
var names = {}
|
|
87
|
+
var elements = spec.elements || {}
|
|
88
|
+
var keys = Object.keys(elements)
|
|
89
|
+
for (var i = 0; i < keys.length; i++) {
|
|
90
|
+
var element = elements[keys[i]]
|
|
91
|
+
if (!element) continue
|
|
92
|
+
var events = element.on
|
|
93
|
+
if (!events || typeof events !== 'object') continue
|
|
94
|
+
var evKeys = Object.keys(events)
|
|
95
|
+
for (var j = 0; j < evKeys.length; j++) {
|
|
96
|
+
var binding = events[evKeys[j]]
|
|
97
|
+
if (Array.isArray(binding)) {
|
|
98
|
+
for (var k = 0; k < binding.length; k++) {
|
|
99
|
+
if (binding[k] && typeof binding[k].action === 'string') {
|
|
100
|
+
names[binding[k].action] = true
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
if (binding && typeof binding.action === 'string') {
|
|
106
|
+
names[binding.action] = true
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
var result = []
|
|
111
|
+
var nameKeys = Object.keys(names)
|
|
112
|
+
for (var n = 0; n < nameKeys.length; n++) {
|
|
113
|
+
if (!BUILTIN_ACTIONS[nameKeys[n]]) {
|
|
114
|
+
result.push(nameKeys[n])
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return result
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---- formatActionMessage ----
|
|
121
|
+
// Ported from JSONRenderSurface.tsx
|
|
122
|
+
function formatActionMessage(actionName, params, state) {
|
|
123
|
+
var parts = ['[' + actionName + ']']
|
|
124
|
+
if (params) {
|
|
125
|
+
var pKeys = Object.keys(params)
|
|
126
|
+
for (var i = 0; i < pKeys.length; i++) {
|
|
127
|
+
var v = params[pKeys[i]]
|
|
128
|
+
if (v != null && v !== '') parts.push(pKeys[i] + ': ' + v)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (state) {
|
|
132
|
+
var sKeys = Object.keys(state)
|
|
133
|
+
for (var j = 0; j < sKeys.length; j++) {
|
|
134
|
+
var sv = state[sKeys[j]]
|
|
135
|
+
if (typeof sv === 'object' && sv !== null) {
|
|
136
|
+
var fKeys = Object.keys(sv)
|
|
137
|
+
for (var k = 0; k < fKeys.length; k++) {
|
|
138
|
+
var fv = sv[fKeys[k]]
|
|
139
|
+
if (fv != null && fv !== '') parts.push(fKeys[k] + ': ' + fv)
|
|
140
|
+
}
|
|
141
|
+
} else if (sv != null && sv !== '') {
|
|
142
|
+
parts.push(sKeys[j] + ': ' + sv)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return parts.join('\n')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
createMixedStreamParser: createMixedStreamParser,
|
|
151
|
+
createStateStore: createStateStore,
|
|
152
|
+
applySpecPatch: applySpecPatch,
|
|
153
|
+
nestedToFlat: nestedToFlat,
|
|
154
|
+
buildSpecFromParts: buildSpecFromParts,
|
|
155
|
+
groupParts: groupParts,
|
|
156
|
+
collectActionNames: collectActionNames,
|
|
157
|
+
formatActionMessage: formatActionMessage
|
|
158
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown to HTML using marked
|
|
3
|
+
* Output is used with <rich-text> component
|
|
4
|
+
*/
|
|
5
|
+
var marked = require('marked')
|
|
6
|
+
|
|
7
|
+
// Configure marked for simple output
|
|
8
|
+
marked.setOptions({
|
|
9
|
+
breaks: true,
|
|
10
|
+
gfm: true
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parse markdown string to HTML (safe for rich-text)
|
|
15
|
+
* rich-text does its own sanitization so no DOMPurify needed
|
|
16
|
+
*/
|
|
17
|
+
function parseMarkdown(text) {
|
|
18
|
+
if (!text) return ''
|
|
19
|
+
try {
|
|
20
|
+
var html = marked.parse(text)
|
|
21
|
+
// Remove outer <p> wrapper for simple single-paragraph text
|
|
22
|
+
if (typeof html === 'string') {
|
|
23
|
+
return html.trim()
|
|
24
|
+
}
|
|
25
|
+
return String(html || '')
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return text
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { parseMarkdown: parseMarkdown }
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time formatting utilities (ported from widget-app)
|
|
3
|
+
*/
|
|
4
|
+
var i18n = require('../core/i18n')
|
|
5
|
+
|
|
6
|
+
function formatMessageTime(input) {
|
|
7
|
+
var msgDate = normalizeToDate(input)
|
|
8
|
+
if (!msgDate) return ''
|
|
9
|
+
|
|
10
|
+
var now = new Date()
|
|
11
|
+
var diff = now.getTime() - msgDate.getTime()
|
|
12
|
+
|
|
13
|
+
var lang = i18n.getLang()
|
|
14
|
+
|
|
15
|
+
// < 1 minute
|
|
16
|
+
if (diff < 60 * 1000) return lang === 'zh' ? '刚才' : 'Just now'
|
|
17
|
+
|
|
18
|
+
// < 1 hour
|
|
19
|
+
if (diff < 60 * 60 * 1000) {
|
|
20
|
+
var minutes = Math.floor(diff / (60 * 1000))
|
|
21
|
+
return lang === 'zh' ? minutes + '分钟前' : minutes + 'm ago'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
var startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0)
|
|
25
|
+
var startOfYesterday = new Date(startOfToday.getTime() - 24 * 60 * 60 * 1000)
|
|
26
|
+
|
|
27
|
+
// Today
|
|
28
|
+
if (msgDate >= startOfToday) {
|
|
29
|
+
return fmt(msgDate, 'HH:mm')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Yesterday
|
|
33
|
+
if (msgDate >= startOfYesterday) {
|
|
34
|
+
var prefix = lang === 'zh' ? '昨天' : 'Yesterday'
|
|
35
|
+
return prefix + ' ' + fmt(msgDate, 'HH:mm')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Within 7 days
|
|
39
|
+
var sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
|
|
40
|
+
if (msgDate > sevenDaysAgo) {
|
|
41
|
+
var weekday = lang === 'zh' ? weekdayCN(msgDate) : weekdayEN(msgDate)
|
|
42
|
+
return weekday + ' ' + fmt(msgDate, 'HH:mm')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Same year
|
|
46
|
+
if (msgDate.getFullYear() === now.getFullYear()) {
|
|
47
|
+
return fmt(msgDate, 'MM-DD HH:mm')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Cross year
|
|
51
|
+
return fmt(msgDate, 'YYYY-MM-DD HH:mm')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeToDate(input) {
|
|
55
|
+
if (input instanceof Date) return input
|
|
56
|
+
if (typeof input === 'number' && isFinite(input)) {
|
|
57
|
+
var ms = input < 1e12 ? input * 1000 : input
|
|
58
|
+
var d = new Date(ms)
|
|
59
|
+
return isNaN(d.getTime()) ? null : d
|
|
60
|
+
}
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function pad(n) { return n < 10 ? '0' + n : String(n) }
|
|
65
|
+
|
|
66
|
+
function fmt(d, pattern) {
|
|
67
|
+
var Y = d.getFullYear()
|
|
68
|
+
var M = pad(d.getMonth() + 1)
|
|
69
|
+
var D = pad(d.getDate())
|
|
70
|
+
var h = pad(d.getHours())
|
|
71
|
+
var m = pad(d.getMinutes())
|
|
72
|
+
if (pattern === 'HH:mm') return h + ':' + m
|
|
73
|
+
if (pattern === 'MM-DD HH:mm') return M + '-' + D + ' ' + h + ':' + m
|
|
74
|
+
return Y + '-' + M + '-' + D + ' ' + h + ':' + m
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function weekdayCN(d) {
|
|
78
|
+
return ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'][d.getDay()]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function weekdayEN(d) {
|
|
82
|
+
return ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][d.getDay()]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { formatMessageTime: formatMessageTime }
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate unique clientMsgNo
|
|
3
|
+
*/
|
|
4
|
+
function generateClientMsgNo(prefix) {
|
|
5
|
+
var p = prefix || 'cmn'
|
|
6
|
+
var ts = Date.now()
|
|
7
|
+
var rand = Math.random().toString(36).slice(2, 8)
|
|
8
|
+
return p + '-' + ts + '-' + rand
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = { generateClientMsgNo: generateClientMsgNo }
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tgo-widget-miniprogram",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "TGO visitor chat widget for WeChat Mini Program",
|
|
5
|
+
"miniprogram": "miniprogram_dist",
|
|
6
|
+
"main": "miniprogram_dist/chat/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"miniprogram_dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "node tools/build.js",
|
|
12
|
+
"ci:pack-npm": "node tools/build.js && cd example && node ci.js pack-npm",
|
|
13
|
+
"ci:compile": "node tools/build.js && cd example && node ci.js compile",
|
|
14
|
+
"ci:preview": "node tools/build.js && cd example && node ci.js preview",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"wechat",
|
|
19
|
+
"miniprogram",
|
|
20
|
+
"chat",
|
|
21
|
+
"widget",
|
|
22
|
+
"tgo"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@json-render/core": "^0.11.0",
|
|
27
|
+
"easyjssdk": "^2.0.0",
|
|
28
|
+
"marked": "^16.4.1",
|
|
29
|
+
"zod": "^4.3.6"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@babel/cli": "^7.28.6",
|
|
33
|
+
"@babel/core": "^7.29.0",
|
|
34
|
+
"@babel/preset-env": "^7.29.0",
|
|
35
|
+
"miniprogram-ci": "^2.1.26"
|
|
36
|
+
}
|
|
37
|
+
}
|