diff --git a/packages/app-mobile/root.tsx b/packages/app-mobile/root.tsx index f29b80ff4..0e2c6b398 100644 --- a/packages/app-mobile/root.tsx +++ b/packages/app-mobile/root.tsx @@ -130,6 +130,7 @@ import initializeCommandService from './utils/initializeCommandService'; import PlatformImplementation from './plugins/PlatformImplementation'; import ShareManager from './components/screens/ShareManager'; import appDefaultState, { DEFAULT_ROUTE } from './utils/appDefaultState'; +import { setDateFormat, setTimeFormat, setTimeLocale } from '@joplin/utils/time'; type SideMenuPosition = 'left' | 'right'; @@ -186,10 +187,13 @@ const generalMiddleware = (store: any) => (next: any) => async (action: any) => if ((action.type === 'SETTING_UPDATE_ONE' && (action.key === 'dateFormat' || action.key === 'timeFormat')) || (action.type === 'SETTING_UPDATE_ALL')) { time.setDateFormat(Setting.value('dateFormat')); time.setTimeFormat(Setting.value('timeFormat')); + setDateFormat(Setting.value('dateFormat')); + setTimeFormat(Setting.value('timeFormat')); } if (action.type === 'SETTING_UPDATE_ONE' && action.key === 'locale' || action.type === 'SETTING_UPDATE_ALL') { setLocale(Setting.value('locale')); + setTimeLocale(Setting.value('locale')); } if ((action.type === 'SETTING_UPDATE_ONE' && (action.key.indexOf('encryption.') === 0)) || (action.type === 'SETTING_UPDATE_ALL')) { diff --git a/packages/lib/BaseApplication.ts b/packages/lib/BaseApplication.ts index 774341b12..4bbb667fd 100644 --- a/packages/lib/BaseApplication.ts +++ b/packages/lib/BaseApplication.ts @@ -21,6 +21,7 @@ import BaseItem from './models/BaseItem'; import Note from './models/Note'; import Tag from './models/Tag'; import { splitCommandString } from '@joplin/utils'; +import { setDateFormat, setTimeFormat, setTimeLocale } from '@joplin/utils/time'; import { reg } from './registry'; import time from './time'; import BaseSyncTarget from './BaseSyncTarget'; @@ -357,6 +358,9 @@ export default class BaseApplication { const sideEffects: any = { 'dateFormat': async () => { time.setLocale(Setting.value('locale')); + setTimeLocale(Setting.value('locale')); + setDateFormat(Setting.value('dateFormat')); + setTimeFormat(Setting.value('timeFormat')); time.setDateFormat(Setting.value('dateFormat')); time.setTimeFormat(Setting.value('timeFormat')); }, diff --git a/packages/lib/services/noteList/renderViewProps.ts b/packages/lib/services/noteList/renderViewProps.ts index e49edc4f5..d4199844d 100644 --- a/packages/lib/services/noteList/renderViewProps.ts +++ b/packages/lib/services/noteList/renderViewProps.ts @@ -1,5 +1,6 @@ import Logger from '@joplin/utils/Logger'; import time from '../../time'; +import { formatMsToRelative } from '@joplin/utils/time'; import { TagEntity } from '../database/types'; import { ListRendererDependency, RenderNoteView } from '../plugins/api/noteListType'; @@ -15,10 +16,10 @@ export interface RenderViewPropsOptions { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied const renderViewProp = (name: ListRendererDependency, value: any, options: RenderViewPropsOptions): string => { const renderers: Partial string>> = { - 'note.user_updated_time': () => time.formatMsToLocal(value), - 'note.user_created_time': () => time.formatMsToLocal(value), - 'note.updated_time': () => time.formatMsToLocal(value), - 'note.created_time': () => time.formatMsToLocal(value), + 'note.user_updated_time': () => formatMsToRelative(value), + 'note.user_created_time': () => formatMsToRelative(value), + 'note.updated_time': () => formatMsToRelative(value), + 'note.created_time': () => formatMsToRelative(value), 'note.todo_completed': () => value ? time.formatMsToLocal(value) : '', 'note.todo_due': () => value ? time.formatMsToLocal(value) : '', 'note.tags': () => value ? value.map((t: TagEntity) => t.title).join(', ') : '', diff --git a/packages/lib/time.ts b/packages/lib/time.ts index 6205713de..4e24f3bd5 100644 --- a/packages/lib/time.ts +++ b/packages/lib/time.ts @@ -1,3 +1,9 @@ +// ----------------------------------------------------------------------------------------------- +// !!IMPORTANT!! New time-related code should be added to @joplin/util/time and should be based on +// `dayjs` (which is part of `@joplin/util`). Eventually we'll migrate all code here to +// `@joplin/utils/time`. +// ----------------------------------------------------------------------------------------------- + import shim from './shim'; const moment = require('moment'); diff --git a/packages/utils/package.json b/packages/utils/package.json index d1048283d..b8f46a79f 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -34,6 +34,7 @@ "dependencies": { "@joplin/fork-htmlparser2": "^4.1.51", "async-mutex": "0.4.1", + "dayjs": "1.11.11", "execa": "5.1.1", "fs-extra": "11.2.0", "glob": "10.3.10", diff --git a/packages/utils/time.ts b/packages/utils/time.ts index 1abe5f1fe..c7b5a3756 100644 --- a/packages/utils/time.ts +++ b/packages/utils/time.ts @@ -1,3 +1,55 @@ +// ----------------------------------------------------------------------------------------------- +// NOTE: Some of the code in here is copied from @joplin/lib/time. New time-related code should be +// added here, and should be based on dayjs (not moment) +// ----------------------------------------------------------------------------------------------- + +import dayjs = require('dayjs'); +import dayJsRelativeTime = require('dayjs/plugin/relativeTime'); + +const supportedLocales: Record = { + 'ar': require('dayjs/locale/ar'), + 'bg': require('dayjs/locale/bg'), + 'bs': require('dayjs/locale/bs'), + 'ca': require('dayjs/locale/ca'), + 'cs': require('dayjs/locale/cs'), + 'da': require('dayjs/locale/da'), + 'de': require('dayjs/locale/de'), + 'el': require('dayjs/locale/el'), + 'en-gb': require('dayjs/locale/en-gb'), + 'en': require('dayjs/locale/en'), + 'eo': require('dayjs/locale/eo'), + 'es': require('dayjs/locale/es'), + 'et': require('dayjs/locale/et'), + 'eu': require('dayjs/locale/eu'), + 'fa': require('dayjs/locale/fa'), + 'fi': require('dayjs/locale/fi'), + 'fr': require('dayjs/locale/fr'), + 'gl': require('dayjs/locale/gl'), + 'hr': require('dayjs/locale/hr'), + 'hu': require('dayjs/locale/hu'), + 'id': require('dayjs/locale/id'), + 'it': require('dayjs/locale/it'), + 'ja': require('dayjs/locale/ja'), + 'ko': require('dayjs/locale/ko'), + 'nb': require('dayjs/locale/nb'), + 'nl-be': require('dayjs/locale/nl-be'), + 'nl': require('dayjs/locale/nl'), + 'pl': require('dayjs/locale/pl'), + 'pt-br': require('dayjs/locale/pt-br'), + 'pt': require('dayjs/locale/pt'), + 'ro': require('dayjs/locale/ro'), + 'ru': require('dayjs/locale/ru'), + 'sl': require('dayjs/locale/sl'), + 'sr': require('dayjs/locale/sr'), + 'sv': require('dayjs/locale/sv'), + 'th': require('dayjs/locale/th'), + 'tr': require('dayjs/locale/tr'), + 'uk': require('dayjs/locale/uk'), + 'vi': require('dayjs/locale/vi'), + 'zh-cn': require('dayjs/locale/zh-cn'), + 'zh-tw': require('dayjs/locale/zh-tw'), +}; + export const Second = 1000; export const Minute = 60 * Second; export const Hour = 60 * Minute; @@ -5,6 +57,15 @@ export const Day = 24 * Hour; export const Week = 7 * Day; export const Month = 30 * Day; +function initDayJs() { + dayjs.extend(dayJsRelativeTime); +} + +initDayJs(); + +let dateFormat_ = 'DD/MM/YYYY'; +let timeFormat_ = 'HH:mm'; + export const msleep = (ms: number) => { return new Promise(resolve => setTimeout(resolve, ms)); }; @@ -27,3 +88,48 @@ export function timerPop() { // eslint-disable-next-line no-console console.info(`Time: ${t.name}: ${Date.now() - t.startTime}`); } + +export const formatMsToRelative = (ms: number) => { + if (Date.now() - ms > 2 * Day) return formatMsToLocal(ms); + return dayjs(ms).fromNow(false); +}; + +const joplinLocaleToDayJsLocale = (locale: string) => { + locale = locale.toLowerCase().replace(/_/, '-'); + if (supportedLocales[locale]) return locale; + + const lang = locale.split('-')[0]; + if (supportedLocales[lang]) return lang; + + return 'en-gb'; +}; + +export const setTimeLocale = (locale: string) => { + const dayJsLocale = joplinLocaleToDayJsLocale(locale); + dayjs.locale(dayJsLocale); +}; + +export const setDateFormat = (format: string) => { + dateFormat_ = format; +}; + +export const setTimeFormat = (format: string) => { + timeFormat_ = format; +}; + +const dateFormat = () => { + return dateFormat_; +}; + +const timeFormat = () => { + return timeFormat_; +}; + +const dateTimeFormat = () => { + return `${dateFormat()} ${timeFormat()}`; +}; + +export const formatMsToLocal = (ms: number, format: string|null = null) => { + if (format === null) format = dateTimeFormat(); + return dayjs(ms).format(format); +}; diff --git a/yarn.lock b/yarn.lock index abafef0a2..7032582b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7240,6 +7240,7 @@ __metadata: "@types/markdown-it": 13.0.7 "@types/node-fetch": 2.6.11 async-mutex: 0.4.1 + dayjs: 1.11.11 execa: 5.1.1 fs-extra: 11.2.0 glob: 10.3.10 @@ -18327,6 +18328,13 @@ __metadata: languageName: node linkType: hard +"dayjs@npm:1.11.11": + version: 1.11.11 + resolution: "dayjs@npm:1.11.11" + checksum: 84788275aad8a87fee4f1ce4be08861df29687aae6b7b43dd65350118a37dda56772a3902f802cb2dc651dfed447a5a8df62d88f0fb900dba8333e411190a5d5 + languageName: node + linkType: hard + "dayjs@npm:^1.11.7": version: 1.11.9 resolution: "dayjs@npm:1.11.9"