Skip to content
Snippets Groups Projects
Verified Commit f8e9bcbb authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

feat(log): update the apearance of the log #270

parent 4ff01da7
No related branches found
No related tags found
No related merge requests found
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>update and optimize data-bind #270</title>
<script src="./270.mjs" type="module"></script>
</head>
<body>
<h1>update and optimize data-bind #270</h1>
<p></p>
<ul>
<li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/270">Issue #270</a></li>
<li><a href="/">Back to overview</a></li>
</ul>
<main>
<monster-log id="Bai3A">
</monster-log>
</main>
</body>
</html>
/**
* @file development/issues/open/270.mjs
* @url https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/270
* @description update and optimize data-bind
* @issue 270
*/
import "../../../source/components/style/property.pcss";
import "../../../source/components/style/link.pcss";
import "../../../source/components/style/color.pcss";
import "../../../source/components/style/theme.pcss";
import "../../../source/components/style/normalize.pcss";
import "../../../source/components/style/typography.pcss";
import "../../../source/components/state/log.mjs";
import {Entry} from "../../../source/components/state/log/entry.mjs";
const element = document.getElementById('Bai3A')
element.addEntry(new Entry({title: "Test", message: "Test", user: "Administrator", date: new Date()}))
const for10MinutesDate = new Date();
for10MinutesDate.setMinutes(for10MinutesDate.getMinutes() - 10);
element.addEntry(new Entry({title: "Test", message: "Test" , user: "Hans Meier", date: for10MinutesDate}))
const for1HourAnd16MinutesDate = new Date();
for1HourAnd16MinutesDate.setHours(for1HourAnd16MinutesDate.getHours() - 1);
for1HourAnd16MinutesDate.setMinutes(for1HourAnd16MinutesDate.getMinutes() - 16);
element.addEntry(new Entry({title: "Test", message: "Test", user: "Administrator", date: for1HourAnd16MinutesDate}))
const for10Days5HoursAnd30MinutesDate = new Date();
for10Days5HoursAnd30MinutesDate.setDate(for10Days5HoursAnd30MinutesDate.getDate() - 10);
for10Days5HoursAnd30MinutesDate.setHours(for10Days5HoursAnd30MinutesDate.getHours() - 5);
for10Days5HoursAnd30MinutesDate.setMinutes(for10Days5HoursAnd30MinutesDate.getMinutes() - 30);
element.addEntry(new Entry({title: "Test", message: "Test", user: "Fritz Müller", date: for10Days5HoursAnd30MinutesDate}))
const for1Year2MonthsAnd3DaysDate = new Date();
for1Year2MonthsAnd3DaysDate.setFullYear(for1Year2MonthsAnd3DaysDate.getFullYear() - 1);
for1Year2MonthsAnd3DaysDate.setMonth(for1Year2MonthsAnd3DaysDate.getMonth() - 2);
for1Year2MonthsAnd3DaysDate.setDate(for1Year2MonthsAnd3DaysDate.getDate() - 3);
element.addEntry(new Entry({title: "Test", message: "Test", user: "Administrator", date: for1Year2MonthsAnd3DaysDate}))
...@@ -81,6 +81,9 @@ const filterObserverSymbol = Symbol("filterObserver"); ...@@ -81,6 +81,9 @@ const filterObserverSymbol = Symbol("filterObserver");
* @copyright schukai GmbH * @copyright schukai GmbH
* @summary A rest api datasource * @summary A rest api datasource
*/ */
class Rest extends Datasource { class Rest extends Datasource {
/** /**
* the constructor of the class * the constructor of the class
...@@ -453,6 +456,13 @@ function initFilter() { ...@@ -453,6 +456,13 @@ function initFilter() {
filterControl.attachObserver(this[filterObserverSymbol]); filterControl.attachObserver(this[filterObserverSymbol]);
} }
/**
* @private
* @param json
* @param response
* @param filterControl
* @returns {Promise<never>|Promise<Awaited<unknown>>}
*/
function handleIntersectionObserver(json, response, filterControl) { function handleIntersectionObserver(json, response, filterControl) {
const path = new Pathfinder(json); const path = new Pathfinder(json);
...@@ -495,6 +505,9 @@ function initAutoInit() { ...@@ -495,6 +505,9 @@ function initAutoInit() {
}); });
} }
/**
* @private
*/
function initEventHandler() { function initEventHandler() {
this[intersectionObserverHandlerSymbol] = (entries) => { this[intersectionObserverHandlerSymbol] = (entries) => {
entries.forEach((entry) => { entries.forEach((entry) => {
...@@ -515,6 +528,9 @@ function initEventHandler() { ...@@ -515,6 +528,9 @@ function initEventHandler() {
}; };
} }
/**
* @private
*/
function initIntersectionObserver() { function initIntersectionObserver() {
this.classList.add("intersection-observer"); this.classList.add("intersection-observer");
...@@ -528,6 +544,7 @@ function initIntersectionObserver() { ...@@ -528,6 +544,7 @@ function initIntersectionObserver() {
this[intersectionObserverHandlerSymbol], this[intersectionObserverHandlerSymbol],
options, options,
); );
this[intersectionObserverObserverSymbol].observe(this); this[intersectionObserverObserverSymbol].observe(this);
} }
......
...@@ -21,7 +21,7 @@ import { ...@@ -21,7 +21,7 @@ import {
} from "../../dom/customelement.mjs"; } from "../../dom/customelement.mjs";
import { LogStyleSheet } from "./stylesheet/log.mjs"; import { LogStyleSheet } from "./stylesheet/log.mjs";
import { Entry } from "./log/entry.mjs"; import { Entry } from "./log/entry.mjs";
import { validateInstance } from "../../types/validate.mjs"; import {validateInstance, validateString} from "../../types/validate.mjs";
import "./state.mjs"; import "./state.mjs";
export { Log }; export { Log };
...@@ -39,14 +39,16 @@ const logElementSymbol = Symbol("logElement"); ...@@ -39,14 +39,16 @@ const logElementSymbol = Symbol("logElement");
const emptyStateElementSymbol = Symbol("emptyStateElement"); const emptyStateElementSymbol = Symbol("emptyStateElement");
/** /**
* A Log component * A log entry
* *
* @fragments /fragments/components/layout/collapse/ * @fragments /fragments/components/state/log
*
* @example /examples/components/state/log-simple
* *
* @since 3.74.0 * @since 3.74.0
* @copyright schukai GmbH * @copyright schukai GmbH
* @summary A Log component to show a log message. * @summary The log entry is a single entry in the log.
*/ **/
class Log extends CustomElement { class Log extends CustomElement {
/** /**
* @return {void} * @return {void}
...@@ -116,7 +118,7 @@ class Log extends CustomElement { ...@@ -116,7 +118,7 @@ class Log extends CustomElement {
/** /**
* Add an entry to the log * Add an entry to the log
* @param entry * @param {Entry} entry
* @return {Log} * @return {Log}
*/ */
addEntry(entry) { addEntry(entry) {
...@@ -133,15 +135,19 @@ class Log extends CustomElement { ...@@ -133,15 +135,19 @@ class Log extends CustomElement {
/** /**
* Add a log message * Add a log message
* @param message *
* @param date * @param {string} message
* @param {Date} date
* @return {Log} * @return {Log}
* @throws {TypeError} message is not a string
*/ */
addMessage(message, date) { addMessage(message, date) {
if (!date) { if (!date) {
date = new Date(); date = new Date();
} }
validateString(message);
this.addEntry( this.addEntry(
new Entry({ new Entry({
message: message, message: message,
...@@ -213,22 +219,19 @@ function initEventHandler() { ...@@ -213,22 +219,19 @@ function initEventHandler() {
*/ */
function getTemplate() { function getTemplate() {
// language=HTML // language=HTML
return ` return `<template id="entry">
<template id="entry">
<li data-monster-role="entry"> <li data-monster-role="entry">
<span></span> <span data-monster-replace="path:entry.user"
<div data-monster-replace="path:entry.title" data-monster-attributes="class path:entry.user | ?:user:hidden"></span>
data-monster-attributes="class path:entry.title | ?:title:hidden"></div> <span data-monster-replace="path:entry.title"
<div data-monster-replace="path:entry.message" data-monster-attributes="class path:entry.title | ?:title:hidden"></span>
data-monster-attributes="class path:entry.message | ?:message:hidden"></div> <span data-monster-replace="path:entry.message"
<div data-monster-replace="path:entry.user" data-monster-attributes="class path:entry.message | ?:message:hidden"></span>
data-monster-attributes="class path:entry.user | ?:user:hidden"></div> <span data-monster-replace="path:entry.date | time-ago" data-monster-attributes="title path:entry.date | datetime"></span>
<div data-monster-attributes="class path:entry.date | is-set | ?:datetime:hidden">
<span data-monster-replace="path:entry.date | date"></span>
<span data-monster-replace="path:entry.date | time"></span>
</div>
</li> </li>
</template> </template>
<div part="control" data-monster-role="control"> <div part="control" data-monster-role="control">
<div data-monster-role="empty-state" data-monster-attributes="class path:entries | has-entries | ?:hidden:"> <div data-monster-role="empty-state" data-monster-attributes="class path:entries | has-entries | ?:hidden:">
<monster-state> <monster-state>
...@@ -240,7 +243,7 @@ function getTemplate() { ...@@ -240,7 +243,7 @@ function getTemplate() {
<path d="m256 314.925781c-2.628906 0-5.210938 1.066407-7.070312 2.929688-1.859376 1.867187-2.929688 4.4375-2.929688 7.070312 0 2.636719 1.070312 5.207031 2.929688 7.078125 1.859374 1.859375 4.441406 2.921875 7.070312 2.921875s5.210938-1.0625 7.070312-2.921875c1.859376-1.871094 2.929688-4.441406 2.929688-7.078125 0-2.632812-1.070312-5.203125-2.929688-7.070312-1.859374-1.863281-4.441406-2.929688-7.070312-2.929688zm0 0"/> <path d="m256 314.925781c-2.628906 0-5.210938 1.066407-7.070312 2.929688-1.859376 1.867187-2.929688 4.4375-2.929688 7.070312 0 2.636719 1.070312 5.207031 2.929688 7.078125 1.859374 1.859375 4.441406 2.921875 7.070312 2.921875s5.210938-1.0625 7.070312-2.921875c1.859376-1.871094 2.929688-4.441406 2.929688-7.078125 0-2.632812-1.070312-5.203125-2.929688-7.070312-1.859374-1.863281-4.441406-2.929688-7.070312-2.929688zm0 0"/>
</svg> </svg>
</div> </div>
<div part="content" monster-replace="path:labels.nothingToReport"> <div part="content" data-monster-replace="path:labels.nothingToReport">
There is nothing to report yet. There is nothing to report yet.
</div> </div>
</monster-state> </monster-state>
......
...@@ -66,10 +66,10 @@ class Entry extends Base { ...@@ -66,10 +66,10 @@ class Entry extends Base {
*/ */
get internalDefaults() { get internalDefaults() {
return { return {
title: undefined, title: null,
message: undefined, message: null,
user: undefined, user: null,
date: undefined, date: null,
}; };
} }
......
...@@ -8,21 +8,16 @@ ...@@ -8,21 +8,16 @@
@import "../../style/mixin/hover.pcss"; @import "../../style/mixin/hover.pcss";
[data-monster-role=entries] { [data-monster-role=entries] {
overflow: hidden;
padding: 10px 0 40px 60px;
& ul { & ul {
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
padding: 0; padding: 0 0 0 1.8rem;
position: relative; position: relative;
top: 0 top: 0
} }
& ul:last-of-type {
top: 2rem;
}
& ul:before { & ul:before {
content: ""; content: "";
display: block; display: block;
...@@ -31,81 +26,37 @@ ...@@ -31,81 +26,37 @@
border: 1px dashed var(--monster-color-primary-2); border: 1px dashed var(--monster-color-primary-2);
position: absolute; position: absolute;
top: 0; top: 0;
left: 30px left: 1rem;
}
.title {
font-weight: bold;
} }
& ul li { & ul li {
margin: 20px 60px 60px; margin: 0;
position: relative; position: relative;
padding: 10px 20px; padding: 0.1rem 0.3rem;
color: var(--monster-color-primary-2); color: var(--monster-color-primary-1);
background-color: var(--monster-bg-color-primary-2); background-color: var(--monster-bg-color-primary-1);
border-radius: 5px; border-radius: 5px;
} }
& ul li > span { & ul li:before {
content: "";
display: block;
width: 0;
height: 100%;
border: 1px solid var(--monster-color-primary-2);
position: absolute;
top: 0;
left: -30px
}
& ul li > span:before, & ul li > span:after {
content: ""; content: "";
display: block; display: block;
width: 10px; width: 5px;
height: 10px; height: 5px;
border-radius: 50%; border-radius: 50%;
background: var(--monster-bg-color-primary-2); background: var(--monster-bg-color-primary-3);
border: 2px solid var(--monster-color-primary-2); border: 1px solid var(--monster-color-primary-2);
position: absolute;
left: -7.5px
}
& ul li > span:before {
top: -10px
}
& ul li > span:after {
top: 95%
}
& .title {
text-transform: uppercase;
margin-bottom: 5px
}
& .message:first-letter {
}
& .user {
margin-top: 10px;
font-style: italic;
text-align: right;
margin-right: 20px;
font-size: 0.8rem;
}
& .datetime span {
position: absolute; position: absolute;
left: -100px; left: -0.9rem;
color: var(--monster-color-primary-1); top: 0.6rem;
background-color: var(--monster-bg-color-primary-1);
font-size: 0.5rem;
} }
& .datetime span:first-child {
top: -16px
}
& .datetime span:last-child {
top: 94%
}
} }
\ No newline at end of file
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
import {addAttributeToken} from "../../../dom/attributes.mjs"; import {addAttributeToken} from "../../../dom/attributes.mjs";
import {ATTRIBUTE_ERRORMESSAGE} from "../../../dom/constants.mjs"; import {ATTRIBUTE_ERRORMESSAGE} from "../../../dom/constants.mjs";
export { LogStyleSheet }; export {LogStyleSheet}
/** /**
* @private * @private
...@@ -22,17 +22,10 @@ export { LogStyleSheet }; ...@@ -22,17 +22,10 @@ export { LogStyleSheet };
const LogStyleSheet = new CSSStyleSheet(); const LogStyleSheet = new CSSStyleSheet();
try { try {
LogStyleSheet.insertRule( LogStyleSheet.insertRule(`
`
@layer log { @layer log {
[data-monster-role=control]{box-sizing:border-box;outline:none;width:100%}[data-monster-role=control].flex{align-items:center;display:flex;flex-direction:row}:host{box-sizing:border-box;display:block}.block{display:block}.inline{display:inline}.inline-block{display:inline-block}.grid{display:grid}.inline-grid{display:inline-grid}.flex{display:flex}.inline-flex{display:inline-flex}.hidden,.hide,.none{display:none}.visible{visibility:visible}.invisible{visibility:hidden}.monster-border-primary-1,.monster-border-primary-2,.monster-border-primary-3,.monster-border-primary-4{border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width)}.monster-border-0{border-radius:0;border-style:none;border-width:0}.monster-border-primary-1{border-color:var(--monster-bg-color-primary-1)}.monster-border-primary-2{border-color:var(--monster-bg-color-primary-2)}.monster-border-primary-3{border-color:var(--monster-bg-color-primary-3)}.monster-border-primary-4{border-color:var(--monster-bg-color-primary-4)}.monster-border-secondary-1,.monster-border-secondary-2,.monster-border-secondary-3,.monster-border-secondary-4{border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width)}.monster-border-secondary-1{border-color:var(--monster-bg-color-secondary-1)}.monster-border-secondary-2{border-color:var(--monster-bg-color-secondary-2)}.monster-border-secondary-3{border-color:var(--monster-bg-color-secondary-3)}.monster-border-secondary-4{border-color:var(--monster-bg-color-secondary-4)}.monster-border-tertiary-1,.monster-border-tertiary-2,.monster-border-tertiary-3,.monster-border-tertiary-4{border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width)}.monster-border-tertiary-1{border-color:var(--monster-bg-color-tertiary-1)}.monster-border-tertiary-2{border-color:var(--monster-bg-color-tertiary-2)}.monster-border-tertiary-3{border-color:var(--monster-bg-color-tertiary-3)}.monster-border-tertiary-4{border-color:var(--monster-bg-color-tertiary-4)}[data-monster-role=entries]{overflow:hidden;padding:10px 0 40px 60px}[data-monster-role=entries] ul{list-style-type:none;margin:0;padding:0;position:relative;top:0}[data-monster-role=entries] ul:last-of-type{top:2rem}[data-monster-role=entries] ul:before{border:1px dashed var(--monster-color-primary-2);content:\"\";display:block;height:100%;left:30px;position:absolute;top:0;width:0}[data-monster-role=entries] ul li{background-color:var(--monster-bg-color-primary-2);border-radius:5px;color:var(--monster-color-primary-2);margin:20px 60px 60px;padding:10px 20px;position:relative}[data-monster-role=entries] ul li>span{border:1px solid var(--monster-color-primary-2);content:\"\";display:block;height:100%;left:-30px;position:absolute;top:0;width:0}[data-monster-role=entries] ul li>span:after,[data-monster-role=entries] ul li>span:before{background:var(--monster-bg-color-primary-2);border:2px solid var(--monster-color-primary-2);border-radius:50%;content:\"\";display:block;height:10px;left:-7.5px;position:absolute;width:10px}[data-monster-role=entries] ul li>span:before{top:-10px}[data-monster-role=entries] ul li>span:after{top:95%}[data-monster-role=entries] .title{margin-bottom:5px;text-transform:uppercase}[data-monster-role=entries] .user{font-size:.8rem;font-style:italic;margin-right:20px;margin-top:10px;text-align:right}[data-monster-role=entries] .datetime span{background-color:var(--monster-bg-color-primary-1);color:var(--monster-color-primary-1);font-size:.5rem;left:-100px;position:absolute}[data-monster-role=entries] .datetime span:first-child{top:-16px}[data-monster-role=entries] .datetime span:last-child{top:94%} [data-monster-role=control]{box-sizing:border-box;outline:none;width:100%}[data-monster-role=control].flex{align-items:center;display:flex;flex-direction:row}:host{box-sizing:border-box;display:block}.block{display:block}.inline{display:inline}.inline-block{display:inline-block}.grid{display:grid}.inline-grid{display:inline-grid}.flex{display:flex}.inline-flex{display:inline-flex}.hidden,.hide,.none{display:none}.visible{visibility:visible}.invisible{visibility:hidden}.monster-border-primary-1,.monster-border-primary-2,.monster-border-primary-3,.monster-border-primary-4{border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width)}.monster-border-0{border-radius:0;border-style:none;border-width:0}.monster-border-primary-1{border-color:var(--monster-bg-color-primary-1)}.monster-border-primary-2{border-color:var(--monster-bg-color-primary-2)}.monster-border-primary-3{border-color:var(--monster-bg-color-primary-3)}.monster-border-primary-4{border-color:var(--monster-bg-color-primary-4)}.monster-border-secondary-1,.monster-border-secondary-2,.monster-border-secondary-3,.monster-border-secondary-4{border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width)}.monster-border-secondary-1{border-color:var(--monster-bg-color-secondary-1)}.monster-border-secondary-2{border-color:var(--monster-bg-color-secondary-2)}.monster-border-secondary-3{border-color:var(--monster-bg-color-secondary-3)}.monster-border-secondary-4{border-color:var(--monster-bg-color-secondary-4)}.monster-border-tertiary-1,.monster-border-tertiary-2,.monster-border-tertiary-3,.monster-border-tertiary-4{border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width)}.monster-border-tertiary-1{border-color:var(--monster-bg-color-tertiary-1)}.monster-border-tertiary-2{border-color:var(--monster-bg-color-tertiary-2)}.monster-border-tertiary-3{border-color:var(--monster-bg-color-tertiary-3)}.monster-border-tertiary-4{border-color:var(--monster-bg-color-tertiary-4)}[data-monster-role=entries] ul{list-style-type:none;margin:0;padding:0 0 0 1.8rem;position:relative;top:0}[data-monster-role=entries] ul:before{border:1px dashed var(--monster-color-primary-2);content:\"\";display:block;height:100%;left:1rem;position:absolute;top:0;width:0}[data-monster-role=entries] .title{font-weight:700}[data-monster-role=entries] ul li{background-color:var(--monster-bg-color-primary-1);border-radius:5px;color:var(--monster-color-primary-1);margin:0;padding:.1rem .3rem;position:relative}[data-monster-role=entries] ul li:before{background:var(--monster-bg-color-primary-3);border:1px solid var(--monster-color-primary-2);border-radius:50%;content:\"\";display:block;height:5px;left:-.9rem;position:absolute;top:.6rem;width:5px}
}`, }`, 0);
0,
);
} catch (e) { } catch (e) {
addAttributeToken( addAttributeToken(document.getRootNode().querySelector('html'), ATTRIBUTE_ERRORMESSAGE, e + "");
document.getRootNode().querySelector("html"),
ATTRIBUTE_ERRORMESSAGE,
e + "",
);
} }
...@@ -31,6 +31,7 @@ import { ...@@ -31,6 +31,7 @@ import {
} from "../types/validate.mjs"; } from "../types/validate.mjs";
import {clone} from "../util/clone.mjs"; import {clone} from "../util/clone.mjs";
import {Pathfinder} from "./pathfinder.mjs"; import {Pathfinder} from "./pathfinder.mjs";
import {formatTimeAgo} from "../i18n/time-ago.mjs";
export {Transformer}; export {Transformer};
...@@ -723,6 +724,19 @@ function transform(value) { ...@@ -723,6 +724,19 @@ function transform(value) {
throw new Error(`unsupported locale or missing format (${e.message})`); throw new Error(`unsupported locale or missing format (${e.message})`);
} }
case "time-ago":
date = new Date(value);
if (isNaN(date.getTime())) {
throw new Error("invalid date");
}
try {
locale = getLocaleOfDocument();
return formatTimeAgo(date, locale.toString());
} catch (e) {
throw new Error(`unsupported locale or missing format (${e.message})`);
}
case "year": case "year":
date = new Date(value); date = new Date(value);
if (isNaN(date.getTime())) { if (isNaN(date.getTime())) {
...@@ -877,3 +891,6 @@ function evaluateCondition(value) { ...@@ -877,3 +891,6 @@ function evaluateCondition(value) {
value === true value === true
); );
} }
/**
* Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
* Node module: @schukai/monster
*
* This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
* The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
*
* For those who do not wish to adhere to the AGPLv3, a commercial license is available.
* Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
* For more information about purchasing a commercial license, please contact schukai GmbH.
*
* SPDX-License-Identifier: AGPL-3.0
*/
export {formatTimeAgo};
/**
* Format a date as a relative time string with multiple units.
*
* @param date
* @param locale
* @returns {string}
*/
function formatTimeAgo(date, locale) {
const now = new Date(Math.floor(new Date().getTime() / 1000) * 1000);
const roundedDate = new Date(Math.floor(date.getTime() / 1000) * 1000);
const diffInSeconds = Math.floor((roundedDate - now) / 1000);
const absDiff = Math.abs(diffInSeconds);
const translation = translations[normalizeLocale(locale)];
if (absDiff === 0) {
return translation.template.justNow();
}
if (absDiff > 31536000) {
return translation.template.on(new Intl.DateTimeFormat(locale, {
dateStyle: 'full',
timeStyle: 'short'
}).format(date));
}
const units = [
{name: 'year', length: 31536000}, // Hinzugefügt für Konsistenz, auch wenn nicht genutzt in dieser spezifischen Logik
{name: 'day', length: 86400},
{name: 'hour', length: 3600},
{name: 'minute', length: 60},
{name: 'second', length: 1},
];
let remainder = absDiff;
const parts = [];
for (const unit of units) {
if (remainder >= unit.length) {
const count = Math.floor(remainder / unit.length);
remainder %= unit.length;
const formatted = formatUnitCount(count, unit.name, locale);
parts.push(formatted);
}
}
if (parts.length === 0) {
return translation.template.justNow();
}
const joined = new Intl.ListFormat(locale, {style: 'long', type: 'conjunction'}).format(parts);
if (diffInSeconds > 0) {
return translation.template.future(joined);
} else {
return translation.template.past(joined);
}
}
function formatUnitCount(count, unit, locale) {
const lang = normalizeLocale(locale);
const fallback = translations[lang] || translations['en'];
const dictForUnit = fallback.units[unit];
const pr = new Intl.PluralRules(lang);
const category = pr.select(count);
const phrase = dictForUnit[category] || dictForUnit.other;
return `${count} ${phrase}`;
}
function normalizeLocale(locale) {
return locale.split('-')[0].toLowerCase();
}
/**
* @private
*/
const translations = {
en: {
template: {
on: (value) => `on ${value}`,
past: (value) => `${value} ago`,
future: (value) => `in ${value}`,
justNow: () => `just now`,
},
units: {
day: {zero: 'days', one: 'day', two: 'days', few: 'days', many: 'days', other: 'days'},
hour: {zero: 'hours', one: 'hour', two: 'hours', few: 'hours', many: 'hours', other: 'hours'},
minute: {zero: 'minutes', one: 'minute', two: 'minutes', few: 'minutes', many: 'minutes', other: 'minutes'},
second: {zero: 'seconds', one: 'second', two: 'seconds', few: 'seconds', many: 'seconds', other: 'seconds'},
}
},
zh: {
template: {
on: (value) => `在${value}`,
past: (value) => `${value}前`,
future: (value) => `${value}后`,
justNow: () => `刚刚`,
},
units: {
day: {zero: '', one: '', two: '', few: '', many: '', other: ''},
hour: {zero: '小时', one: '小时', two: '小时', few: '小时', many: '小时', other: '小时'},
minute: {zero: '分钟', one: '分钟', two: '分钟', few: '分钟', many: '分钟', other: '分钟'},
second: {zero: '', one: '', two: '', few: '', many: '', other: ''},
}
},
es: {
template: {
on: (value) => `el ${value}`,
past: (value) => `hace ${value}`,
future: (value) => `en ${value}`,
justNow: () => `justo ahora`,
},
units: {
day: {zero: 'días', one: 'día', two: 'días', few: 'días', many: 'días', other: 'días'},
hour: {zero: 'horas', one: 'hora', two: 'horas', few: 'horas', many: 'horas', other: 'horas'},
minute: {zero: 'minutos', one: 'minuto', two: 'minutos', few: 'minutos', many: 'minutos', other: 'minutos'},
second: {
zero: 'segundos',
one: 'segundo',
two: 'segundos',
few: 'segundos',
many: 'segundos',
other: 'segundos'
},
}
},
de: {
template: {
on: (value) => `am ${value}`,
past: (value) => `vor ${value}`,
future: (value) => `in ${value}`,
justNow: () => `gerade eben`,
},
units: {
day: {zero: 'Tagen', one: 'Tag', two: 'Tagen', few: 'Tagen', many: 'Tagen', other: 'Tagen'},
hour: {zero: 'Stunden', one: 'Stunde', two: 'Stunden', few: 'Stunden', many: 'Stunden', other: 'Stunden'},
minute: {zero: 'Minuten', one: 'Minute', two: 'Minuten', few: 'Minuten', many: 'Minuten', other: 'Minuten'},
second: {
zero: 'Sekunden',
one: 'Sekunde',
two: 'Sekunden',
few: 'Sekunden',
many: 'Sekunden',
other: 'Sekunden'
},
}
},
fr: {
template: {
on: (value) => `le ${value}`,
past: (value) => `il y a ${value}`,
future: (value) => `dans ${value}`,
justNow: () => `à l'instant`,
},
units: {
day: {zero: 'jours', one: 'jour', two: 'jours', few: 'jours', many: 'jours', other: 'jours'},
hour: {zero: 'heures', one: 'heure', two: 'heures', few: 'heures', many: 'heures', other: 'heures'},
minute: {zero: 'minutes', one: 'minute', two: 'minutes', few: 'minutes', many: 'minutes', other: 'minutes'},
second: {
zero: 'secondes',
one: 'seconde',
two: 'secondes',
few: 'secondes',
many: 'secondes',
other: 'secondes'
},
}
},
ru: {
template: {
on: (value) => `на ${value}`,
past: (value) => `${value} назад`,
future: (value) => `через ${value}`,
justNow: () => `только что`,
},
units: {
day: {zero: 'дней', one: 'день', two: 'дня', few: 'дня', many: 'дней', other: 'дней'},
hour: {zero: 'часов', one: 'час', two: 'часа', few: 'часа', many: 'часов', other: 'часов'},
minute: {zero: 'минут', one: 'минута', two: 'минуты', few: 'минуты', many: 'минут', other: 'минут'},
second: {zero: 'секунд', one: 'секунда', two: 'секунды', few: 'секунды', many: 'секунд', other: 'секунд'},
}
},
ar: {
template: {
on: (value) => `في ${value}`,
past: (value) => `منذ ${value}`,
future: (value) => `بعد ${value}`,
justNow: () => `الآن`,
},
units: {
day: {zero: 'أيام', one: 'يوم', two: 'يومين', few: 'أيام', many: 'أيام', other: 'أيام'},
hour: {zero: 'ساعات', one: 'ساعة', two: 'ساعتين', few: 'ساعات', many: 'ساعات', other: 'ساعات'},
minute: {zero: 'دقائق', one: 'دقيقة', two: 'دقيقتين', few: 'دقائق', many: 'دقائق', other: 'دقائق'},
second: {zero: 'ثواني', one: 'ثانية', two: 'ثانيتين', few: 'ثواني', many: 'ثواني', other: 'ثواني'},
}
},
hi: {
template: {
on: (value) => `${value} को`,
past: (value) => `${value} पहले`,
future: (value) => `${value} में`,
justNow: () => `अभी`,
},
units: {
day: {zero: 'दिन', one: 'दिन', two: 'दिन', few: 'दिन', many: 'दिन', other: 'दिन'},
hour: {zero: 'घंटे', one: 'घंटा', two: 'घंटे', few: 'घंटे', many: 'घंटे', other: 'घंटे'},
minute: {zero: 'मिनट', one: 'मिनट', two: 'मिनट', few: 'मिनट', many: 'मिनट', other: 'मिनट'},
second: {zero: 'सेकंड', one: 'सेकंड', two: 'सेकंड', few: 'सेकंड', many: 'सेकंड', other: 'सेकंड'},
}
},
pt: {
template: {
on: (value) => `em ${value}`,
past: (value) => `há ${value}`,
future: (value) => `em ${value}`,
justNow: () => `agora mesmo`,
},
units: {
day: {zero: 'dias', one: 'dia', two: 'dias', few: 'dias', many: 'dias', other: 'dias'},
hour: {zero: 'horas', one: 'hora', two: 'horas', few: 'horas', many: 'horas', other: 'horas'},
minute: {zero: 'minutos', one: 'minuto', two: 'minutos', few: 'minutos', many: 'minutos', other: 'minutos'},
second: {
zero: 'segundos',
one: 'segundo',
two: 'segundos',
few: 'segundos',
many: 'segundos',
other: 'segundos'
},
}
},
ja: {
template: {
on: (value) => `${value}に`,
past: (value) => `${value}前`,
future: (value) => `${value}後`,
justNow: () => `たった今`,
},
units: {
day: {zero: '', one: '', two: '', few: '', many: '', other: ''},
hour: {zero: '時間', one: '時間', two: '時間', few: '時間', many: '時間', other: '時間'},
minute: {zero: '', one: '', two: '', few: '', many: '', other: ''},
second: {zero: '', one: '', two: '', few: '', many: '', other: ''},
}
},
it: {
template: {
on: (value) => `il ${value}`,
past: (value) => `${value} fa`,
future: (value) => `in ${value}`,
justNow: () => `proprio ora`,
},
units: {
day: {zero: 'giorni', one: 'giorno', two: 'giorni', few: 'giorni', many: 'giorni', other: 'giorni'},
hour: {zero: 'ore', one: 'ora', two: 'ore', few: 'ore', many: 'ore', other: 'ore'},
minute: {zero: 'minuti', one: 'minuto', two: 'minuti', few: 'minuti', many: 'minuti', other: 'minuti'},
second: {
zero: 'secondi',
one: 'secondo',
two: 'secondi',
few: 'secondi',
many: 'secondi',
other: 'secondi'
},
}
},
ko: {
template: {
on: (value) => `${value}에`,
past: (value) => `${value} 전`,
future: (value) => `${value} 후`,
justNow: () => `방금`,
},
units: {
day: {zero: '', one: '', two: '', few: '', many: '', other: ''},
hour: {zero: '시간', one: '시간', two: '시간', few: '시간', many: '시간', other: '시간'},
minute: {zero: '', one: '', two: '', few: '', many: '', other: ''},
second: {zero: '', one: '', two: '', few: '', many: '', other: ''},
}
},
sw: {
template: {
on: (value) => `kwa ${value}`,
past: (value) => `${value} iliyopita`,
future: (value) => `katika ${value}`,
justNow: () => `hivi punde`,
},
units: {
day: {zero: 'siku', one: 'siku', two: 'siku', few: 'siku', many: 'siku', other: 'siku'},
hour: {zero: 'masaa', one: 'saa', two: 'masaa', few: 'masaa', many: 'masaa', other: 'masaa'},
minute: {zero: 'dakika', one: 'dakika', two: 'dakika', few: 'dakika', many: 'dakika', other: 'dakika'},
second: {
zero: 'sekunde',
one: 'sekunde',
two: 'sekunde',
few: 'sekunde',
many: 'sekunde',
other: 'sekunde'
},
}
},
nl: {
template: {
on: (value) => `op ${value}`,
past: (value) => `${value} geleden`,
future: (value) => `over ${value}`,
justNow: () => `net`,
},
units: {
day: {zero: 'dagen', one: 'dag', two: 'dagen', few: 'dagen', many: 'dagen', other: 'dagen'},
hour: {zero: 'uren', one: 'uur', two: 'uren', few: 'uren', many: 'uren', other: 'uren'},
minute: {zero: 'minuten', one: 'minuut', two: 'minuten', few: 'minuten', many: 'minuten', other: 'minuten'},
second: {
zero: 'seconden',
one: 'seconde',
two: 'seconden',
few: 'seconden',
many: 'seconden',
other: 'seconden'
},
}
},
ms: {
template: {
on: (value) => `pada ${value}`,
past: (value) => `${value} yang lalu`,
future: (value) => `${value} lagi`,
justNow: () => `tadi`,
},
units: {
day: {zero: 'hari', one: 'hari', two: 'hari', few: 'hari', many: 'hari', other: 'hari'},
hour: {zero: 'jam', one: 'jam', two: 'jam', few: 'jam', many: 'jam', other: 'jam'},
minute: {zero: 'minit', one: 'minit', two: 'minit', few: 'minit', many: 'minit', other: 'minit'},
second: {zero: 'saat', one: 'saat', two: 'saat', few: 'saat', many: 'saat', other: 'saat'},
}
},
id: {
template: {
on: (value) => `pada ${value}`,
past: (value) => `${value} yang lalu`,
future: (value) => `dalam ${value}`,
justNow: () => `tadi`,
},
units: {
day: {zero: 'hari', one: 'hari', two: 'hari', few: 'hari', many: 'hari', other: 'hari'},
hour: {zero: 'jam', one: 'jam', two: 'jam', few: 'jam', many: 'jam', other: 'jam'},
minute: {zero: 'menit', one: 'menit', two: 'menit', few: 'menit', many: 'menit', other: 'menit'},
second: {zero: 'detik', one: 'detik', two: 'detik', few: 'detik', many: 'detik', other: 'detik'},
}
},
tr: {
template: {
on: (value) => `${value} tarihinde`,
past: (value) => `${value} önce`,
future: (value) => `${value} sonra`,
justNow: () => `az önce`,
},
units: {
day: {zero: 'günler', one: 'gün', two: 'gün', few: 'gün', many: 'gün', other: 'gün'},
hour: {zero: 'saatler', one: 'saat', two: 'saat', few: 'saat', many: 'saat', other: 'saat'},
minute: {zero: 'dakikalar', one: 'dakika', two: 'dakika', few: 'dakika', many: 'dakika', other: 'dakika'},
second: {zero: 'saniyeler', one: 'saniye', two: 'saniye', few: 'saniye', many: 'saniye', other: 'saniye'},
}
},
pl: {
template: {
on: (value) => `w dniu ${value}`,
past: (value) => `${value} temu`,
future: (value) => `za ${value}`,
justNow: () => `przed chwilą`,
},
units: {
day: {zero: 'dni', one: 'dzień', two: 'dni', few: 'dni', many: 'dni', other: 'dni'},
hour: {zero: 'godzin', one: 'godzina', two: 'godziny', few: 'godziny', many: 'godzin', other: 'godzin'},
minute: {zero: 'minut', one: 'minuta', two: 'minuty', few: 'minuty', many: 'minut', other: 'minut'},
second: {zero: 'sekund', one: 'sekunda', two: 'sekundy', few: 'sekundy', many: 'sekund', other: 'sekund'},
}
},
sv: {
template: {
on: (value) => `den ${value}`,
past: (value) => `för ${value} sedan`,
future: (value) => `om ${value}`,
justNow: () => `nyss`,
},
units: {
day: {zero: 'dagar', one: 'dag', two: 'dagar', few: 'dagar', many: 'dagar', other: 'dagar'},
hour: {zero: 'timmar', one: 'timme', two: 'timmar', few: 'timmar', many: 'timmar', other: 'timmar'},
minute: {zero: 'minuter', one: 'minut', two: 'minuter', few: 'minuter', many: 'minuter', other: 'minuter'},
second: {
zero: 'sekunder',
one: 'sekund',
two: 'sekunder',
few: 'sekunder',
many: 'sekunder',
other: 'sekunder'
},
}
},
ro: {
template: {
on: (value) => `pe ${value}`,
past: (value) => `acum ${value}`,
future: (value) => `peste ${value}`,
justNow: () => `chiar acum`,
},
units: {
day: {zero: 'zile', one: 'zi', two: 'zile', few: 'zile', many: 'zile', other: 'zile'},
hour: {zero: 'ore', one: 'oră', two: 'ore', few: 'ore', many: 'ore', other: 'ore'},
minute: {zero: 'minute', one: 'minut', two: 'minute', few: 'minute', many: 'minute', other: 'minute'},
second: {
zero: 'secunde',
one: 'secundă',
two: 'secunde',
few: 'secunde',
many: 'secunde',
other: 'secunde'
},
}
},
bg: {
template: {
on: (value) => `на ${value}`,
past: (value) => `преди ${value}`,
future: (value) => `след ${value}`,
justNow: () => `току-що`,
},
units: {
day: {zero: 'дни', one: 'ден', two: 'дни', few: 'дни', many: 'дни', other: 'дни'},
hour: {zero: 'часа', one: 'час', two: 'часа', few: 'часа', many: 'часа', other: 'часа'},
minute: {zero: 'минути', one: 'минута', two: 'минути', few: 'минути', many: 'минути', other: 'минути'},
second: {
zero: 'секунди',
one: 'секунда',
two: 'секунди',
few: 'секунди',
many: 'секунди',
other: 'секунди'
},
}
},
da: {
template: {
on: (value) => `den ${value}`,
past: (value) => `for ${value} siden`,
future: (value) => `om ${value}`,
justNow: () => `lige nu`,
},
units: {
day: {zero: 'dage', one: 'dag', two: 'dage', few: 'dage', many: 'dage', other: 'dage'},
hour: {zero: 'timer', one: 'time', two: 'timer', few: 'timer', many: 'timer', other: 'timer'},
minute: {
zero: 'minutter',
one: 'minut',
two: 'minutter',
few: 'minutter',
many: 'minutter',
other: 'minutter'
},
second: {
zero: 'sekunder',
one: 'sekund',
two: 'sekunder',
few: 'sekunder',
many: 'sekunder',
other: 'sekunder'
},
}
},
fi: {
template: {
on: (value) => `päivänä ${value}`,
past: (value) => `${value} sitten`,
future: (value) => `${value} kuluttua`,
justNow: () => `juuri nyt`,
},
units: {
day: {zero: 'päivää', one: 'päivä', two: 'päivää', few: 'päivää', many: 'päivää', other: 'päivää'},
hour: {zero: 'tuntia', one: 'tunti', two: 'tuntia', few: 'tuntia', many: 'tuntia', other: 'tuntia'},
minute: {
zero: 'minuuttia',
one: 'minuutti',
two: 'minuuttia',
few: 'minuuttia',
many: 'minuuttia',
other: 'minuuttia'
},
second: {
zero: 'sekuntia',
one: 'sekunti',
two: 'sekuntia',
few: 'sekuntia',
many: 'sekuntia',
other: 'sekuntia'
},
}
},
cs: {
template: {
on: (value) => `dne ${value}`,
past: (value) => `před ${value}`,
future: (value) => `za ${value}`,
justNow: () => `právě teď`,
},
units: {
day: {zero: 'dny', one: 'den', two: 'dny', few: 'dny', many: 'dnů', other: 'dny'},
hour: {zero: 'hodin', one: 'hodina', two: 'hodiny', few: 'hodiny', many: 'hodin', other: 'hodin'},
minute: {zero: 'minut', one: 'minuta', two: 'minuty', few: 'minuty', many: 'minut', other: 'minut'},
second: {zero: 'sekund', one: 'sekunda', two: 'sekundy', few: 'sekundy', many: 'sekund', other: 'sekund'},
}
},
el: {
template: {
on: (value) => `στις ${value}`,
past: (value) => `πριν ${value}`,
future: (value) => `σε ${value}`,
justNow: () => `μόλις τώρα`,
},
units: {
day: {zero: 'ημέρες', one: 'ημέρα', two: 'ημέρες', few: 'ημέρες', many: 'ημέρες', other: 'ημέρες'},
hour: {zero: 'ώρες', one: 'ώρα', two: 'ώρες', few: 'ώρες', many: 'ώρες', other: 'ώρες'},
minute: {zero: 'λεπτά', one: 'λεπτό', two: 'λεπτά', few: 'λεπτά', many: 'λεπτά', other: 'λεπτά'},
second: {
zero: 'δευτερόλεπτα',
one: 'δευτερόλεπτο',
two: 'δευτερόλεπτα',
few: 'δευτερόλεπτα',
many: 'δευτερόλεπτα',
other: 'δευτερόλεπτα'
},
}
},
hr: {
template: {
on: (value) => `na ${value}`,
past: (value) => `prije ${value}`,
future: (value) => `za ${value}`,
justNow: () => `prije trenutka`,
},
units: {
day: {zero: 'dana', one: 'dan', two: 'dana', few: 'dana', many: 'dana', other: 'dana'},
hour: {zero: 'sati', one: 'sat', two: 'sata', few: 'sata', many: 'sati', other: 'sati'},
minute: {zero: 'minuta', one: 'minuta', two: 'minute', few: 'minute', many: 'minuta', other: 'minuta'},
second: {
zero: 'sekundi',
one: 'sekunda',
two: 'sekunde',
few: 'sekunde',
many: 'sekundi',
other: 'sekundi'
},
}
},
sk: {
template: {
on: (value) => `v deň ${value}`,
past: (value) => `pred ${value}`,
future: (value) => `za ${value}`,
justNow: () => `práve teraz`,
},
units: {
day: {zero: 'dni', one: 'deň', two: 'dni', few: 'dni', many: 'dní', other: 'dni'},
hour: {zero: 'hodiny', one: 'hodina', two: 'hodiny', few: 'hodiny', many: 'hodín', other: 'hodiny'},
minute: {zero: 'minúty', one: 'minúta', two: 'minúty', few: 'minúty', many: 'minút', other: 'minúty'},
second: {zero: 'sekundy', one: 'sekunda', two: 'sekundy', few: 'sekundy', many: 'sekúnd', other: 'sekundy'},
}
},
no: {
template: {
on: (value) => `på ${value}`,
past: (value) => `${value} siden`,
future: (value) => `om ${value}`,
justNow: () => `nettopp`,
},
units: {
day: {zero: 'dager', one: 'dag', two: 'dager', few: 'dager', many: 'dager', other: 'dager'},
hour: {zero: 'timer', one: 'time', two: 'timer', few: 'timer', many: 'timer', other: 'timer'},
minute: {
zero: 'minutter',
one: 'minutt',
two: 'minutter',
few: 'minutter',
many: 'minutter',
other: 'minutter'
},
second: {
zero: 'sekunder',
one: 'sekund',
two: 'sekunder',
few: 'sekunder',
many: 'sekunder',
other: 'sekunder'
},
}
},
sl: {
template: {
on: (value) => `na ${value}`,
past: (value) => `pred ${value}`,
future: (value) => `čez ${value}`,
justNow: () => `ravno zdaj`,
},
units: {
day: {zero: 'dni', one: 'dan', two: 'dni', few: 'dni', many: 'dni', other: 'dni'},
hour: {zero: 'ure', one: 'ura', two: 'ure', few: 'ure', many: 'ure', other: 'ure'},
minute: {zero: 'minute', one: 'minuta', two: 'minute', few: 'minute', many: 'minute', other: 'minute'},
second: {zero: 'sekunde', one: 'sekunda', two: 'sekunde', few: 'sekunde', many: 'sekund', other: 'sekund'},
}
},
vi: {
template: {
on: (value) => `vào ${value}`,
past: (value) => `${value} trước`,
future: (value) => `${value} sau`,
justNow: () => `vừa mới`,
},
units: {
day: {zero: 'ngày', one: 'ngày', two: 'ngày', few: 'ngày', many: 'ngày', other: 'ngày'},
hour: {zero: 'giờ', one: 'giờ', two: 'giờ', few: 'giờ', many: 'giờ', other: 'giờ'},
minute: {zero: 'phút', one: 'phút', two: 'phút', few: 'phút', many: 'phút', other: 'phút'},
second: {zero: 'giây', one: 'giây', two: 'giây', few: 'giây', many: 'giây', other: 'giây'},
}
},
};
\ No newline at end of file
import {expect} from "chai"
import {formatTimeAgo} from "../../../source/i18n/time-ago.mjs";
describe('formatTimeAgo', () => {
it('returns "just now" for times less than a second ago', () => {
const now = new Date();
expect(formatTimeAgo(now, 'en')).to.equal('just now');
});
it('returns "in 1 second" for one second in the future', () => {
const oneSecondFuture = new Date(Date.now() + 1000);
expect(formatTimeAgo(oneSecondFuture, 'en')).to.equal('in 1 second');
});
it('returns "1 second ago" for one second in the past', () => {
const oneSecondPast = new Date(Date.now() - 1000);
expect(formatTimeAgo(oneSecondPast, 'en')).to.equal('1 second ago');
});
it('returns "in 1 minute, 30 seconds" for 90 seconds in the future', () => {
const ninetySecondsFuture = new Date(Date.now() + 90000);
expect(formatTimeAgo(ninetySecondsFuture, 'en')).to.equal('in 1 minute and 30 seconds');
});
it('returns "1 minute, 30 seconds ago" for 90 seconds in the past', () => {
const ninetySecondsPast = new Date(Date.now() - 90000);
expect(formatTimeAgo(ninetySecondsPast, 'en')).to.equal('1 minute and 30 seconds ago');
});
// Weitere Tests können hinzugefügt werden
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment