@@ -6,6 +6,7 @@ function Bubbles(container, self, options) {
6
6
typeSpeed = options . typeSpeed || 5 // delay per character, to simulate the machine "typing"
7
7
widerBy = options . widerBy || 2 // add a little extra width to bubbles to make sure they don't break
8
8
sidePadding = options . sidePadding || 6 // padding on both sides of chat bubbles
9
+ recallInteractions = options . recallInteractions || 0 // number of interactions to be remembered and brought back upon restart
9
10
inputCallbackFn = options . inputCallbackFn || false // should we display an input field?
10
11
11
12
var standingAnswer = "ice" // remember where to restart convo if interrupted
@@ -14,6 +15,52 @@ function Bubbles(container, self, options) {
14
15
//--> NOTE that this object is only assigned once, per session and does not change for this
15
16
// constructor name during open session.
16
17
18
+ // local storage for recalling conversations upon restart
19
+ var localStorageCheck = function ( ) {
20
+ var test = "chat-bubble-storage-test"
21
+ try {
22
+ localStorage . setItem ( test , test )
23
+ localStorage . removeItem ( test )
24
+ return true
25
+ } catch ( error ) {
26
+ console . error ( "Your server does not allow storing data locally. Most likely it's because you've opened this page from your hard-drive. For testing you can disable your browser's security or start a localhost environment." ) ;
27
+ return false
28
+ }
29
+ }
30
+ var localStorageAvailable = localStorageCheck ( ) && recallInteractions > 0
31
+ var interactionsLS = "chat-bubble-interactions"
32
+ var interactionsHistory = localStorageAvailable &&
33
+ JSON . parse ( localStorage . getItem ( interactionsLS ) ) || [ ]
34
+
35
+ // prepare next save point
36
+ interactionsSave = function ( say , reply ) {
37
+ if ( ! localStorageAvailable ) return
38
+ // limit number of saves
39
+ if ( interactionsHistory . length > recallInteractions )
40
+ interactionsHistory . shift ( ) // removes the oldest (first) save to make space
41
+
42
+ // do not memorize buttons; only user input gets memorized:
43
+ if (
44
+ // `bubble-button` class name signals that it's a button
45
+ say . includes ( "bubble-button" ) &&
46
+ // if it is not of a type of textual reply
47
+ reply !== "reply reply-freeform" &&
48
+ // if it is not of a type of textual reply or memorized user choice
49
+ reply !== "reply reply-pick"
50
+ )
51
+ // ...it shan't be memorized
52
+ return
53
+
54
+ // save to memory
55
+ interactionsHistory . push ( { say : say , reply : reply } )
56
+ }
57
+
58
+ // commit save to localStorage
59
+ interactionsSaveCommit = function ( ) {
60
+ if ( ! localStorageAvailable ) return
61
+ localStorage . setItem ( interactionsLS , JSON . stringify ( interactionsHistory ) )
62
+ }
63
+
17
64
// set up the stage
18
65
container . classList . add ( "bubble-container" )
19
66
var bubbleWrap = document . createElement ( "div" )
@@ -78,8 +125,11 @@ function Bubbles(container, self, options) {
78
125
this . reply ( _convo [ here ] )
79
126
here ? ( standingAnswer = here ) : false
80
127
}
128
+
129
+ var iceBreaker = false // this variable holds answer to whether this is the initative bot interaction or not
81
130
this . reply = function ( turn ) {
82
- turn = typeof turn !== "undefined" ? turn : _convo . ice
131
+ iceBreaker = typeof turn === "undefined"
132
+ turn = ! iceBreaker ? turn : _convo . ice
83
133
questionsHTML = ""
84
134
if ( turn . reply !== undefined ) {
85
135
turn . reply . reverse ( )
@@ -92,6 +142,8 @@ function Bubbles(container, self, options) {
92
142
self +
93
143
".answer('" +
94
144
el . answer +
145
+ "', '" +
146
+ el . question +
95
147
"');this.classList.add('bubble-pick')\">" +
96
148
el . question +
97
149
"</span>"
@@ -106,13 +158,21 @@ function Bubbles(container, self, options) {
106
158
} )
107
159
}
108
160
// navigate "answers"
109
- this . answer = function ( key ) {
161
+ this . answer = function ( key , content ) {
110
162
var func = function ( key ) {
111
163
typeof window [ key ] === "function" ? window [ key ] ( ) : false
112
164
}
113
165
_convo [ key ] !== undefined
114
166
? ( this . reply ( _convo [ key ] ) , ( standingAnswer = key ) )
115
167
: func ( key )
168
+
169
+ // add re-generated user picks to the history stack
170
+ if ( _convo [ key ] !== undefined && content !== undefined ) {
171
+ interactionsSave (
172
+ '<span class="bubble-button reply-pick">' + content + "</span>" ,
173
+ "reply reply-pick"
174
+ )
175
+ }
116
176
}
117
177
118
178
// api for typing bubble
@@ -147,12 +207,15 @@ function Bubbles(container, self, options) {
147
207
148
208
// create a bubble
149
209
var bubbleQueue = false
150
- var addBubble = function ( say , posted , reply ) {
210
+ var addBubble = function ( say , posted , reply , live ) {
211
+ live = typeof live !== "undefined" ? live : true // bubbles that are not "live" are not animated and displayed differently
212
+ var animationTime = live ? this . animationTime : 0
213
+ var typeSpeed = live ? typeSpeed : 0
151
214
reply = typeof reply !== "undefined" ? reply : ""
152
215
// create bubble element
153
216
var bubble = document . createElement ( "div" )
154
217
var bubbleContent = document . createElement ( "span" )
155
- bubble . className = "bubble imagine " + reply
218
+ bubble . className = "bubble imagine " + ( ! live ? " history " : "" ) + reply
156
219
bubbleContent . className = "bubble-content"
157
220
bubbleContent . innerHTML = say
158
221
bubble . appendChild ( bubbleContent )
@@ -179,8 +242,8 @@ function Bubbles(container, self, options) {
179
242
} )
180
243
}
181
244
// time, size & animate
182
- wait = animationTime * 2
183
- minTypingWait = animationTime * 6
245
+ wait = live ? animationTime * 2 : 0
246
+ minTypingWait = live ? animationTime * 6 : 0
184
247
if ( say . length * typeSpeed > animationTime && reply == "" ) {
185
248
wait += typeSpeed * say . length
186
249
wait < minTypingWait ? ( wait = minTypingWait ) : false
@@ -200,6 +263,11 @@ function Bubbles(container, self, options) {
200
263
: bubble . style . width
201
264
bubble . classList . add ( "say" )
202
265
posted ( )
266
+
267
+ // save the interaction
268
+ interactionsSave ( say , reply )
269
+ ! iceBreaker && interactionsSaveCommit ( ) // save point
270
+
203
271
// animate scrolling
204
272
containerHeight = container . offsetHeight
205
273
scrollDifference = bubbleWrap . scrollHeight - bubbleWrap . scrollTop
@@ -218,6 +286,16 @@ function Bubbles(container, self, options) {
218
286
setTimeout ( scrollBubbles , animationTime / 2 )
219
287
} , wait + animationTime * 2 )
220
288
}
289
+
290
+ // recall previous interactions
291
+ for ( var i = 0 ; i < interactionsHistory . length ; i ++ ) {
292
+ addBubble (
293
+ interactionsHistory [ i ] . say ,
294
+ function ( ) { } ,
295
+ interactionsHistory [ i ] . reply ,
296
+ false
297
+ )
298
+ }
221
299
}
222
300
223
301
// below functions are specifically for WebPack-type project that work with import()
@@ -227,7 +305,7 @@ function prepHTML(options) {
227
305
// options
228
306
var options = typeof options !== "undefined" ? options : { }
229
307
var container = options . container || "chat" // id of the container HTML element
230
- var relative_path = options . relative_path || "./node_modules/chat-bubble/"
308
+ var relative_path = options . relative_path || "./node_modules/chat-bubble/"
231
309
232
310
// make HTML container element
233
311
window [ container ] = document . createElement ( "div" )
@@ -237,20 +315,21 @@ function prepHTML(options) {
237
315
// style everything
238
316
var appendCSS = function ( file ) {
239
317
var link = document . createElement ( "link" )
240
- link . href = file ;
318
+ link . href = file
241
319
link . type = "text/css"
242
320
link . rel = "stylesheet"
243
321
link . media = "screen,print"
244
322
document . getElementsByTagName ( "head" ) [ 0 ] . appendChild ( link )
245
323
}
246
- appendCSS ( relative_path + "component/styles/input.css" ) ;
247
- appendCSS ( relative_path + "component/styles/reply.css" )
248
- appendCSS ( relative_path + "component/styles/says.css" )
249
- appendCSS ( relative_path + "component/styles/setup.css" )
250
- appendCSS ( relative_path + "component/styles/typing.css" )
251
-
324
+ appendCSS ( relative_path + "component/styles/input.css" )
325
+ appendCSS ( relative_path + "component/styles/reply.css" )
326
+ appendCSS ( relative_path + "component/styles/says.css" )
327
+ appendCSS ( relative_path + "component/styles/setup.css" )
328
+ appendCSS ( relative_path + "component/styles/typing.css" )
252
329
}
253
330
254
331
// exports for es6
255
- exports . Bubbles = Bubbles
256
- exports . prepHTML = prepHTML
332
+ if ( typeof exports !== "undefined" ) {
333
+ exports . Bubbles = Bubbles
334
+ exports . prepHTML = prepHTML
335
+ }
0 commit comments