@constructor-io/constructorio-ui-pia 2.14.0 → 2.16.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 +71 -35
- package/dist/constructorio-ui-pia-bundled.js +45 -45
- package/lib/cjs/components/CioPia/CheckoutTriggerBar.js +25 -0
- package/lib/cjs/components/CioPia/CioPiaQna.js +16 -4
- package/lib/cjs/components/CioPia/CioPiaStyles.css +33 -0
- package/lib/cjs/components/ConversationHistory/ConversationHistory.js +7 -2
- package/lib/cjs/components/PiaConversation/PiaConversation.js +2 -1
- package/lib/cjs/components/PiaConversation/PiaConversationStyles.css +5 -0
- package/lib/cjs/hooks/useConversation.js +41 -6
- package/lib/cjs/stories/assets/storybook-styles.css +12 -10
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/CioPia/CheckoutTriggerBar.js +20 -0
- package/lib/mjs/components/CioPia/CioPiaQna.js +16 -4
- package/lib/mjs/components/CioPia/CioPiaStyles.css +33 -0
- package/lib/mjs/components/ConversationHistory/ConversationHistory.js +7 -3
- package/lib/mjs/components/PiaConversation/PiaConversation.js +2 -1
- package/lib/mjs/components/PiaConversation/PiaConversationStyles.css +5 -0
- package/lib/mjs/hooks/useConversation.js +39 -4
- package/lib/mjs/stories/assets/storybook-styles.css +12 -10
- package/lib/mjs/version.js +1 -1
- package/lib/types/components/CioPia/CheckoutTriggerBar.d.ts +11 -0
- package/lib/types/components/CioPia/index.d.ts +1 -1
- package/lib/types/components/CioPia/types.d.ts +52 -1
- package/lib/types/components/PiaConversation/PiaConversation.d.ts +3 -2
- package/lib/types/hooks/useConversation.d.ts +2 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/types.d.ts +5 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const react_1 = tslib_1.__importDefault(require("react"));
|
|
5
|
+
const translate_1 = require("../../utils/translate");
|
|
6
|
+
function CheckoutTriggerBar({ triggers, state, translations, }) {
|
|
7
|
+
const visible = triggers.filter((t) => (t.triggerWhen ? t.triggerWhen(state) : true));
|
|
8
|
+
if (visible.length === 0)
|
|
9
|
+
return null;
|
|
10
|
+
return (react_1.default.createElement("div", { className: 'cio-pia-checkout-triggers', "data-testid": 'cio-pia-checkout-triggers' }, visible.map((trigger) => {
|
|
11
|
+
var _a;
|
|
12
|
+
const label = (_a = trigger.label) !== null && _a !== void 0 ? _a : (0, translate_1.translate)('Checkout', translations);
|
|
13
|
+
const onClick = () => trigger.onTrigger(state);
|
|
14
|
+
if (trigger.renderButton) {
|
|
15
|
+
return (react_1.default.createElement(react_1.default.Fragment, { key: trigger.id }, trigger.renderButton({
|
|
16
|
+
onClick,
|
|
17
|
+
label,
|
|
18
|
+
disabled: trigger.disabled,
|
|
19
|
+
id: trigger.id,
|
|
20
|
+
})));
|
|
21
|
+
}
|
|
22
|
+
return (react_1.default.createElement("button", { key: trigger.id, type: 'button', className: 'cio-pia-checkout-trigger', "data-testid": `cio-pia-checkout-trigger-${trigger.id}`, disabled: trigger.disabled, onClick: onClick }, label));
|
|
23
|
+
})));
|
|
24
|
+
}
|
|
25
|
+
exports.default = CheckoutTriggerBar;
|
|
@@ -18,11 +18,12 @@ const PiaInlineAnswer_1 = tslib_1.__importDefault(require("../PiaInlineAnswer/Pi
|
|
|
18
18
|
const PiaModal_1 = tslib_1.__importDefault(require("../PiaConversation/PiaModal"));
|
|
19
19
|
const PiaConversation_1 = tslib_1.__importDefault(require("../PiaConversation/PiaConversation"));
|
|
20
20
|
const StatusRegion_1 = tslib_1.__importStar(require("../StatusRegion/StatusRegion"));
|
|
21
|
+
const CheckoutTriggerBar_1 = tslib_1.__importDefault(require("./CheckoutTriggerBar"));
|
|
21
22
|
/** The question-and-answer experience: `mode: 'default'`, `mode: 'conversation'`, and the modal. */
|
|
22
23
|
// eslint-disable-next-line complexity
|
|
23
24
|
function CioPiaQna(props) {
|
|
24
25
|
var _a;
|
|
25
|
-
const { apiKey, itemId, itemName, threadId, variationId, cioClient, displayConfigs, componentOverrides, callbacks, formatters, productCardProps, children, translations, suggestedQuestionsParameters, answerParameters, parameters, trackingConfigs, abTest, } = props;
|
|
26
|
+
const { apiKey, itemId, itemName, threadId, variationId, cioClient, displayConfigs, componentOverrides, callbacks, formatters, productCardProps, children, translations, suggestedQuestionsParameters, answerParameters, parameters, trackingConfigs, checkoutTriggers, abTest, initialConversationHistory, } = props;
|
|
26
27
|
const { learnMoreUrl, showFeedback, mode = 'default', type = 'inline', showPreviousItems, disclaimerPosition = 'bottom', } = displayConfigs || {};
|
|
27
28
|
const isConversation = mode === 'conversation' || type === 'modal';
|
|
28
29
|
const { priceCurrency } = productCardProps || {};
|
|
@@ -45,7 +46,14 @@ function CioPiaQna(props) {
|
|
|
45
46
|
variationId,
|
|
46
47
|
threadId: pia.threadId,
|
|
47
48
|
});
|
|
48
|
-
const { currentQuestion, displayedQuestions, conversationHistory, currentAnswer, currentItems, isLoading, error, context, handleSubmitQuestion, handleQuestionClick, handleInputFocus, handleFeedback, resetState, } = (0, useConversation_1.default)({
|
|
49
|
+
const { currentQuestion, displayedQuestions, conversationHistory, currentAnswer, currentItems, isLoading, error, context, handleSubmitQuestion, handleQuestionClick, handleInputFocus, handleFeedback, resetState, } = (0, useConversation_1.default)({
|
|
50
|
+
pia,
|
|
51
|
+
itemId,
|
|
52
|
+
isConversation,
|
|
53
|
+
callbacks,
|
|
54
|
+
tracking,
|
|
55
|
+
initialConversationHistory,
|
|
56
|
+
});
|
|
49
57
|
const { containerRef: viewportContainerRef } = (0, useViewportTracking_1.default)({
|
|
50
58
|
tracking,
|
|
51
59
|
questions: displayedQuestions,
|
|
@@ -73,12 +81,15 @@ function CioPiaQna(props) {
|
|
|
73
81
|
handleSubmitQuestion,
|
|
74
82
|
conversationHistory,
|
|
75
83
|
};
|
|
84
|
+
const checkoutElement = checkoutTriggers && checkoutTriggers.length > 0 ? (react_1.default.createElement(CheckoutTriggerBar_1.default, { triggers: checkoutTriggers, state: renderProps, translations: translations })) : null;
|
|
76
85
|
const conversationHistoryProps = {
|
|
77
86
|
conversationHistory,
|
|
78
87
|
isLoading,
|
|
79
88
|
isAnswerLoading,
|
|
80
89
|
error,
|
|
81
|
-
|
|
90
|
+
checkoutElement,
|
|
91
|
+
// Until a live answer arrives, a seeded last entry shows its own items. `null` would hide them.
|
|
92
|
+
currentItems: pia.answers.data ? currentItems : undefined,
|
|
82
93
|
showFeedback,
|
|
83
94
|
showPreviousItems,
|
|
84
95
|
learnMoreUrl,
|
|
@@ -111,7 +122,8 @@ function CioPiaQna(props) {
|
|
|
111
122
|
!isAnswerLoading && error && (react_1.default.createElement(ErrorBlock_1.default, { key: error.message || (0, translate_1.translate)('Unexpected error', translations), message: error.message, translations: translations })),
|
|
112
123
|
!isAnswerLoading && !error && currentAnswer && (react_1.default.createElement(PiaInlineAnswer_1.default, { currentAnswer: currentAnswer, currentItems: currentItems, showFeedback: showFeedback, learnMoreUrl: learnMoreUrl, disclaimerPosition: disclaimerPosition, translations: translations, callbacks: callbacks, componentOverrides: componentOverrides, onFeedback: handleFeedback, onResultClick: tracking.trackResultClick, question: currentQuestion, qnaResultId: qnaResultId, priceCurrency: priceCurrency })),
|
|
113
124
|
isLoading && react_1.default.createElement(SuggestedQuestionsSkeleton_1.default, null),
|
|
114
|
-
!isLoading && !error && (react_1.default.createElement(SuggestedQuestionsContainer_1.default, { questions: displayedQuestions, onQuestionClick: handleQuestionClick, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.suggestedQuestions }))
|
|
125
|
+
!isLoading && !error && (react_1.default.createElement(SuggestedQuestionsContainer_1.default, { questions: displayedQuestions, onQuestionClick: handleQuestionClick, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.suggestedQuestions })),
|
|
126
|
+
checkoutElement),
|
|
115
127
|
react_1.default.createElement(StatusRegion_1.default, { message: answerStatus, "data-testid": 'answer-status' })));
|
|
116
128
|
}
|
|
117
129
|
exports.default = CioPiaQna;
|
|
@@ -54,3 +54,36 @@
|
|
|
54
54
|
outline: 2px solid #0f1324;
|
|
55
55
|
outline-offset: 2px;
|
|
56
56
|
}
|
|
57
|
+
|
|
58
|
+
.cio-pia-checkout-triggers {
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-wrap: wrap;
|
|
61
|
+
gap: 8px;
|
|
62
|
+
margin-top: 4px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.cio-pia-checkout-trigger {
|
|
66
|
+
background: #0f1324;
|
|
67
|
+
color: #fff;
|
|
68
|
+
border: none;
|
|
69
|
+
border-radius: 6px;
|
|
70
|
+
padding: 10px 16px;
|
|
71
|
+
font-size: 14px;
|
|
72
|
+
font-weight: 500;
|
|
73
|
+
cursor: pointer;
|
|
74
|
+
transition: opacity 0.15s ease;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.cio-pia-checkout-trigger:hover {
|
|
78
|
+
opacity: 0.9;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.cio-pia-checkout-trigger:focus-visible {
|
|
82
|
+
outline: 2px solid #0f1324;
|
|
83
|
+
outline-offset: 2px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.cio-pia-checkout-trigger:disabled {
|
|
87
|
+
opacity: 0.5;
|
|
88
|
+
cursor: not-allowed;
|
|
89
|
+
}
|
|
@@ -22,11 +22,16 @@ function ConversationHistory({ conversationHistory, isLoading, isAnswerLoading =
|
|
|
22
22
|
return () => cancelAnimationFrame(frameId);
|
|
23
23
|
}, [conversationHistory, isLoading]);
|
|
24
24
|
const lastEntry = conversationHistory[conversationHistory.length - 1];
|
|
25
|
-
|
|
25
|
+
// An entry answered before mount (a restored conversation) was not just answered: announcing
|
|
26
|
+
// "Answer ready" on page load would tell the shopper something happened when nothing did.
|
|
27
|
+
const [answeredOnMountId] = (0, react_1.useState)(() => ((lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.answer) ? lastEntry.id : undefined));
|
|
28
|
+
const hasNewAnswer = !!(lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.answer) && lastEntry.id !== answeredOnMountId;
|
|
29
|
+
const answerStatus = (0, StatusRegion_1.answerStatusMessage)(isAnswerLoading, hasNewAnswer, translations);
|
|
26
30
|
const disclaimer = (react_1.default.createElement(Disclaimer_1.default, { learnMoreUrl: learnMoreUrl, translations: translations, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.disclaimer }));
|
|
27
31
|
return (react_1.default.createElement("div", { className: 'cio-pia-conversation-history' },
|
|
28
32
|
disclaimerPosition === 'top' && disclaimer,
|
|
29
33
|
react_1.default.createElement("div", { ref: scrollContainerRef, className: 'cio-pia-conversation-entries', role: 'log', tabIndex: 0, "aria-label": (0, translate_1.translate)('Conversation history', translations) }, conversationHistory.map((entry, index) => {
|
|
34
|
+
var _a;
|
|
30
35
|
const isLast = index === conversationHistory.length - 1;
|
|
31
36
|
const previousEntryItems = showPreviousItems ? entry.items : null;
|
|
32
37
|
const latestEntryItems = currentItems !== undefined ? currentItems : entry.items;
|
|
@@ -38,7 +43,7 @@ function ConversationHistory({ conversationHistory, isLoading, isAnswerLoading =
|
|
|
38
43
|
isLast && !isLoading && error && (react_1.default.createElement(ErrorBlock_1.default, { key: error.message || (0, translate_1.translate)('Unexpected error', translations), message: error.message, translations: translations, announce: false })),
|
|
39
44
|
entry.answer && (react_1.default.createElement("div", { className: 'cio-pia-answer-container' },
|
|
40
45
|
react_1.default.createElement(Answer_1.default, { text: entry.answer, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.answer }),
|
|
41
|
-
carouselItems && (react_1.default.createElement(PiaCustomCarousel_1.default, { items: carouselItems, componentOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.carousel, callbacks: callbacks, onResultClick: onResultClick, question: entry.question, qnaResultId: qnaResultId, translations: translations, priceCurrency: priceCurrency })),
|
|
46
|
+
carouselItems && (react_1.default.createElement(PiaCustomCarousel_1.default, { items: carouselItems, componentOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.carousel, callbacks: callbacks, onResultClick: onResultClick, question: entry.question, qnaResultId: (_a = entry.qnaResultId) !== null && _a !== void 0 ? _a : (isLast ? qnaResultId : undefined), translations: translations, priceCurrency: priceCurrency })),
|
|
42
47
|
isLast && showFeedback && (react_1.default.createElement(Feedback_1.default, { translations: translations, onFeedback: handleFeedback, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.feedback }))))));
|
|
43
48
|
})),
|
|
44
49
|
disclaimerPosition === 'bottom' && disclaimer,
|
|
@@ -7,11 +7,12 @@ const SuggestedQuestionsContainer_1 = tslib_1.__importDefault(require("../Sugges
|
|
|
7
7
|
const SuggestedQuestionsSkeleton_1 = tslib_1.__importDefault(require("../SuggestedQuestionsContainer/SuggestedQuestionsSkeleton"));
|
|
8
8
|
const translate_1 = require("../../utils/translate");
|
|
9
9
|
const ConversationHistory_1 = tslib_1.__importDefault(require("../ConversationHistory/ConversationHistory"));
|
|
10
|
-
function PiaConversation({ conversationHistory, isLoading, error, currentItems, showFeedback, showPreviousItems, learnMoreUrl, disclaimerPosition, translations, callbacks, componentOverrides, displayedQuestions, handleSubmitQuestion, handleQuestionClick, containerRef, handleFeedback, onResultClick, qnaResultId, onInputFocus, priceCurrency, }) {
|
|
10
|
+
function PiaConversation({ conversationHistory, isLoading, error, currentItems, showFeedback, showPreviousItems, learnMoreUrl, disclaimerPosition, translations, callbacks, componentOverrides, displayedQuestions, handleSubmitQuestion, handleQuestionClick, containerRef, handleFeedback, onResultClick, qnaResultId, onInputFocus, priceCurrency, checkoutElement, }) {
|
|
11
11
|
const hasHistory = conversationHistory.length > 0;
|
|
12
12
|
return (react_1.default.createElement("div", { ref: containerRef, className: 'cio-pia-container cio-pia-conversation', "data-testid": 'cio-pia-container' },
|
|
13
13
|
!hasHistory && (react_1.default.createElement("p", { className: 'cio-pia-title', "data-testid": 'cio-pia-title' }, (0, translate_1.translate)('Any questions about this product?', translations))),
|
|
14
14
|
react_1.default.createElement(ConversationHistory_1.default, { conversationHistory: conversationHistory, isLoading: isLoading, error: error, currentItems: currentItems, showFeedback: showFeedback, showPreviousItems: showPreviousItems, learnMoreUrl: learnMoreUrl, disclaimerPosition: disclaimerPosition, translations: translations, callbacks: callbacks, componentOverrides: componentOverrides, handleFeedback: handleFeedback, onResultClick: onResultClick, qnaResultId: qnaResultId, priceCurrency: priceCurrency }),
|
|
15
|
+
checkoutElement && react_1.default.createElement("div", { className: 'cio-pia-conversation-checkout' }, checkoutElement),
|
|
15
16
|
react_1.default.createElement("div", { className: 'cio-pia-conversation-footer' },
|
|
16
17
|
isLoading && !error && react_1.default.createElement(SuggestedQuestionsSkeleton_1.default, null),
|
|
17
18
|
!isLoading && !error && (react_1.default.createElement(SuggestedQuestionsContainer_1.default, { questions: displayedQuestions, onQuestionClick: handleQuestionClick, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.suggestedQuestions })),
|
|
@@ -1,15 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const react_1 = require("react");
|
|
4
|
-
function
|
|
4
|
+
function isRestorableEntry(entry) {
|
|
5
|
+
if (typeof entry !== 'object' || entry === null)
|
|
6
|
+
return false;
|
|
7
|
+
const { question, answer, items } = entry;
|
|
8
|
+
return (typeof question === 'string' &&
|
|
9
|
+
typeof answer === 'string' &&
|
|
10
|
+
(items === undefined || items === null || Array.isArray(items)));
|
|
11
|
+
}
|
|
12
|
+
// The seed comes from the host's own storage, typed or not: a malformed one must not take the widget down.
|
|
13
|
+
function restoreHistory(seed) {
|
|
14
|
+
if (!Array.isArray(seed)) {
|
|
15
|
+
console.warn('[CioPia] initialConversationHistory must be an array. It was ignored.');
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const entries = seed.filter(isRestorableEntry);
|
|
19
|
+
if (entries.length < seed.length) {
|
|
20
|
+
console.warn(`[CioPia] ${seed.length - entries.length} initialConversationHistory entries were ignored. Each needs a string question and answer, and items must be an array.`);
|
|
21
|
+
}
|
|
22
|
+
// Seeded ids are replaced: `id` is the React key, so a caller's values cannot be trusted to be unique.
|
|
23
|
+
return entries.map((entry, index) => (Object.assign(Object.assign({}, entry), { id: index + 1 })));
|
|
24
|
+
}
|
|
25
|
+
function useConversation({ pia, itemId, isConversation, callbacks, tracking, initialConversationHistory, }) {
|
|
5
26
|
var _a, _b, _c;
|
|
6
27
|
const { suggestedQuestions, answers, threadId } = pia;
|
|
7
28
|
const { getAnswer } = answers;
|
|
8
29
|
const context = (0, react_1.useMemo)(() => ({ itemId, threadId }), [itemId, threadId]);
|
|
9
30
|
const [currentQuestion, setCurrentQuestion] = (0, react_1.useState)('');
|
|
10
31
|
const [displayedQuestions, setDisplayedQuestions] = (0, react_1.useState)([]);
|
|
11
|
-
const [conversationHistory, setConversationHistory] = (0, react_1.useState)(
|
|
12
|
-
|
|
32
|
+
const [conversationHistory, setConversationHistory] = (0, react_1.useState)(() => isConversation && initialConversationHistory !== undefined
|
|
33
|
+
? restoreHistory(initialConversationHistory)
|
|
34
|
+
: []);
|
|
35
|
+
const entryIdRef = (0, react_1.useRef)(conversationHistory.length);
|
|
36
|
+
const conversationHistoryRef = (0, react_1.useRef)(conversationHistory);
|
|
37
|
+
const prevItemIdRef = (0, react_1.useRef)(itemId);
|
|
13
38
|
const prevAnswerDataRef = (0, react_1.useRef)(answers.data);
|
|
14
39
|
const hasTrackedCurrentAnswerRef = (0, react_1.useRef)(false);
|
|
15
40
|
const answersRef = (0, react_1.useRef)(answers);
|
|
@@ -33,6 +58,9 @@ function useConversation({ pia, itemId, isConversation, callbacks, tracking, })
|
|
|
33
58
|
(0, react_1.useEffect)(() => {
|
|
34
59
|
answersRef.current = answers;
|
|
35
60
|
}, [answers]);
|
|
61
|
+
(0, react_1.useEffect)(() => {
|
|
62
|
+
conversationHistoryRef.current = conversationHistory;
|
|
63
|
+
}, [conversationHistory]);
|
|
36
64
|
const submitQuestion = (0, react_1.useCallback)((question, source) => {
|
|
37
65
|
lastSourceRef.current = source;
|
|
38
66
|
lastQuestionRef.current = question;
|
|
@@ -63,9 +91,12 @@ function useConversation({ pia, itemId, isConversation, callbacks, tracking, })
|
|
|
63
91
|
(_c = (_b = callbacksRef.current) === null || _b === void 0 ? void 0 : _b.onFocus) === null || _c === void 0 ? void 0 : _c.call(_b, contextRef.current);
|
|
64
92
|
}, []);
|
|
65
93
|
const handleFeedback = (0, react_1.useCallback)((type) => {
|
|
66
|
-
var _a, _b, _c, _d;
|
|
67
|
-
|
|
68
|
-
|
|
94
|
+
var _a, _b, _c, _d, _e, _f;
|
|
95
|
+
// A seeded last entry has no live response behind it; its own id is the answer rated.
|
|
96
|
+
const history = conversationHistoryRef.current;
|
|
97
|
+
const qnaResultId = (_b = (_a = answersRef.current.data) === null || _a === void 0 ? void 0 : _a.qna_result_id) !== null && _b !== void 0 ? _b : (_c = history[history.length - 1]) === null || _c === void 0 ? void 0 : _c.qnaResultId;
|
|
98
|
+
(_d = trackingRef.current) === null || _d === void 0 ? void 0 : _d.trackAnswerFeedback(type, qnaResultId);
|
|
99
|
+
(_f = (_e = callbacksRef.current) === null || _e === void 0 ? void 0 : _e.onFeedback) === null || _f === void 0 ? void 0 : _f.call(_e, type);
|
|
69
100
|
}, []);
|
|
70
101
|
const resetState = (0, react_1.useCallback)(() => {
|
|
71
102
|
setCurrentQuestion('');
|
|
@@ -75,6 +106,10 @@ function useConversation({ pia, itemId, isConversation, callbacks, tracking, })
|
|
|
75
106
|
showsFollowUpsRef.current = false;
|
|
76
107
|
}, [suggestedQuestions.data]);
|
|
77
108
|
(0, react_1.useEffect)(() => {
|
|
109
|
+
// Skip the mount run: it would wipe the seeded history.
|
|
110
|
+
if (prevItemIdRef.current === itemId)
|
|
111
|
+
return;
|
|
112
|
+
prevItemIdRef.current = itemId;
|
|
78
113
|
setCurrentQuestion('');
|
|
79
114
|
setDisplayedQuestions([]);
|
|
80
115
|
setConversationHistory([]);
|
|
@@ -25,8 +25,8 @@ div.link-list {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
@media all and (-ms-high-contrast:none) {
|
|
29
|
-
div.link-list {
|
|
28
|
+
@media all and (-ms-high-contrast: none) {
|
|
29
|
+
div.link-list {
|
|
30
30
|
display: -ms-grid;
|
|
31
31
|
-ms-grid-columns: 1fr 1fr;
|
|
32
32
|
-ms-grid-rows: 1fr 1fr;
|
|
@@ -38,20 +38,23 @@ a.link-item {
|
|
|
38
38
|
padding: 20px;
|
|
39
39
|
border: 1px solid #00000010;
|
|
40
40
|
border-radius: 5px;
|
|
41
|
-
transition:
|
|
41
|
+
transition:
|
|
42
|
+
background 150ms ease-out,
|
|
43
|
+
border 150ms ease-out,
|
|
44
|
+
transform 150ms ease-out;
|
|
42
45
|
color: #333333;
|
|
43
46
|
display: flex;
|
|
44
47
|
align-items: flex-start;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
a.link-item:hover {
|
|
48
|
-
border-color: #
|
|
51
|
+
border-color: #1ea7fd50;
|
|
49
52
|
transform: translate3d(0, -3px, 0);
|
|
50
53
|
box-shadow: rgba(0, 0, 0, 0.08) 0 3px 10px 0;
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
a.link-item:active {
|
|
54
|
-
border-color: #
|
|
57
|
+
border-color: #1ea7fd;
|
|
55
58
|
transform: translate3d(0, 0, 0);
|
|
56
59
|
}
|
|
57
60
|
|
|
@@ -81,8 +84,8 @@ span.badge {
|
|
|
81
84
|
font-size: 11px;
|
|
82
85
|
line-height: 12px;
|
|
83
86
|
font-weight: 700;
|
|
84
|
-
background: #
|
|
85
|
-
color: #
|
|
87
|
+
background: #e7fdd8;
|
|
88
|
+
color: #66bf3c;
|
|
86
89
|
padding: 4px 12px;
|
|
87
90
|
margin-right: 10px;
|
|
88
91
|
vertical-align: top;
|
|
@@ -100,10 +103,9 @@ div.badge-wrapper code {
|
|
|
100
103
|
display: inline-block;
|
|
101
104
|
}
|
|
102
105
|
|
|
103
|
-
|
|
104
106
|
.badge.tip {
|
|
105
|
-
background: #
|
|
106
|
-
color: #
|
|
107
|
+
background: #e7fdd8;
|
|
108
|
+
color: #66bf3c;
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
.badge.note {
|
package/lib/cjs/version.js
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { translate } from '../../utils/translate';
|
|
3
|
+
export default function CheckoutTriggerBar({ triggers, state, translations, }) {
|
|
4
|
+
const visible = triggers.filter((t) => (t.triggerWhen ? t.triggerWhen(state) : true));
|
|
5
|
+
if (visible.length === 0)
|
|
6
|
+
return null;
|
|
7
|
+
return (React.createElement("div", { className: 'cio-pia-checkout-triggers', "data-testid": 'cio-pia-checkout-triggers' }, visible.map((trigger) => {
|
|
8
|
+
const label = trigger.label ?? translate('Checkout', translations);
|
|
9
|
+
const onClick = () => trigger.onTrigger(state);
|
|
10
|
+
if (trigger.renderButton) {
|
|
11
|
+
return (React.createElement(React.Fragment, { key: trigger.id }, trigger.renderButton({
|
|
12
|
+
onClick,
|
|
13
|
+
label,
|
|
14
|
+
disabled: trigger.disabled,
|
|
15
|
+
id: trigger.id,
|
|
16
|
+
})));
|
|
17
|
+
}
|
|
18
|
+
return (React.createElement("button", { key: trigger.id, type: 'button', className: 'cio-pia-checkout-trigger', "data-testid": `cio-pia-checkout-trigger-${trigger.id}`, disabled: trigger.disabled, onClick: onClick }, label));
|
|
19
|
+
})));
|
|
20
|
+
}
|
|
@@ -15,10 +15,11 @@ import PiaInlineAnswer from '../PiaInlineAnswer/PiaInlineAnswer';
|
|
|
15
15
|
import PiaModal from '../PiaConversation/PiaModal';
|
|
16
16
|
import PiaConversation from '../PiaConversation/PiaConversation';
|
|
17
17
|
import StatusRegion, { answerStatusMessage } from '../StatusRegion/StatusRegion';
|
|
18
|
+
import CheckoutTriggerBar from './CheckoutTriggerBar';
|
|
18
19
|
/** The question-and-answer experience: `mode: 'default'`, `mode: 'conversation'`, and the modal. */
|
|
19
20
|
// eslint-disable-next-line complexity
|
|
20
21
|
export default function CioPiaQna(props) {
|
|
21
|
-
const { apiKey, itemId, itemName, threadId, variationId, cioClient, displayConfigs, componentOverrides, callbacks, formatters, productCardProps, children, translations, suggestedQuestionsParameters, answerParameters, parameters, trackingConfigs, abTest, } = props;
|
|
22
|
+
const { apiKey, itemId, itemName, threadId, variationId, cioClient, displayConfigs, componentOverrides, callbacks, formatters, productCardProps, children, translations, suggestedQuestionsParameters, answerParameters, parameters, trackingConfigs, checkoutTriggers, abTest, initialConversationHistory, } = props;
|
|
22
23
|
const { learnMoreUrl, showFeedback, mode = 'default', type = 'inline', showPreviousItems, disclaimerPosition = 'bottom', } = displayConfigs || {};
|
|
23
24
|
const isConversation = mode === 'conversation' || type === 'modal';
|
|
24
25
|
const { priceCurrency } = productCardProps || {};
|
|
@@ -41,7 +42,14 @@ export default function CioPiaQna(props) {
|
|
|
41
42
|
variationId,
|
|
42
43
|
threadId: pia.threadId,
|
|
43
44
|
});
|
|
44
|
-
const { currentQuestion, displayedQuestions, conversationHistory, currentAnswer, currentItems, isLoading, error, context, handleSubmitQuestion, handleQuestionClick, handleInputFocus, handleFeedback, resetState, } = useConversation({
|
|
45
|
+
const { currentQuestion, displayedQuestions, conversationHistory, currentAnswer, currentItems, isLoading, error, context, handleSubmitQuestion, handleQuestionClick, handleInputFocus, handleFeedback, resetState, } = useConversation({
|
|
46
|
+
pia,
|
|
47
|
+
itemId,
|
|
48
|
+
isConversation,
|
|
49
|
+
callbacks,
|
|
50
|
+
tracking,
|
|
51
|
+
initialConversationHistory,
|
|
52
|
+
});
|
|
45
53
|
const { containerRef: viewportContainerRef } = useViewportTracking({
|
|
46
54
|
tracking,
|
|
47
55
|
questions: displayedQuestions,
|
|
@@ -69,12 +77,15 @@ export default function CioPiaQna(props) {
|
|
|
69
77
|
handleSubmitQuestion,
|
|
70
78
|
conversationHistory,
|
|
71
79
|
};
|
|
80
|
+
const checkoutElement = checkoutTriggers && checkoutTriggers.length > 0 ? (React.createElement(CheckoutTriggerBar, { triggers: checkoutTriggers, state: renderProps, translations: translations })) : null;
|
|
72
81
|
const conversationHistoryProps = {
|
|
73
82
|
conversationHistory,
|
|
74
83
|
isLoading,
|
|
75
84
|
isAnswerLoading,
|
|
76
85
|
error,
|
|
77
|
-
|
|
86
|
+
checkoutElement,
|
|
87
|
+
// Until a live answer arrives, a seeded last entry shows its own items. `null` would hide them.
|
|
88
|
+
currentItems: pia.answers.data ? currentItems : undefined,
|
|
78
89
|
showFeedback,
|
|
79
90
|
showPreviousItems,
|
|
80
91
|
learnMoreUrl,
|
|
@@ -107,6 +118,7 @@ export default function CioPiaQna(props) {
|
|
|
107
118
|
!isAnswerLoading && error && (React.createElement(ErrorBlock, { key: error.message || translate('Unexpected error', translations), message: error.message, translations: translations })),
|
|
108
119
|
!isAnswerLoading && !error && currentAnswer && (React.createElement(PiaInlineAnswer, { currentAnswer: currentAnswer, currentItems: currentItems, showFeedback: showFeedback, learnMoreUrl: learnMoreUrl, disclaimerPosition: disclaimerPosition, translations: translations, callbacks: callbacks, componentOverrides: componentOverrides, onFeedback: handleFeedback, onResultClick: tracking.trackResultClick, question: currentQuestion, qnaResultId: qnaResultId, priceCurrency: priceCurrency })),
|
|
109
120
|
isLoading && React.createElement(SuggestedQuestionsSkeleton, null),
|
|
110
|
-
!isLoading && !error && (React.createElement(SuggestedQuestionsContainer, { questions: displayedQuestions, onQuestionClick: handleQuestionClick, componentOverride: componentOverrides?.suggestedQuestions }))
|
|
121
|
+
!isLoading && !error && (React.createElement(SuggestedQuestionsContainer, { questions: displayedQuestions, onQuestionClick: handleQuestionClick, componentOverride: componentOverrides?.suggestedQuestions })),
|
|
122
|
+
checkoutElement),
|
|
111
123
|
React.createElement(StatusRegion, { message: answerStatus, "data-testid": 'answer-status' })));
|
|
112
124
|
}
|
|
@@ -54,3 +54,36 @@
|
|
|
54
54
|
outline: 2px solid #0f1324;
|
|
55
55
|
outline-offset: 2px;
|
|
56
56
|
}
|
|
57
|
+
|
|
58
|
+
.cio-pia-checkout-triggers {
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-wrap: wrap;
|
|
61
|
+
gap: 8px;
|
|
62
|
+
margin-top: 4px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.cio-pia-checkout-trigger {
|
|
66
|
+
background: #0f1324;
|
|
67
|
+
color: #fff;
|
|
68
|
+
border: none;
|
|
69
|
+
border-radius: 6px;
|
|
70
|
+
padding: 10px 16px;
|
|
71
|
+
font-size: 14px;
|
|
72
|
+
font-weight: 500;
|
|
73
|
+
cursor: pointer;
|
|
74
|
+
transition: opacity 0.15s ease;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.cio-pia-checkout-trigger:hover {
|
|
78
|
+
opacity: 0.9;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.cio-pia-checkout-trigger:focus-visible {
|
|
82
|
+
outline: 2px solid #0f1324;
|
|
83
|
+
outline-offset: 2px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.cio-pia-checkout-trigger:disabled {
|
|
87
|
+
opacity: 0.5;
|
|
88
|
+
cursor: not-allowed;
|
|
89
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import Answer from '../Answer/Answer';
|
|
3
3
|
import Feedback from '../Feedback/Feedback';
|
|
4
4
|
import Disclaimer from '../CioPia/Disclaimer';
|
|
@@ -19,7 +19,11 @@ export default function ConversationHistory({ conversationHistory, isLoading, is
|
|
|
19
19
|
return () => cancelAnimationFrame(frameId);
|
|
20
20
|
}, [conversationHistory, isLoading]);
|
|
21
21
|
const lastEntry = conversationHistory[conversationHistory.length - 1];
|
|
22
|
-
|
|
22
|
+
// An entry answered before mount (a restored conversation) was not just answered: announcing
|
|
23
|
+
// "Answer ready" on page load would tell the shopper something happened when nothing did.
|
|
24
|
+
const [answeredOnMountId] = useState(() => (lastEntry?.answer ? lastEntry.id : undefined));
|
|
25
|
+
const hasNewAnswer = !!lastEntry?.answer && lastEntry.id !== answeredOnMountId;
|
|
26
|
+
const answerStatus = answerStatusMessage(isAnswerLoading, hasNewAnswer, translations);
|
|
23
27
|
const disclaimer = (React.createElement(Disclaimer, { learnMoreUrl: learnMoreUrl, translations: translations, componentOverride: componentOverrides?.disclaimer }));
|
|
24
28
|
return (React.createElement("div", { className: 'cio-pia-conversation-history' },
|
|
25
29
|
disclaimerPosition === 'top' && disclaimer,
|
|
@@ -35,7 +39,7 @@ export default function ConversationHistory({ conversationHistory, isLoading, is
|
|
|
35
39
|
isLast && !isLoading && error && (React.createElement(ErrorBlock, { key: error.message || translate('Unexpected error', translations), message: error.message, translations: translations, announce: false })),
|
|
36
40
|
entry.answer && (React.createElement("div", { className: 'cio-pia-answer-container' },
|
|
37
41
|
React.createElement(Answer, { text: entry.answer, componentOverride: componentOverrides?.answer }),
|
|
38
|
-
carouselItems && (React.createElement(PiaCustomCarousel, { items: carouselItems, componentOverrides: componentOverrides?.carousel, callbacks: callbacks, onResultClick: onResultClick, question: entry.question, qnaResultId: qnaResultId, translations: translations, priceCurrency: priceCurrency })),
|
|
42
|
+
carouselItems && (React.createElement(PiaCustomCarousel, { items: carouselItems, componentOverrides: componentOverrides?.carousel, callbacks: callbacks, onResultClick: onResultClick, question: entry.question, qnaResultId: entry.qnaResultId ?? (isLast ? qnaResultId : undefined), translations: translations, priceCurrency: priceCurrency })),
|
|
39
43
|
isLast && showFeedback && (React.createElement(Feedback, { translations: translations, onFeedback: handleFeedback, componentOverride: componentOverrides?.feedback }))))));
|
|
40
44
|
})),
|
|
41
45
|
disclaimerPosition === 'bottom' && disclaimer,
|
|
@@ -4,11 +4,12 @@ import SuggestedQuestionsContainer from '../SuggestedQuestionsContainer/Suggeste
|
|
|
4
4
|
import SuggestedQuestionsSkeleton from '../SuggestedQuestionsContainer/SuggestedQuestionsSkeleton';
|
|
5
5
|
import { translate } from '../../utils/translate';
|
|
6
6
|
import ConversationHistory from '../ConversationHistory/ConversationHistory';
|
|
7
|
-
export default function PiaConversation({ conversationHistory, isLoading, error, currentItems, showFeedback, showPreviousItems, learnMoreUrl, disclaimerPosition, translations, callbacks, componentOverrides, displayedQuestions, handleSubmitQuestion, handleQuestionClick, containerRef, handleFeedback, onResultClick, qnaResultId, onInputFocus, priceCurrency, }) {
|
|
7
|
+
export default function PiaConversation({ conversationHistory, isLoading, error, currentItems, showFeedback, showPreviousItems, learnMoreUrl, disclaimerPosition, translations, callbacks, componentOverrides, displayedQuestions, handleSubmitQuestion, handleQuestionClick, containerRef, handleFeedback, onResultClick, qnaResultId, onInputFocus, priceCurrency, checkoutElement, }) {
|
|
8
8
|
const hasHistory = conversationHistory.length > 0;
|
|
9
9
|
return (React.createElement("div", { ref: containerRef, className: 'cio-pia-container cio-pia-conversation', "data-testid": 'cio-pia-container' },
|
|
10
10
|
!hasHistory && (React.createElement("p", { className: 'cio-pia-title', "data-testid": 'cio-pia-title' }, translate('Any questions about this product?', translations))),
|
|
11
11
|
React.createElement(ConversationHistory, { conversationHistory: conversationHistory, isLoading: isLoading, error: error, currentItems: currentItems, showFeedback: showFeedback, showPreviousItems: showPreviousItems, learnMoreUrl: learnMoreUrl, disclaimerPosition: disclaimerPosition, translations: translations, callbacks: callbacks, componentOverrides: componentOverrides, handleFeedback: handleFeedback, onResultClick: onResultClick, qnaResultId: qnaResultId, priceCurrency: priceCurrency }),
|
|
12
|
+
checkoutElement && React.createElement("div", { className: 'cio-pia-conversation-checkout' }, checkoutElement),
|
|
12
13
|
React.createElement("div", { className: 'cio-pia-conversation-footer' },
|
|
13
14
|
isLoading && !error && React.createElement(SuggestedQuestionsSkeleton, null),
|
|
14
15
|
!isLoading && !error && (React.createElement(SuggestedQuestionsContainer, { questions: displayedQuestions, onQuestionClick: handleQuestionClick, componentOverride: componentOverrides?.suggestedQuestions })),
|
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
|
|
2
|
+
function isRestorableEntry(entry) {
|
|
3
|
+
if (typeof entry !== 'object' || entry === null)
|
|
4
|
+
return false;
|
|
5
|
+
const { question, answer, items } = entry;
|
|
6
|
+
return (typeof question === 'string' &&
|
|
7
|
+
typeof answer === 'string' &&
|
|
8
|
+
(items === undefined || items === null || Array.isArray(items)));
|
|
9
|
+
}
|
|
10
|
+
// The seed comes from the host's own storage, typed or not: a malformed one must not take the widget down.
|
|
11
|
+
function restoreHistory(seed) {
|
|
12
|
+
if (!Array.isArray(seed)) {
|
|
13
|
+
console.warn('[CioPia] initialConversationHistory must be an array. It was ignored.');
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const entries = seed.filter(isRestorableEntry);
|
|
17
|
+
if (entries.length < seed.length) {
|
|
18
|
+
console.warn(`[CioPia] ${seed.length - entries.length} initialConversationHistory entries were ignored. Each needs a string question and answer, and items must be an array.`);
|
|
19
|
+
}
|
|
20
|
+
// Seeded ids are replaced: `id` is the React key, so a caller's values cannot be trusted to be unique.
|
|
21
|
+
return entries.map((entry, index) => ({ ...entry, id: index + 1 }));
|
|
22
|
+
}
|
|
23
|
+
export default function useConversation({ pia, itemId, isConversation, callbacks, tracking, initialConversationHistory, }) {
|
|
3
24
|
const { suggestedQuestions, answers, threadId } = pia;
|
|
4
25
|
const { getAnswer } = answers;
|
|
5
26
|
const context = useMemo(() => ({ itemId, threadId }), [itemId, threadId]);
|
|
6
27
|
const [currentQuestion, setCurrentQuestion] = useState('');
|
|
7
28
|
const [displayedQuestions, setDisplayedQuestions] = useState([]);
|
|
8
|
-
const [conversationHistory, setConversationHistory] = useState(
|
|
9
|
-
|
|
29
|
+
const [conversationHistory, setConversationHistory] = useState(() => isConversation && initialConversationHistory !== undefined
|
|
30
|
+
? restoreHistory(initialConversationHistory)
|
|
31
|
+
: []);
|
|
32
|
+
const entryIdRef = useRef(conversationHistory.length);
|
|
33
|
+
const conversationHistoryRef = useRef(conversationHistory);
|
|
34
|
+
const prevItemIdRef = useRef(itemId);
|
|
10
35
|
const prevAnswerDataRef = useRef(answers.data);
|
|
11
36
|
const hasTrackedCurrentAnswerRef = useRef(false);
|
|
12
37
|
const answersRef = useRef(answers);
|
|
@@ -30,6 +55,9 @@ export default function useConversation({ pia, itemId, isConversation, callbacks
|
|
|
30
55
|
useEffect(() => {
|
|
31
56
|
answersRef.current = answers;
|
|
32
57
|
}, [answers]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
conversationHistoryRef.current = conversationHistory;
|
|
60
|
+
}, [conversationHistory]);
|
|
33
61
|
const submitQuestion = useCallback((question, source) => {
|
|
34
62
|
lastSourceRef.current = source;
|
|
35
63
|
lastQuestionRef.current = question;
|
|
@@ -57,7 +85,10 @@ export default function useConversation({ pia, itemId, isConversation, callbacks
|
|
|
57
85
|
callbacksRef.current?.onFocus?.(contextRef.current);
|
|
58
86
|
}, []);
|
|
59
87
|
const handleFeedback = useCallback((type) => {
|
|
60
|
-
|
|
88
|
+
// A seeded last entry has no live response behind it; its own id is the answer rated.
|
|
89
|
+
const history = conversationHistoryRef.current;
|
|
90
|
+
const qnaResultId = answersRef.current.data?.qna_result_id ?? history[history.length - 1]?.qnaResultId;
|
|
91
|
+
trackingRef.current?.trackAnswerFeedback(type, qnaResultId);
|
|
61
92
|
callbacksRef.current?.onFeedback?.(type);
|
|
62
93
|
}, []);
|
|
63
94
|
const resetState = useCallback(() => {
|
|
@@ -68,6 +99,10 @@ export default function useConversation({ pia, itemId, isConversation, callbacks
|
|
|
68
99
|
showsFollowUpsRef.current = false;
|
|
69
100
|
}, [suggestedQuestions.data]);
|
|
70
101
|
useEffect(() => {
|
|
102
|
+
// Skip the mount run: it would wipe the seeded history.
|
|
103
|
+
if (prevItemIdRef.current === itemId)
|
|
104
|
+
return;
|
|
105
|
+
prevItemIdRef.current = itemId;
|
|
71
106
|
setCurrentQuestion('');
|
|
72
107
|
setDisplayedQuestions([]);
|
|
73
108
|
setConversationHistory([]);
|
|
@@ -25,8 +25,8 @@ div.link-list {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
@media all and (-ms-high-contrast:none) {
|
|
29
|
-
div.link-list {
|
|
28
|
+
@media all and (-ms-high-contrast: none) {
|
|
29
|
+
div.link-list {
|
|
30
30
|
display: -ms-grid;
|
|
31
31
|
-ms-grid-columns: 1fr 1fr;
|
|
32
32
|
-ms-grid-rows: 1fr 1fr;
|
|
@@ -38,20 +38,23 @@ a.link-item {
|
|
|
38
38
|
padding: 20px;
|
|
39
39
|
border: 1px solid #00000010;
|
|
40
40
|
border-radius: 5px;
|
|
41
|
-
transition:
|
|
41
|
+
transition:
|
|
42
|
+
background 150ms ease-out,
|
|
43
|
+
border 150ms ease-out,
|
|
44
|
+
transform 150ms ease-out;
|
|
42
45
|
color: #333333;
|
|
43
46
|
display: flex;
|
|
44
47
|
align-items: flex-start;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
a.link-item:hover {
|
|
48
|
-
border-color: #
|
|
51
|
+
border-color: #1ea7fd50;
|
|
49
52
|
transform: translate3d(0, -3px, 0);
|
|
50
53
|
box-shadow: rgba(0, 0, 0, 0.08) 0 3px 10px 0;
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
a.link-item:active {
|
|
54
|
-
border-color: #
|
|
57
|
+
border-color: #1ea7fd;
|
|
55
58
|
transform: translate3d(0, 0, 0);
|
|
56
59
|
}
|
|
57
60
|
|
|
@@ -81,8 +84,8 @@ span.badge {
|
|
|
81
84
|
font-size: 11px;
|
|
82
85
|
line-height: 12px;
|
|
83
86
|
font-weight: 700;
|
|
84
|
-
background: #
|
|
85
|
-
color: #
|
|
87
|
+
background: #e7fdd8;
|
|
88
|
+
color: #66bf3c;
|
|
86
89
|
padding: 4px 12px;
|
|
87
90
|
margin-right: 10px;
|
|
88
91
|
vertical-align: top;
|
|
@@ -100,10 +103,9 @@ div.badge-wrapper code {
|
|
|
100
103
|
display: inline-block;
|
|
101
104
|
}
|
|
102
105
|
|
|
103
|
-
|
|
104
106
|
.badge.tip {
|
|
105
|
-
background: #
|
|
106
|
-
color: #
|
|
107
|
+
background: #e7fdd8;
|
|
108
|
+
color: #66bf3c;
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
.badge.note {
|
package/lib/mjs/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '2.
|
|
1
|
+
export default '2.16.0';
|