@redocly/theme 0.53.0-next.3 → 0.53.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/lib/components/LastUpdated/LastUpdated.d.ts +2 -7
- package/lib/components/LastUpdated/LastUpdated.js +12 -12
- package/lib/core/hooks/__mocks__/index.d.ts +1 -0
- package/lib/core/hooks/__mocks__/index.js +1 -0
- package/lib/core/hooks/__mocks__/use-time-ago.d.ts +3 -0
- package/lib/core/hooks/__mocks__/use-time-ago.js +7 -0
- package/lib/core/hooks/index.d.ts +1 -0
- package/lib/core/hooks/index.js +1 -0
- package/lib/core/hooks/use-time-ago.d.ts +3 -0
- package/lib/core/hooks/use-time-ago.js +40 -0
- package/lib/core/types/common.d.ts +1 -0
- package/lib/core/types/common.js +3 -0
- package/lib/core/types/index.d.ts +1 -0
- package/lib/core/types/index.js +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/package.json +1 -2
- package/src/components/LastUpdated/LastUpdated.tsx +18 -13
- package/src/core/hooks/__mocks__/index.ts +1 -0
- package/src/core/hooks/__mocks__/use-time-ago.ts +3 -0
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/use-time-ago.ts +54 -0
- package/src/core/types/common.ts +1 -0
- package/src/core/types/index.ts +1 -0
- package/src/core/types/l10n.ts +16 -1
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
timeago: (date: Date, locale: string) => string;
|
|
3
|
-
iso: (date: Date) => string;
|
|
4
|
-
short: (date: Date, locale: string) => string;
|
|
5
|
-
long: (date: Date, locale: string) => string;
|
|
6
|
-
};
|
|
1
|
+
type Formats = 'timeago' | 'iso' | 'short' | 'long';
|
|
7
2
|
export type LastUpdatedProps = {
|
|
8
3
|
lastModified: Date;
|
|
9
|
-
format?:
|
|
4
|
+
format?: Formats;
|
|
10
5
|
locale?: string;
|
|
11
6
|
className?: string;
|
|
12
7
|
};
|
|
@@ -27,22 +27,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
29
|
exports.LastUpdated = LastUpdated;
|
|
30
|
-
const
|
|
30
|
+
const react_1 = __importStar(require("react"));
|
|
31
31
|
const styled_components_1 = __importDefault(require("styled-components"));
|
|
32
|
-
const timeago_js_1 = require("timeago.js");
|
|
33
|
-
const hooks_1 = require("../../core/hooks");
|
|
34
32
|
const constants_1 = require("../../core/constants");
|
|
35
|
-
const
|
|
36
|
-
timeago: (date, locale) => (0, timeago_js_1.format)(date, locale),
|
|
37
|
-
iso: (date) => date.toISOString().split('T')[0],
|
|
38
|
-
short: (date, locale) => date.toLocaleDateString(locale, { month: 'short', day: 'numeric', year: 'numeric' }),
|
|
39
|
-
long: (date, locale) => date.toLocaleDateString(locale, { month: 'long', day: 'numeric', year: 'numeric' }),
|
|
40
|
-
};
|
|
33
|
+
const hooks_1 = require("../../core/hooks");
|
|
41
34
|
function LastUpdated(props) {
|
|
42
35
|
const { markdown: { lastUpdatedBlock = {} } = {} } = (0, hooks_1.useThemeConfig)();
|
|
43
36
|
const { useTranslate, useL10nConfig } = (0, hooks_1.useThemeHooks)();
|
|
37
|
+
const { format: timeagoFormat } = (0, hooks_1.useTimeAgo)();
|
|
44
38
|
const { translate } = useTranslate();
|
|
45
39
|
const { currentLocale } = useL10nConfig();
|
|
40
|
+
const formats = (0, react_1.useMemo)(() => ({
|
|
41
|
+
timeago: (date) => timeagoFormat(date),
|
|
42
|
+
iso: (date) => date.toISOString().split('T')[0],
|
|
43
|
+
short: (date, locale) => date.toLocaleDateString(locale, { month: 'short', day: 'numeric', year: 'numeric' }),
|
|
44
|
+
long: (date, locale) => date.toLocaleDateString(locale, { month: 'long', day: 'numeric', year: 'numeric' }),
|
|
45
|
+
}), [timeagoFormat]);
|
|
46
46
|
if (lastUpdatedBlock === null || lastUpdatedBlock === void 0 ? void 0 : lastUpdatedBlock.hide) {
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
@@ -52,15 +52,15 @@ function LastUpdated(props) {
|
|
|
52
52
|
lastUpdatedBlock.locale ||
|
|
53
53
|
(currentLocale !== constants_1.DEFAULT_LOCALE_PLACEHOLDER ? currentLocale || 'en-US' : 'en-US');
|
|
54
54
|
const isoDate = lastModified.toISOString().split('T')[0];
|
|
55
|
-
const lastUpdatedString =
|
|
55
|
+
const lastUpdatedString = formats[format](lastModified, locale);
|
|
56
56
|
const translationKey = format === 'timeago' ? 'page.lastUpdated.timeago' : 'page.lastUpdated.on';
|
|
57
57
|
const text = format === 'timeago'
|
|
58
58
|
? translate(translationKey, 'Last updated') + ' '
|
|
59
59
|
: translate(translationKey, 'Last updated on');
|
|
60
|
-
return (
|
|
60
|
+
return (react_1.default.createElement(LastUpdatedWrapper, { className: props.className, "data-component-name": "LastUpdated/LastUpdated", rawOnPrint: format === 'timeago', "data-print-datetime": isoDate, "data-translation-key": translationKey },
|
|
61
61
|
text,
|
|
62
62
|
" ",
|
|
63
|
-
|
|
63
|
+
react_1.default.createElement("time", { dateTime: isoDate }, lastUpdatedString)));
|
|
64
64
|
}
|
|
65
65
|
const LastUpdatedWrapper = styled_components_1.default.div `
|
|
66
66
|
color: var(--last-updated-text-color);
|
|
@@ -24,3 +24,4 @@ export * from '../../../core/hooks/use-product-picker';
|
|
|
24
24
|
export * from '../../../core/hooks/search/use-search-dialog';
|
|
25
25
|
export * from '../../../core/hooks/use-language-picker';
|
|
26
26
|
export * from '../../../core/hooks/__mocks__/use-element-size';
|
|
27
|
+
export * from '../../../core/hooks/__mocks__/use-time-ago';
|
|
@@ -40,4 +40,5 @@ __exportStar(require("../../../core/hooks/use-product-picker"), exports);
|
|
|
40
40
|
__exportStar(require("../../../core/hooks/search/use-search-dialog"), exports);
|
|
41
41
|
__exportStar(require("../../../core/hooks/use-language-picker"), exports);
|
|
42
42
|
__exportStar(require("../../../core/hooks/__mocks__/use-element-size"), exports);
|
|
43
|
+
__exportStar(require("../../../core/hooks/__mocks__/use-time-ago"), exports);
|
|
43
44
|
//# sourceMappingURL=index.js.map
|
|
@@ -34,3 +34,4 @@ export * from '../../core/hooks/code-walkthrough/use-code-walkthrough-controls';
|
|
|
34
34
|
export * from '../../core/hooks/code-walkthrough/use-code-panel';
|
|
35
35
|
export * from '../../core/hooks/code-walkthrough/use-renderable-files';
|
|
36
36
|
export * from '../../core/hooks/use-element-size';
|
|
37
|
+
export * from '../../core/hooks/use-time-ago';
|
package/lib/core/hooks/index.js
CHANGED
|
@@ -50,4 +50,5 @@ __exportStar(require("../../core/hooks/code-walkthrough/use-code-walkthrough-con
|
|
|
50
50
|
__exportStar(require("../../core/hooks/code-walkthrough/use-code-panel"), exports);
|
|
51
51
|
__exportStar(require("../../core/hooks/code-walkthrough/use-renderable-files"), exports);
|
|
52
52
|
__exportStar(require("../../core/hooks/use-element-size"), exports);
|
|
53
|
+
__exportStar(require("../../core/hooks/use-time-ago"), exports);
|
|
53
54
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useTimeAgo = useTimeAgo;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const hooks_1 = require("../../core/hooks");
|
|
6
|
+
const timeUnits = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'];
|
|
7
|
+
const timeUnitConversions = [
|
|
8
|
+
60, // 60 seconds in 1 min
|
|
9
|
+
60, // 60 mins in 1 hour
|
|
10
|
+
24, // 24 hours in 1 day
|
|
11
|
+
7, // 7 days in 1 week
|
|
12
|
+
365 / 7 / 12, // 4.345238095238096 weeks in 1 month
|
|
13
|
+
12, // 12 months in 1 year
|
|
14
|
+
];
|
|
15
|
+
function useTimeAgo() {
|
|
16
|
+
const { useTranslate } = (0, hooks_1.useThemeHooks)();
|
|
17
|
+
const { translate } = useTranslate();
|
|
18
|
+
const format = (0, react_1.useCallback)((date) => {
|
|
19
|
+
let timeUnitIndex = 0;
|
|
20
|
+
let elapsedTime = (Date.now() - date.getTime()) / 1000;
|
|
21
|
+
for (; elapsedTime >= timeUnitConversions[timeUnitIndex] &&
|
|
22
|
+
timeUnitIndex < timeUnitConversions.length; timeUnitIndex++) {
|
|
23
|
+
elapsedTime /= timeUnitConversions[timeUnitIndex];
|
|
24
|
+
}
|
|
25
|
+
elapsedTime = Math.floor(elapsedTime);
|
|
26
|
+
if (timeUnitIndex === 0)
|
|
27
|
+
return translate('time.justNow', 'just now');
|
|
28
|
+
let timeUnit = timeUnits[timeUnitIndex];
|
|
29
|
+
if (elapsedTime > 1)
|
|
30
|
+
timeUnit = `${timeUnit}s`;
|
|
31
|
+
const translationKey = `time.past.${timeUnit}`;
|
|
32
|
+
return translate(translationKey, {
|
|
33
|
+
defaultValue: `${elapsedTime} ${timeUnit} ago`,
|
|
34
|
+
count: elapsedTime,
|
|
35
|
+
replace: { value: elapsedTime },
|
|
36
|
+
});
|
|
37
|
+
}, [translate]);
|
|
38
|
+
return { format };
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=use-time-ago.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Plural<T extends string> = `${T}s`;
|
package/lib/core/types/index.js
CHANGED
|
@@ -27,4 +27,5 @@ __exportStar(require("../../core/types/sidebar"), exports);
|
|
|
27
27
|
__exportStar(require("../../core/types/filter"), exports);
|
|
28
28
|
__exportStar(require("../../core/types/user-menu"), exports);
|
|
29
29
|
__exportStar(require("../../core/types/user-claims"), exports);
|
|
30
|
+
__exportStar(require("../../core/types/common"), exports);
|
|
30
31
|
//# sourceMappingURL=index.js.map
|
package/lib/core/types/l10n.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TOptions } from 'i18next';
|
|
2
|
-
export type TranslationKey = 'dev.newApp' | 'dev.newApp.text' | 'dev.sidebar.header' | 'dev.sidebar.footer.text' | 'dev.create.app.dialog.appName.placeholder' | 'dev.create.app.dialog.appName.error' | 'dev.create.app.dialog.selectAPIs' | 'dev.create.app.dialog.description' | 'dev.create.app.dialog.description.placeholder' | 'dev.create.app.dialog.create' | 'dev.create.app.dialog.cancel' | 'dev.main.tab.appKeys' | 'dev.main.tab.logs' | 'dev.app.description.title' | 'dev.edit.description.dialog.title' | 'dev.edit.description.dialog.save' | 'dev.edit.description.dialog.cancel' | 'dev.edit.apis.dialog.selectedAPIs' | 'dev.app.key.create' | 'dev.create.key.dialog.title' | 'dev.create.key.dialog.create' | 'dev.create.key.dialog.cancel' | 'dev.app.edit' | 'dev.app.delete' | 'dev.edit.app.dialog.title' | 'dev.edit.app.dialog.save' | 'dev.edit.app.dialog.cancel' | 'dev.delete.app.dialog.title' | 'dev.delete.app.dialog.confirmation' | 'dev.delete.app.dialog.delete' | 'dev.delete.app.dialog.cancel' | 'dev.app.key.roll' | 'dev.roll.key.dialog.title' | 'dev.roll.key.dialog.apiKey' | 'dev.roll.key.dialog.expires' | 'dev.roll.key.dialog.confirmation' | 'dev.roll.key.dialog.cancel' | 'dev.roll.key.dialog.roll' | 'dev.update.key.dialog.title' | 'dev.update.key.dialog.update' | 'dev.update.key.dialog.cancel' | 'dev.app.key.api.name' | 'dev.app.key.api.status' | 'dev.app.key.api.edit' | 'dev.edit.apis.dialog.title' | 'dev.edit.apis.dialog.apiKey' | 'dev.edit.apis.dialog.save' | 'dev.edit.apis.dialog.cancel' | 'dev.select.placeholder' | 'dev.app.overview.status.pending' | 'dev.app.overview.status.approved' | 'dev.app.overview.status.revoked' | 'dev.app.overview.status' | 'dev.app.overview.non-production' | 'dev.app.overview.production' | 'dev.app.overview.clientId' | 'dev.app.overview.apiKey' | 'dev.app.key.revoke' | 'dev.revoke.key.dialog.title' | 'dev.revoke.key.dialog.apiKey' | 'dev.revoke.key.dialog.expires' | 'dev.revoke.key.dialog.confirmation' | 'dev.revoke.key.dialog.revoke' | 'dev.revoke.key.dialog.cancel' | 'dev.app.overview.expires' | 'dev.app.overview.created' | 'dev.app.overview.visibilityToggle.hide' | 'dev.app.overview.visibilityToggle.show' | 'search.loading' | 'search.noResults.title' | 'search.keys.navigate' | 'search.keys.select' | 'search.keys.exit' | 'search.label' | 'search.cancel' | 'search.recent' | 'search.navbar.label' | 'search.suggested' | 'search.showMore' | 'search.filter.title' | 'search.filter.reset' | 'search.filter.field.reset' | 'search.ai.welcomeText' | 'search.ai.newConversation' | 'search.ai.backToSearch' | 'search.ai.placeholder' | 'search.ai.generatingResponse' | 'search.ai.followUpQuestion' | 'search.ai.suggestionsTitle' | 'search.ai.thinkingText' | 'search.ai.resourcesFound' | 'search.ai.resourcesFound.basedOn' | 'search.ai.resourcesFound.resources' | 'search.ai.button' | 'search.ai.label' | 'search.ai.disclaimer' | 'search.ai.error.description' | 'search.ai.error.description.forbidden' | 'search.ai.error.description.unauthorized' | 'search.ai.error.header' | 'search.ai.error.header.forbidden' | 'search.ai.error.header.unauthorized' | 'toc.header' | 'footer.copyrightText' | 'page.homeButton' | 'page.forbidden.title' | 'page.notFound.title' | 'page.notFound.description' | 'page.lastUpdated.timeago' | 'page.lastUpdated.on' | 'catalog.filters.placeholder' | 'catalog.filters.title' | 'catalog.filters.clearAll' | 'catalog.filters.select.addFilter' | 'catalog.filters.select.all' | 'catalog.filters.done' | 'sidebar.menu.backLabel' | 'sidebar.menu.backToLabel' | 'sidebar.actions.show' | 'sidebar.actions.hide' | 'sidebar.actions.changeLayout' | 'versionPicker.label' | 'versionPicker.unversioned' | 'codeSnippet.copy.buttonText' | 'codeSnippet.copy.tooltipText' | 'codeSnippet.copy.toasterText' | 'markdown.editPage.text' | 'feedback.settings.comment.submitText' | 'feedback.settings.comment.label' | 'feedback.settings.comment.send' | 'feedback.settings.comment.cancel' | 'feedback.settings.comment.satisfiedLabel' | 'feedback.settings.comment.neutralLabel' | 'feedback.settings.comment.dissatisfiedLabel' | 'feedback.settings.submitText' | 'feedback.settings.label' | 'feedback.settings.reasons.label' | 'feedback.settings.reasons.send' | 'feedback.settings.comment.likeLabel' | 'feedback.settings.comment.dislikeLabel' | 'feedback.sentiment.thumbUp' | 'feedback.sentiment.thumbDown' | 'feedback.settings.leftScaleLabel' | 'feedback.settings.rightScaleLabel' | 'feedback.settings.optionalEmail.placeholder' | 'feedback.settings.optionalEmail.label' | 'codeSnippet.report.buttonText' | 'codeSnippet.report.tooltipText' | 'codeSnippet.report.label' | 'userMenu.login' | 'userMenu.logout' | 'userMenu.devOnboardingLabel' | 'mobileMenu.mainMenu' | 'mobileMenu.previous' | 'mobileMenu.products' | 'page.nextButton' | 'page.previousButton' | 'openapi.download.description.title' | 'openapi.info.title' | 'openapi.info.contact.url' | 'openapi.info.contact.name' | 'openapi.info.license' | 'openapi.info.termsOfService' | 'openapi.info.metadata.title' | 'openapi.key' | 'openapi.value' | 'openapi.enum' | 'openapi.items' | 'openapi.default' | 'openapi.variable' | 'openapi.variables' | 'openapi.actions.show' | 'openapi.actions.hide' | 'openapi.actions.more' | 'openapi.languages.title' | 'openapi.servers.title' | 'openapi.operations' | 'openapi.webhooks' | 'openapi.description' | 'openapi.badges.deprecated' | 'openapi.badges.required' | 'openapi.badges.webhook' | 'openapi.request' | 'openapi.path' | 'openapi.query' | 'openapi.cookie' | 'openapi.header' | 'openapi.body' | 'openapi.responses' | 'openapi.response' | 'openapi.callbacks' | 'openapi.callbackRequest' | 'openapi.callbackResponse' | 'openapi.payload' | 'openapi.discriminator' | 'openapi.contentType' | 'openapi.tryIt' | 'openapi.loading' | 'openapi.example' | 'openapi.examples' | 'openapi.additionalProperties' | 'openapi.patternProperties' | 'openapi.required' | 'openapi.recursive' | 'openapi.complex' | 'openapi.deprecated' | 'openapi.hideExample' | 'openapi.showExample' | 'openapi.expandAll' | 'openapi.collapseAll' | 'openapi.noResponseExample' | 'openapi.noResponseContent' | 'openapi.noRequestPayload' | 'openapi.hidePattern' | 'openapi.showPattern' | 'openapi.authorizationUrl' | 'openapi.tokenUrl' | 'openapi.refreshUrl' | 'openapi.scopes' | 'openapi.security' | 'openapi.httpAuthorizationScheme' | 'openapi.bearerFormat' | 'openapi.parameterName' | 'openapi.flowType' | 'openapi.connectUrl' | 'openapi.requiredScopes' | 'openapi.unsupportedLanguage' | 'openapi.failedToGenerateCodeSample' | 'asyncapi.download.description.title' | 'asyncapi.info.title' | 'graphql.queries' | 'graphql.mutations' | 'graphql.subscriptions' | 'graphql.directives' | 'graphql.objects' | 'graphql.interfaces' | 'graphql.unions' | 'graphql.enums' | 'graphql.inputs' | 'graphql.scalars' | 'graphql.arguments.label' | 'graphql.arguments.show' | 'graphql.arguments.hide' | 'graphql.arguments.here' | 'graphql.returnTypes.label' | 'graphql.returnTypes.show' | 'graphql.returnTypes.hide' | 'graphql.possibleTypes' | 'graphql.defaultValue' | 'graphql.deprecationReason' | 'graphql.implementedInterfaces' | 'graphql.nonNull' | 'graphql.required' | 'graphql.deprecated' | 'graphql.variables' | 'graphql.querySample' | 'graphql.mutationSample' | 'graphql.subscriptionSample' | 'graphql.responseSample' | 'graphql.locations' | 'graphql.sample' | 'graphql.referenced' | 'codeWalkthrough.download' | 'codeWalkthrough.preview';
|
|
2
|
+
export type TranslationKey = 'dev.newApp' | 'dev.newApp.text' | 'dev.sidebar.header' | 'dev.sidebar.footer.text' | 'dev.create.app.dialog.appName.placeholder' | 'dev.create.app.dialog.appName.error' | 'dev.create.app.dialog.selectAPIs' | 'dev.create.app.dialog.description' | 'dev.create.app.dialog.description.placeholder' | 'dev.create.app.dialog.create' | 'dev.create.app.dialog.cancel' | 'dev.main.tab.appKeys' | 'dev.main.tab.logs' | 'dev.app.description.title' | 'dev.edit.description.dialog.title' | 'dev.edit.description.dialog.save' | 'dev.edit.description.dialog.cancel' | 'dev.edit.apis.dialog.selectedAPIs' | 'dev.app.key.create' | 'dev.create.key.dialog.title' | 'dev.create.key.dialog.create' | 'dev.create.key.dialog.cancel' | 'dev.app.edit' | 'dev.app.delete' | 'dev.edit.app.dialog.title' | 'dev.edit.app.dialog.save' | 'dev.edit.app.dialog.cancel' | 'dev.delete.app.dialog.title' | 'dev.delete.app.dialog.confirmation' | 'dev.delete.app.dialog.delete' | 'dev.delete.app.dialog.cancel' | 'dev.app.key.roll' | 'dev.roll.key.dialog.title' | 'dev.roll.key.dialog.apiKey' | 'dev.roll.key.dialog.expires' | 'dev.roll.key.dialog.confirmation' | 'dev.roll.key.dialog.cancel' | 'dev.roll.key.dialog.roll' | 'dev.update.key.dialog.title' | 'dev.update.key.dialog.update' | 'dev.update.key.dialog.cancel' | 'dev.app.key.api.name' | 'dev.app.key.api.status' | 'dev.app.key.api.edit' | 'dev.edit.apis.dialog.title' | 'dev.edit.apis.dialog.apiKey' | 'dev.edit.apis.dialog.save' | 'dev.edit.apis.dialog.cancel' | 'dev.select.placeholder' | 'dev.app.overview.status.pending' | 'dev.app.overview.status.approved' | 'dev.app.overview.status.revoked' | 'dev.app.overview.status' | 'dev.app.overview.non-production' | 'dev.app.overview.production' | 'dev.app.overview.clientId' | 'dev.app.overview.apiKey' | 'dev.app.key.revoke' | 'dev.revoke.key.dialog.title' | 'dev.revoke.key.dialog.apiKey' | 'dev.revoke.key.dialog.expires' | 'dev.revoke.key.dialog.confirmation' | 'dev.revoke.key.dialog.revoke' | 'dev.revoke.key.dialog.cancel' | 'dev.app.overview.expires' | 'dev.app.overview.created' | 'dev.app.overview.visibilityToggle.hide' | 'dev.app.overview.visibilityToggle.show' | 'search.loading' | 'search.noResults.title' | 'search.keys.navigate' | 'search.keys.select' | 'search.keys.exit' | 'search.label' | 'search.cancel' | 'search.recent' | 'search.navbar.label' | 'search.suggested' | 'search.showMore' | 'search.filter.title' | 'search.filter.reset' | 'search.filter.field.reset' | 'search.ai.welcomeText' | 'search.ai.newConversation' | 'search.ai.backToSearch' | 'search.ai.placeholder' | 'search.ai.generatingResponse' | 'search.ai.followUpQuestion' | 'search.ai.suggestionsTitle' | 'search.ai.thinkingText' | 'search.ai.resourcesFound' | 'search.ai.resourcesFound.basedOn' | 'search.ai.resourcesFound.resources' | 'search.ai.button' | 'search.ai.label' | 'search.ai.disclaimer' | 'search.ai.error.description' | 'search.ai.error.description.forbidden' | 'search.ai.error.description.unauthorized' | 'search.ai.error.header' | 'search.ai.error.header.forbidden' | 'search.ai.error.header.unauthorized' | 'toc.header' | 'footer.copyrightText' | 'page.homeButton' | 'page.forbidden.title' | 'page.notFound.title' | 'page.notFound.description' | 'page.lastUpdated.timeago' | 'page.lastUpdated.on' | 'catalog.filters.placeholder' | 'catalog.filters.title' | 'catalog.filters.clearAll' | 'catalog.filters.select.addFilter' | 'catalog.filters.select.all' | 'catalog.filters.done' | 'sidebar.menu.backLabel' | 'sidebar.menu.backToLabel' | 'sidebar.actions.show' | 'sidebar.actions.hide' | 'sidebar.actions.changeLayout' | 'versionPicker.label' | 'versionPicker.unversioned' | 'codeSnippet.copy.buttonText' | 'codeSnippet.copy.tooltipText' | 'codeSnippet.copy.toasterText' | 'markdown.editPage.text' | 'feedback.settings.comment.submitText' | 'feedback.settings.comment.label' | 'feedback.settings.comment.send' | 'feedback.settings.comment.cancel' | 'feedback.settings.comment.satisfiedLabel' | 'feedback.settings.comment.neutralLabel' | 'feedback.settings.comment.dissatisfiedLabel' | 'feedback.settings.submitText' | 'feedback.settings.label' | 'feedback.settings.reasons.label' | 'feedback.settings.reasons.send' | 'feedback.settings.comment.likeLabel' | 'feedback.settings.comment.dislikeLabel' | 'feedback.sentiment.thumbUp' | 'feedback.sentiment.thumbDown' | 'feedback.settings.leftScaleLabel' | 'feedback.settings.rightScaleLabel' | 'feedback.settings.optionalEmail.placeholder' | 'feedback.settings.optionalEmail.label' | 'codeSnippet.report.buttonText' | 'codeSnippet.report.tooltipText' | 'codeSnippet.report.label' | 'userMenu.login' | 'userMenu.logout' | 'userMenu.devOnboardingLabel' | 'mobileMenu.mainMenu' | 'mobileMenu.previous' | 'mobileMenu.products' | 'page.nextButton' | 'page.previousButton' | 'openapi.download.description.title' | 'openapi.info.title' | 'openapi.info.contact.url' | 'openapi.info.contact.name' | 'openapi.info.license' | 'openapi.info.termsOfService' | 'openapi.info.metadata.title' | 'openapi.key' | 'openapi.value' | 'openapi.enum' | 'openapi.items' | 'openapi.default' | 'openapi.variable' | 'openapi.variables' | 'openapi.actions.show' | 'openapi.actions.hide' | 'openapi.actions.more' | 'openapi.languages.title' | 'openapi.servers.title' | 'openapi.operations' | 'openapi.webhooks' | 'openapi.description' | 'openapi.badges.deprecated' | 'openapi.badges.required' | 'openapi.badges.webhook' | 'openapi.request' | 'openapi.path' | 'openapi.query' | 'openapi.cookie' | 'openapi.header' | 'openapi.body' | 'openapi.responses' | 'openapi.response' | 'openapi.callbacks' | 'openapi.callbackRequest' | 'openapi.callbackResponse' | 'openapi.payload' | 'openapi.discriminator' | 'openapi.contentType' | 'openapi.tryIt' | 'openapi.loading' | 'openapi.example' | 'openapi.examples' | 'openapi.additionalProperties' | 'openapi.patternProperties' | 'openapi.required' | 'openapi.recursive' | 'openapi.complex' | 'openapi.deprecated' | 'openapi.hideExample' | 'openapi.showExample' | 'openapi.expandAll' | 'openapi.collapseAll' | 'openapi.noResponseExample' | 'openapi.noResponseContent' | 'openapi.noRequestPayload' | 'openapi.hidePattern' | 'openapi.showPattern' | 'openapi.authorizationUrl' | 'openapi.tokenUrl' | 'openapi.refreshUrl' | 'openapi.scopes' | 'openapi.security' | 'openapi.httpAuthorizationScheme' | 'openapi.bearerFormat' | 'openapi.parameterName' | 'openapi.flowType' | 'openapi.connectUrl' | 'openapi.requiredScopes' | 'openapi.unsupportedLanguage' | 'openapi.failedToGenerateCodeSample' | 'asyncapi.download.description.title' | 'asyncapi.info.title' | 'graphql.queries' | 'graphql.mutations' | 'graphql.subscriptions' | 'graphql.directives' | 'graphql.objects' | 'graphql.interfaces' | 'graphql.unions' | 'graphql.enums' | 'graphql.inputs' | 'graphql.scalars' | 'graphql.arguments.label' | 'graphql.arguments.show' | 'graphql.arguments.hide' | 'graphql.arguments.here' | 'graphql.returnTypes.label' | 'graphql.returnTypes.show' | 'graphql.returnTypes.hide' | 'graphql.possibleTypes' | 'graphql.defaultValue' | 'graphql.deprecationReason' | 'graphql.implementedInterfaces' | 'graphql.nonNull' | 'graphql.required' | 'graphql.deprecated' | 'graphql.variables' | 'graphql.querySample' | 'graphql.mutationSample' | 'graphql.subscriptionSample' | 'graphql.responseSample' | 'graphql.locations' | 'graphql.sample' | 'graphql.referenced' | 'codeWalkthrough.download' | 'codeWalkthrough.preview' | 'time.justNow' | 'time.past.second' | 'time.past.seconds' | 'time.past.minute' | 'time.past.minutes' | 'time.past.hour' | 'time.past.hours' | 'time.past.day' | 'time.past.days' | 'time.past.week' | 'time.past.weeks' | 'time.past.month' | 'time.past.months' | 'time.past.year' | 'time.past.years';
|
|
3
3
|
export type Locale = {
|
|
4
4
|
code: string;
|
|
5
5
|
name: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/theme",
|
|
3
|
-
"version": "0.53.0
|
|
3
|
+
"version": "0.53.0",
|
|
4
4
|
"description": "Shared UI components lib",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"theme",
|
|
@@ -88,7 +88,6 @@
|
|
|
88
88
|
"nprogress": "0.2.0",
|
|
89
89
|
"react-calendar": "4.2.1",
|
|
90
90
|
"react-date-picker": "10.0.3",
|
|
91
|
-
"timeago.js": "4.0.2",
|
|
92
91
|
"@redocly/config": "0.24.1"
|
|
93
92
|
},
|
|
94
93
|
"scripts": {
|
|
@@ -1,22 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
|
-
import { format } from 'timeago.js';
|
|
4
3
|
|
|
5
|
-
import { useThemeHooks, useThemeConfig } from '@redocly/theme/core/hooks';
|
|
6
4
|
import { DEFAULT_LOCALE_PLACEHOLDER } from '@redocly/theme/core/constants';
|
|
5
|
+
import { useThemeHooks, useThemeConfig, useTimeAgo } from '@redocly/theme/core/hooks';
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
timeago: (date: Date, locale: string) => format(date, locale),
|
|
10
|
-
iso: (date: Date) => date.toISOString().split('T')[0],
|
|
11
|
-
short: (date: Date, locale: string) =>
|
|
12
|
-
date.toLocaleDateString(locale, { month: 'short', day: 'numeric', year: 'numeric' }),
|
|
13
|
-
long: (date: Date, locale: string) =>
|
|
14
|
-
date.toLocaleDateString(locale, { month: 'long', day: 'numeric', year: 'numeric' }),
|
|
15
|
-
};
|
|
7
|
+
type Formats = 'timeago' | 'iso' | 'short' | 'long';
|
|
16
8
|
|
|
17
9
|
export type LastUpdatedProps = {
|
|
18
10
|
lastModified: Date;
|
|
19
|
-
format?:
|
|
11
|
+
format?: Formats;
|
|
20
12
|
locale?: string;
|
|
21
13
|
className?: string;
|
|
22
14
|
};
|
|
@@ -24,9 +16,22 @@ export type LastUpdatedProps = {
|
|
|
24
16
|
export function LastUpdated(props: LastUpdatedProps): JSX.Element | null {
|
|
25
17
|
const { markdown: { lastUpdatedBlock = {} } = {} } = useThemeConfig();
|
|
26
18
|
const { useTranslate, useL10nConfig } = useThemeHooks();
|
|
19
|
+
const { format: timeagoFormat } = useTimeAgo();
|
|
27
20
|
const { translate } = useTranslate();
|
|
28
21
|
const { currentLocale } = useL10nConfig();
|
|
29
22
|
|
|
23
|
+
const formats: Record<Formats, (date: Date, locale: string) => string> = useMemo(
|
|
24
|
+
() => ({
|
|
25
|
+
timeago: (date: Date) => timeagoFormat(date),
|
|
26
|
+
iso: (date: Date) => date.toISOString().split('T')[0],
|
|
27
|
+
short: (date: Date, locale: string) =>
|
|
28
|
+
date.toLocaleDateString(locale, { month: 'short', day: 'numeric', year: 'numeric' }),
|
|
29
|
+
long: (date: Date, locale: string) =>
|
|
30
|
+
date.toLocaleDateString(locale, { month: 'long', day: 'numeric', year: 'numeric' }),
|
|
31
|
+
}),
|
|
32
|
+
[timeagoFormat],
|
|
33
|
+
);
|
|
34
|
+
|
|
30
35
|
if (lastUpdatedBlock?.hide) {
|
|
31
36
|
return null;
|
|
32
37
|
}
|
|
@@ -40,7 +45,7 @@ export function LastUpdated(props: LastUpdatedProps): JSX.Element | null {
|
|
|
40
45
|
|
|
41
46
|
const isoDate = lastModified.toISOString().split('T')[0];
|
|
42
47
|
|
|
43
|
-
const lastUpdatedString =
|
|
48
|
+
const lastUpdatedString = formats[format](lastModified, locale);
|
|
44
49
|
const translationKey = format === 'timeago' ? 'page.lastUpdated.timeago' : 'page.lastUpdated.on';
|
|
45
50
|
|
|
46
51
|
const text =
|
|
@@ -24,3 +24,4 @@ export * from '@redocly/theme/core/hooks/use-product-picker';
|
|
|
24
24
|
export * from '@redocly/theme/core/hooks/search/use-search-dialog';
|
|
25
25
|
export * from '@redocly/theme/core/hooks/use-language-picker';
|
|
26
26
|
export * from '@redocly/theme/core/hooks/__mocks__/use-element-size';
|
|
27
|
+
export * from '@redocly/theme/core/hooks/__mocks__/use-time-ago';
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -34,3 +34,4 @@ export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-walkthrough-c
|
|
|
34
34
|
export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-panel';
|
|
35
35
|
export * from '@redocly/theme/core/hooks/code-walkthrough/use-renderable-files';
|
|
36
36
|
export * from '@redocly/theme/core/hooks/use-element-size';
|
|
37
|
+
export * from '@redocly/theme/core/hooks/use-time-ago';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { Plural, TranslationKey } from '@redocly/theme/core/types';
|
|
4
|
+
|
|
5
|
+
import { useThemeHooks } from '@redocly/theme/core/hooks';
|
|
6
|
+
|
|
7
|
+
const timeUnits = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'] as const;
|
|
8
|
+
const timeUnitConversions = [
|
|
9
|
+
60, // 60 seconds in 1 min
|
|
10
|
+
60, // 60 mins in 1 hour
|
|
11
|
+
24, // 24 hours in 1 day
|
|
12
|
+
7, // 7 days in 1 week
|
|
13
|
+
365 / 7 / 12, // 4.345238095238096 weeks in 1 month
|
|
14
|
+
12, // 12 months in 1 year
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
type TimeUnit = (typeof timeUnits)[number];
|
|
18
|
+
|
|
19
|
+
export function useTimeAgo() {
|
|
20
|
+
const { useTranslate } = useThemeHooks();
|
|
21
|
+
const { translate } = useTranslate();
|
|
22
|
+
|
|
23
|
+
const format = useCallback(
|
|
24
|
+
(date: Date) => {
|
|
25
|
+
let timeUnitIndex = 0;
|
|
26
|
+
let elapsedTime = (Date.now() - date.getTime()) / 1000;
|
|
27
|
+
for (
|
|
28
|
+
;
|
|
29
|
+
elapsedTime >= timeUnitConversions[timeUnitIndex] &&
|
|
30
|
+
timeUnitIndex < timeUnitConversions.length;
|
|
31
|
+
timeUnitIndex++
|
|
32
|
+
) {
|
|
33
|
+
elapsedTime /= timeUnitConversions[timeUnitIndex];
|
|
34
|
+
}
|
|
35
|
+
elapsedTime = Math.floor(elapsedTime);
|
|
36
|
+
|
|
37
|
+
if (timeUnitIndex === 0) return translate('time.justNow', 'just now');
|
|
38
|
+
|
|
39
|
+
let timeUnit: TimeUnit | Plural<TimeUnit> = timeUnits[timeUnitIndex];
|
|
40
|
+
if (elapsedTime > 1) timeUnit = `${timeUnit}s`;
|
|
41
|
+
|
|
42
|
+
const translationKey: TranslationKey = `time.past.${timeUnit}`;
|
|
43
|
+
|
|
44
|
+
return translate(translationKey, {
|
|
45
|
+
defaultValue: `${elapsedTime} ${timeUnit} ago`,
|
|
46
|
+
count: elapsedTime,
|
|
47
|
+
replace: { value: elapsedTime },
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
[translate],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return { format };
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Plural<T extends string> = `${T}s`;
|
package/src/core/types/index.ts
CHANGED
package/src/core/types/l10n.ts
CHANGED
|
@@ -264,7 +264,22 @@ export type TranslationKey =
|
|
|
264
264
|
| 'graphql.sample'
|
|
265
265
|
| 'graphql.referenced'
|
|
266
266
|
| 'codeWalkthrough.download'
|
|
267
|
-
| 'codeWalkthrough.preview'
|
|
267
|
+
| 'codeWalkthrough.preview'
|
|
268
|
+
| 'time.justNow'
|
|
269
|
+
| 'time.past.second'
|
|
270
|
+
| 'time.past.seconds'
|
|
271
|
+
| 'time.past.minute'
|
|
272
|
+
| 'time.past.minutes'
|
|
273
|
+
| 'time.past.hour'
|
|
274
|
+
| 'time.past.hours'
|
|
275
|
+
| 'time.past.day'
|
|
276
|
+
| 'time.past.days'
|
|
277
|
+
| 'time.past.week'
|
|
278
|
+
| 'time.past.weeks'
|
|
279
|
+
| 'time.past.month'
|
|
280
|
+
| 'time.past.months'
|
|
281
|
+
| 'time.past.year'
|
|
282
|
+
| 'time.past.years';
|
|
268
283
|
|
|
269
284
|
export type Locale = { code: string; name: string };
|
|
270
285
|
|