@seamly/web-ui 20.4.0 → 20.5.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/build/dist/lib/index.debug.js +35 -68
- package/build/dist/lib/index.debug.min.js +1 -1
- package/build/dist/lib/index.debug.min.js.LICENSE.txt +4 -16
- package/build/dist/lib/index.js +374 -3850
- package/build/dist/lib/index.min.js +1 -1
- package/build/dist/lib/index.min.js.LICENSE.txt +0 -5
- package/build/dist/lib/standalone.js +394 -3862
- package/build/dist/lib/standalone.min.js +1 -1
- package/build/dist/lib/style-guide.js +200 -113
- package/build/dist/lib/style-guide.min.js +1 -1
- package/package.json +2 -4
- package/src/javascripts/api/index.js +17 -1
- package/src/javascripts/domains/config/reducer.js +2 -0
- package/src/javascripts/domains/forms/provider.js +14 -6
- package/src/javascripts/domains/visibility/actions.js +2 -0
- package/src/javascripts/domains/visibility/hooks.js +60 -1
- package/src/javascripts/domains/visibility/reducer.js +5 -0
- package/src/javascripts/domains/visibility/selectors.js +5 -0
- package/src/javascripts/domains/visibility/utils.js +5 -1
- package/src/javascripts/style-guide/components/app.js +2 -0
- package/src/javascripts/style-guide/states.js +30 -50
- package/src/javascripts/ui/components/conversation/event/card-component.js +1 -2
- package/src/javascripts/ui/components/conversation/event/conversation-suggestions.js +19 -9
- package/src/javascripts/ui/components/conversation/event/cta.js +1 -2
- package/src/javascripts/ui/components/conversation/event/participant.js +2 -11
- package/src/javascripts/ui/components/conversation/event/splash.js +1 -3
- package/src/javascripts/ui/components/conversation/event/text.js +9 -9
- package/src/javascripts/ui/components/layout/chat.js +52 -48
- package/src/javascripts/ui/components/suggestions/suggestions-list.js +12 -14
- package/src/javascripts/ui/components/view/deprecated-view.js +16 -11
- package/src/javascripts/ui/components/view/inline-view.js +13 -8
- package/src/javascripts/ui/hooks/seamly-entry-hooks.js +3 -2
- package/src/javascripts/ui/hooks/seamly-state-hooks.js +4 -3
- package/src/javascripts/ui/hooks/use-seamly-chat.js +41 -29
- package/src/javascripts/ui/hooks/use-seamly-commands.js +16 -4
- package/src/javascripts/ui/utils/seamly-utils.js +16 -6
- package/src/javascripts/lib/parse-body.js +0 -10
- package/src/javascripts/ui/components/conversation/event/hooks/use-text-rendering.js +0 -35
|
@@ -6,19 +6,17 @@ const SuggestionsList = ({
|
|
|
6
6
|
suggestions = [],
|
|
7
7
|
onClickSuggestion,
|
|
8
8
|
hasIcon = true,
|
|
9
|
-
}) =>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
)
|
|
22
|
-
}
|
|
9
|
+
}) => (
|
|
10
|
+
<ul className={className('suggestions__list', givenClassName)}>
|
|
11
|
+
{suggestions.map((suggestion) => (
|
|
12
|
+
<SuggestionsItem
|
|
13
|
+
hasIcon={hasIcon}
|
|
14
|
+
key={suggestion.id}
|
|
15
|
+
onClick={onClickSuggestion}
|
|
16
|
+
{...suggestion}
|
|
17
|
+
/>
|
|
18
|
+
))}
|
|
19
|
+
</ul>
|
|
20
|
+
)
|
|
23
21
|
|
|
24
22
|
export default SuggestionsList
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useVisibility, useShowInlineView } from 'domains/visibility'
|
|
1
2
|
import DeprecatedAppFrame from '../layout/deprecated-app-frame'
|
|
2
3
|
import ChatFrame from '../layout/chat-frame'
|
|
3
4
|
import AgentInfo from '../layout/agent-info'
|
|
@@ -6,25 +7,29 @@ import Conversation from '../conversation/conversation'
|
|
|
6
7
|
import EntryContainer from '../entry/entry-container'
|
|
7
8
|
import Interrupt from '../layout/interrupt'
|
|
8
9
|
import { useSeamlyChat } from '../../hooks/seamly-hooks'
|
|
9
|
-
import { useVisibility } from '../../../domains/visibility'
|
|
10
10
|
import DeprecatedToggleButton from '../entry/deprecated-toggle-button'
|
|
11
11
|
|
|
12
12
|
const DeprecatedView = () => {
|
|
13
13
|
const { isVisible } = useVisibility()
|
|
14
14
|
const { openChat, closeChat } = useSeamlyChat()
|
|
15
|
+
const { showInlineView, containerRef } = useShowInlineView()
|
|
15
16
|
|
|
16
17
|
return (
|
|
17
18
|
isVisible && (
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
<div ref={containerRef}>
|
|
20
|
+
{showInlineView && (
|
|
21
|
+
<DeprecatedAppFrame>
|
|
22
|
+
<DeprecatedToggleButton onOpenChat={openChat} />
|
|
23
|
+
<Header onCloseChat={closeChat}>
|
|
24
|
+
<AgentInfo />
|
|
25
|
+
</Header>
|
|
26
|
+
<ChatFrame interruptComponent={Interrupt}>
|
|
27
|
+
<Conversation />
|
|
28
|
+
<EntryContainer />
|
|
29
|
+
</ChatFrame>
|
|
30
|
+
</DeprecatedAppFrame>
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
28
33
|
)
|
|
29
34
|
)
|
|
30
35
|
}
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import { className } from '
|
|
1
|
+
import { className } from 'lib/css'
|
|
2
|
+
import { useVisibility, useShowInlineView } from 'domains/visibility'
|
|
3
|
+
import { useInterrupt } from 'domains/interrupt'
|
|
2
4
|
import Chat from '../layout/chat'
|
|
3
5
|
import Interrupt from '../layout/interrupt'
|
|
4
6
|
import Conversation from '../conversation/conversation'
|
|
5
7
|
import EntryContainer from '../entry/entry-container'
|
|
6
8
|
import ChatFrame from '../layout/chat-frame'
|
|
7
9
|
import useSeamlyChat from '../../hooks/use-seamly-chat'
|
|
8
|
-
import { useVisibility } from '../../../domains/visibility'
|
|
9
10
|
import PreChatMessages from '../layout/pre-chat-messages'
|
|
10
11
|
import Suggestions from '../suggestions'
|
|
11
12
|
import InOutTransition, {
|
|
12
13
|
transitionStartStates,
|
|
13
14
|
} from '../widgets/in-out-transition'
|
|
14
|
-
import { useInterrupt } from '../../../domains/interrupt'
|
|
15
15
|
|
|
16
16
|
const InlineView = () => {
|
|
17
17
|
useSeamlyChat()
|
|
18
|
+
const { showInlineView, containerRef } = useShowInlineView()
|
|
19
|
+
|
|
18
20
|
const { isOpen } = useVisibility()
|
|
19
21
|
const { hasInterrupt, meta } = useInterrupt()
|
|
20
22
|
|
|
@@ -29,6 +31,7 @@ const InlineView = () => {
|
|
|
29
31
|
transitionStartState={transitionStartStates.rendered}
|
|
30
32
|
>
|
|
31
33
|
<div
|
|
34
|
+
ref={containerRef}
|
|
32
35
|
className={className(
|
|
33
36
|
'unstarted-wrapper',
|
|
34
37
|
'unstarted-wrapper--inline',
|
|
@@ -42,11 +45,13 @@ const InlineView = () => {
|
|
|
42
45
|
isActive={isOpen}
|
|
43
46
|
transitionStartState={transitionStartStates.rendered}
|
|
44
47
|
>
|
|
45
|
-
<Chat>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
<Chat ref={containerRef}>
|
|
49
|
+
{showInlineView && (
|
|
50
|
+
<ChatFrame interruptComponent={Interrupt}>
|
|
51
|
+
{isOpen && <Conversation />}
|
|
52
|
+
<EntryContainer />
|
|
53
|
+
</ChatFrame>
|
|
54
|
+
)}
|
|
50
55
|
</Chat>
|
|
51
56
|
</InOutTransition>
|
|
52
57
|
</>
|
|
@@ -80,12 +80,13 @@ export const useSeamlyEntry = () => {
|
|
|
80
80
|
active,
|
|
81
81
|
userSelected,
|
|
82
82
|
options: entryOptions,
|
|
83
|
+
optionsOverride: entryOptionsOverride,
|
|
83
84
|
} = useSeamlyStateContext().entryMeta
|
|
84
85
|
const dispatch = useSeamlyDispatchContext()
|
|
85
86
|
|
|
86
87
|
const activeEntry = userSelected || active || defaultEntry
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
const activeEntryOptions =
|
|
89
|
+
entryOptionsOverride[activeEntry] || entryOptions[activeEntry] || {}
|
|
89
90
|
|
|
90
91
|
const setBlockAutoEntrySwitch = useCallback(
|
|
91
92
|
(value) => {
|
|
@@ -103,14 +103,15 @@ export const useEntryTextLimit = () => {
|
|
|
103
103
|
const {
|
|
104
104
|
entryMeta: {
|
|
105
105
|
options: { text },
|
|
106
|
+
optionsOverride: { text: overrideText },
|
|
106
107
|
},
|
|
107
108
|
} = useSeamlyStateContext()
|
|
108
109
|
|
|
109
|
-
const { limit } = text || {}
|
|
110
|
+
const { limit } = overrideText || text || { limit: null }
|
|
110
111
|
|
|
111
112
|
return {
|
|
112
|
-
hasLimit: limit
|
|
113
|
-
limit: limit
|
|
113
|
+
hasLimit: limit !== null,
|
|
114
|
+
limit: limit !== null ? limit : null,
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
|
|
@@ -1,29 +1,31 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'preact/hooks'
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'preact/hooks'
|
|
2
2
|
import { useI18n } from 'domains/i18n'
|
|
3
3
|
import { seamlyActions } from 'ui/utils/seamly-utils'
|
|
4
4
|
import { useVisibility, visibilityStates } from 'domains/visibility'
|
|
5
5
|
import useSeamlyDispatchContext from './use-seamly-dispatch'
|
|
6
|
-
import { useEvents } from './seamly-state-hooks'
|
|
6
|
+
import { useEvents, useSeamlyLayoutMode } from './seamly-state-hooks'
|
|
7
7
|
import useSeamlyCommands from './use-seamly-commands'
|
|
8
8
|
import { useSeamlyHasConversation } from './seamly-api-hooks'
|
|
9
9
|
import { useLiveRegion } from './live-region-hooks'
|
|
10
|
-
import {
|
|
10
|
+
import { useSelector } from '../../domains/redux/hooks'
|
|
11
|
+
import { selectShowInlineView } from '../../domains/visibility/selectors'
|
|
11
12
|
|
|
12
13
|
const { SET_IS_LOADING } = seamlyActions
|
|
13
14
|
|
|
14
15
|
const useSeamlyChat = () => {
|
|
15
16
|
const { t } = useI18n()
|
|
16
|
-
const {
|
|
17
|
+
const { isInline, isWindow } = useSeamlyLayoutMode()
|
|
17
18
|
const { isOpen, isVisible, setVisibility } = useVisibility()
|
|
19
|
+
const showInlineView = useSelector(selectShowInlineView)
|
|
18
20
|
const dispatch = useSeamlyDispatchContext()
|
|
19
21
|
const events = useEvents()
|
|
20
22
|
const spinnerTimeout = useRef(null)
|
|
21
|
-
const { start, connect, apiConfigReady } = useSeamlyCommands()
|
|
23
|
+
const { start, connect, apiConfigReady, apiConnected } = useSeamlyCommands()
|
|
22
24
|
const hasConversation = useSeamlyHasConversation()
|
|
23
25
|
const prevIsOpen = useRef(null)
|
|
24
26
|
const prevIsVisible = useRef(null)
|
|
27
|
+
const startCalled = useRef(false)
|
|
25
28
|
const { sendAssertive } = useLiveRegion()
|
|
26
|
-
const connectCalled = useRef(false)
|
|
27
29
|
|
|
28
30
|
const hasEvents = events.length > 0
|
|
29
31
|
|
|
@@ -76,28 +78,41 @@ const useSeamlyChat = () => {
|
|
|
76
78
|
}, [hasEvents, dispatch])
|
|
77
79
|
|
|
78
80
|
useEffect(() => {
|
|
79
|
-
// This is needed to reset the ref to allow connect to happen again.
|
|
81
|
+
// This is needed to reset the ref to allow connect and start to happen again.
|
|
80
82
|
// Mostly due to Interrupt situations and a reset being called.
|
|
81
|
-
if (!
|
|
82
|
-
|
|
83
|
+
if (!apiConfigReady || !apiConnected) {
|
|
84
|
+
startCalled.current = false
|
|
83
85
|
}
|
|
84
|
-
}, [
|
|
86
|
+
}, [apiConfigReady, apiConnected])
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
// We don't connect
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
const connectAndStart = useCallback(async () => {
|
|
89
|
+
// We don't connect if we are already connected to the api to avoid multiple in-flight connection processes.
|
|
90
|
+
if (!apiConnected) {
|
|
91
|
+
await connect()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// We only start a conversation when the chat interface is either 'open' or if using the inline view if it's 'open' or 'minimized'.
|
|
95
|
+
if (isOpen || (isVisible && isInline)) {
|
|
96
|
+
start()
|
|
97
|
+
startCalled.current = true
|
|
98
|
+
}
|
|
99
|
+
}, [apiConnected, connect, isInline, isOpen, isVisible, start])
|
|
91
100
|
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
// We dont't connect or start when the apiConfig is not ready yet.
|
|
103
|
+
// We also keep track of whether start has been called to avoid multiple in-flight connection processes.
|
|
104
|
+
// We check if the window view is not open and no conversation is started yet.
|
|
105
|
+
// Lastly we check if the inline view is not scrolled in to view.
|
|
92
106
|
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
!
|
|
107
|
+
!apiConfigReady ||
|
|
108
|
+
startCalled.current ||
|
|
109
|
+
(isWindow && !isOpen && !hasConversation) ||
|
|
110
|
+
(isInline && !showInlineView)
|
|
96
111
|
) {
|
|
97
112
|
return
|
|
98
113
|
}
|
|
99
114
|
|
|
100
|
-
if (hasConversation) {
|
|
115
|
+
if (hasConversation && isOpen) {
|
|
101
116
|
// We deactivate the extra startup loading spinner when a conversation is available
|
|
102
117
|
// We also stop setting the loading indicator in the first place to avoid a flash.
|
|
103
118
|
clearTimeout(spinnerTimeout.current)
|
|
@@ -106,19 +121,16 @@ const useSeamlyChat = () => {
|
|
|
106
121
|
isLoading: false,
|
|
107
122
|
})
|
|
108
123
|
}
|
|
109
|
-
|
|
110
|
-
start()
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
connectCalled.current = true
|
|
124
|
+
connectAndStart()
|
|
114
125
|
}, [
|
|
115
|
-
isOpen,
|
|
116
|
-
hasConversation,
|
|
117
126
|
apiConfigReady,
|
|
118
|
-
|
|
119
|
-
connect,
|
|
127
|
+
connectAndStart,
|
|
120
128
|
dispatch,
|
|
121
|
-
|
|
129
|
+
hasConversation,
|
|
130
|
+
isInline,
|
|
131
|
+
isOpen,
|
|
132
|
+
isWindow,
|
|
133
|
+
showInlineView,
|
|
122
134
|
])
|
|
123
135
|
|
|
124
136
|
const openChat = () => {
|
|
@@ -7,7 +7,7 @@ import { Actions as InterruptActions } from 'domains/interrupt'
|
|
|
7
7
|
import { useConfig } from 'domains/config'
|
|
8
8
|
import * as AppActions from 'domains/app/actions'
|
|
9
9
|
import { useUserHasResponded } from 'domains/app/hooks'
|
|
10
|
-
import { useVisibility } from 'domains/visibility'
|
|
10
|
+
import { useVisibility, visibilityStates } from 'domains/visibility'
|
|
11
11
|
import { useStableCallback } from './utility-hooks'
|
|
12
12
|
import useSeamlyDispatchContext from './use-seamly-dispatch'
|
|
13
13
|
import { useSeamlyUnreadCount } from './seamly-state-hooks'
|
|
@@ -26,7 +26,7 @@ const useSeamlyCommands = () => {
|
|
|
26
26
|
|
|
27
27
|
const hasResponded = useUserHasResponded()
|
|
28
28
|
const hasConversation = useSeamlyHasConversation()
|
|
29
|
-
const { visible: visibility } = useVisibility()
|
|
29
|
+
const { visible: visibility, setVisibility } = useVisibility()
|
|
30
30
|
const unreadMessageCount = useSeamlyUnreadCount()
|
|
31
31
|
|
|
32
32
|
const emitEvent = useCallback(
|
|
@@ -44,6 +44,7 @@ const useSeamlyCommands = () => {
|
|
|
44
44
|
hasResponded,
|
|
45
45
|
unreadMessageCount,
|
|
46
46
|
})
|
|
47
|
+
|
|
47
48
|
api.send('start')
|
|
48
49
|
emitEvent('ui.start', {
|
|
49
50
|
visibility,
|
|
@@ -107,7 +108,13 @@ const useSeamlyCommands = () => {
|
|
|
107
108
|
emitEvent('message', message)
|
|
108
109
|
dispatch({
|
|
109
110
|
type: ADD_EVENT,
|
|
110
|
-
event: {
|
|
111
|
+
event: {
|
|
112
|
+
type: 'message',
|
|
113
|
+
payload: {
|
|
114
|
+
...message,
|
|
115
|
+
optimisticallyInjected: true,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
111
118
|
})
|
|
112
119
|
},
|
|
113
120
|
[api, dispatch, emitEvent, getTextMessageBase],
|
|
@@ -213,12 +220,16 @@ const useSeamlyCommands = () => {
|
|
|
213
220
|
.then((initialState) => {
|
|
214
221
|
if (initialState) {
|
|
215
222
|
dispatch({ type: SET_INITIAL_STATE, initialState })
|
|
223
|
+
if (initialState.userResponded) {
|
|
224
|
+
dispatch(AppActions.setHasResponded(initialState.userResponded))
|
|
225
|
+
setVisibility(visibilityStates.open)
|
|
226
|
+
}
|
|
216
227
|
}
|
|
217
228
|
})
|
|
218
229
|
.catch((error) => {
|
|
219
230
|
dispatch(InterruptActions.set(error))
|
|
220
231
|
})
|
|
221
|
-
}, [api, dispatch])
|
|
232
|
+
}, [api, dispatch, setVisibility])
|
|
222
233
|
|
|
223
234
|
return {
|
|
224
235
|
connect,
|
|
@@ -232,6 +243,7 @@ const useSeamlyCommands = () => {
|
|
|
232
243
|
addMessageBubble,
|
|
233
244
|
addUploadBubble,
|
|
234
245
|
addDivider,
|
|
246
|
+
apiConnected: api.connected,
|
|
235
247
|
apiConfigReady: api.configReady,
|
|
236
248
|
}
|
|
237
249
|
}
|
|
@@ -173,13 +173,18 @@ const orderHistory = (events) => {
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
export const mergeHistory = (stateEvents, historyEvents) => {
|
|
176
|
+
const newStateEvents = stateEvents.filter(
|
|
177
|
+
(stateEvent) =>
|
|
178
|
+
// Deduplicate the event streams, giving events in historyEvents
|
|
179
|
+
// precedence so the server is able to push changes to events.
|
|
180
|
+
!historyEvents.some(
|
|
181
|
+
(historyEvent) => historyEvent.payload.id === stateEvent.payload.id,
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
|
|
176
185
|
const newHistoryEvents = historyEvents
|
|
177
186
|
.filter(
|
|
178
187
|
(historyEvent) =>
|
|
179
|
-
// Deduplicate the event streams
|
|
180
|
-
!stateEvents.find(
|
|
181
|
-
(stateEvent) => stateEvent.payload.id === historyEvent.payload.id,
|
|
182
|
-
) &&
|
|
183
188
|
// Remove all non displayable participant messages
|
|
184
189
|
!(
|
|
185
190
|
historyEvent.type === 'participant' &&
|
|
@@ -192,7 +197,7 @@ export const mergeHistory = (stateEvents, historyEvents) => {
|
|
|
192
197
|
// the normal merging logic there is no added effect.
|
|
193
198
|
.reverse()
|
|
194
199
|
|
|
195
|
-
return orderHistory([...newHistoryEvents, ...
|
|
200
|
+
return orderHistory([...newHistoryEvents, ...newStateEvents])
|
|
196
201
|
}
|
|
197
202
|
|
|
198
203
|
const participantReducer = (state, action) => {
|
|
@@ -613,7 +618,12 @@ export const seamlyStateReducer = (state, action) => {
|
|
|
613
618
|
headerTitles: headerTitlesReducer(state.headerTitles, action),
|
|
614
619
|
}
|
|
615
620
|
case SET_INITIAL_STATE:
|
|
616
|
-
|
|
621
|
+
const { initialState } = action
|
|
622
|
+
return {
|
|
623
|
+
...state,
|
|
624
|
+
initialState,
|
|
625
|
+
unreadEvents: initialState.unreadMessageCount,
|
|
626
|
+
}
|
|
617
627
|
case SET_SERVICE_DATA_ITEM:
|
|
618
628
|
return {
|
|
619
629
|
...state,
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import Mustache from 'mustache'
|
|
2
|
-
|
|
3
|
-
Mustache.escape = function (escapeText) {
|
|
4
|
-
return escapeText
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
const parseLinkVariable = (variable) => {
|
|
8
|
-
return `<a href='${variable.url}' data-link-id='${variable.id}' ${
|
|
9
|
-
variable.newTab ? 'target="_blank"' : ''
|
|
10
|
-
}>${variable.name}</a>`
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function parseRichText(text, variables = {}) {
|
|
14
|
-
const view = {}
|
|
15
|
-
Object.entries(variables).forEach(([key, variable]) => {
|
|
16
|
-
switch (variable.type) {
|
|
17
|
-
case 'link':
|
|
18
|
-
view[key] = parseLinkVariable(variable)
|
|
19
|
-
break
|
|
20
|
-
case 'text':
|
|
21
|
-
view[key] = variable.value
|
|
22
|
-
break
|
|
23
|
-
}
|
|
24
|
-
}, {})
|
|
25
|
-
|
|
26
|
-
// Disable escaping as we'll be generating HTML
|
|
27
|
-
const oldEscape = Mustache.escape
|
|
28
|
-
Mustache.escape = function (escapeText) {
|
|
29
|
-
return escapeText
|
|
30
|
-
}
|
|
31
|
-
const output = Mustache.render(text, view)
|
|
32
|
-
Mustache.escape = oldEscape
|
|
33
|
-
|
|
34
|
-
return output
|
|
35
|
-
}
|