├── .eslintrc.json ├── .github ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── coverage-badge.yml │ ├── docs.yml │ └── main.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── .vitepress │ └── config.ts ├── contributing │ └── environment.md ├── guide │ ├── basic-usage.md │ ├── configuration.md │ └── getting-started.md ├── hall-of-fame │ └── contributors.md └── index.md ├── examples ├── .gitignore ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── App.vue │ ├── env.d.ts │ └── main.ts ├── tsconfig.json └── vite.config.ts ├── package-lock.json ├── package.json ├── src ├── converter │ └── defaultConverter.ts ├── install.ts └── timeago.ts ├── tests └── unit │ ├── defaultConverter.spec.ts │ ├── install.spec.ts │ └── timeago.spec.ts ├── tsconfig.json └── vite.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "vue-eslint-parser", 3 | "parserOptions": { 4 | "parser": "@typescript-eslint/parser", 5 | "ecmaVersion": "latest", 6 | "sourceType": "module" 7 | }, 8 | "plugins": ["vue", "@typescript-eslint"], 9 | "extends": [ 10 | "eslint:recommended", 11 | "plugin:vue/vue3-recommended", 12 | "plugin:@typescript-eslint/recommended" 13 | ], 14 | "globals": { 15 | "defineEmits": "readonly", 16 | "defineProps": "readonly", 17 | "withDefaults": "readonly" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '24 11 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.github/workflows/coverage-badge.yml: -------------------------------------------------------------------------------- 1 | name: Generate CodeCoverage Badge 2 | on: 3 | push: 4 | branches: [ master ] 5 | jobs: 6 | checks: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Setup NodeJS 11 | uses: actions/setup-node@v4 12 | with: 13 | node-version: "22" 14 | cache: "npm" 15 | - name: Install dependencies 16 | run: npm install 17 | - name: Run unit tests with coverage 18 | run: | 19 | npm run coverage 20 | STATEMENTS=$(cat coverage/coverage-summary.json | jq -r '.total.statements.pct') 21 | echo "COVERAGE=$(echo ${STATEMENTS}%)" >> $GITHUB_ENV 22 | - name: Create Coverage Badge 23 | uses: schneegans/dynamic-badges-action@v1.7.0 24 | with: 25 | auth: ${{ secrets.GIST_SECRET }} 26 | gistID: 51a8c6c4f125bd6ec25a14a6f12e28bc 27 | filename: vue-timeago3_coverage.json 28 | label: Test Coverage 29 | message: ${{ env.COVERAGE }} 30 | namedLogo: vitest 31 | color: brightgreen 32 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Generate and Deploy Docs 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | generate: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Setup NodeJS 11 | uses: actions/setup-node@v4 12 | with: 13 | node-version: "22" 14 | cache: "npm" 15 | - name: Install dependencies 16 | run: npm install 17 | - name: Generate static files 18 | run: npm run docs:build 19 | - name: Commit files 20 | run: | 21 | cd docs/.vitepress/dist 22 | git init 23 | git add -A 24 | git config --local user.email "action@github.com" 25 | git config --local user.name "GitHub Action" 26 | git commit -m 'deploy' 27 | - name: Push to GitHub pages 28 | uses: ad-m/github-push-action@v0.6.0 29 | with: 30 | github_token: ${{ secrets.GITHUB_TOKEN }} 31 | branch: docs 32 | force: true 33 | directory: ./docs/.vitepress/dist 34 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Lint, Test, Build 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Setup Node 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: "22" 17 | cache: "npm" 18 | - name: Install dependencies 19 | run: npm install 20 | - name: Run ESLint 21 | run: npm run lint 22 | - name: Run Vue TSC 23 | run: npx vue-tsc --noEmit 24 | test: 25 | needs: lint 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: Setup NodeJS 30 | uses: actions/setup-node@v4 31 | with: 32 | node-version: "22" 33 | cache: "npm" 34 | - name: Install dependencies 35 | run: npm install 36 | - name: Run Unit Tests 37 | run: npm run test 38 | build: 39 | needs: test 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v4 43 | - name: Setup NodeJS 44 | uses: actions/setup-node@v4 45 | with: 46 | node-version: "22" 47 | cache: "npm" 48 | - name: Install dependencies 49 | run: npm install 50 | - name: Generate definitions 51 | run: npx vue-tsc --declaration --emitDeclarationOnly 52 | - name: Build Library 53 | run: npx vite build 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist_types 5 | dist-ssr 6 | *.local 7 | /vue-timeago3-0.1.2.tgz 8 | coverage 9 | .npmrc -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # [2.3.0](https://github.com/MrDeerly/vue-timeago3/compare/v2.2.1...v2.3.0) (2022-06-11) 4 | 5 | 6 | ### Features 7 | 8 | * add type checking for strict converterOptions ([#262](https://github.com/MrDeerly/vue-timeago3/issues/262)) ([de6e9f7](https://github.com/MrDeerly/vue-timeago3/commit/de6e9f7808509584e6496cd90026070d6e711afa)) 9 | 10 | # [2.2.0](https://github.com/MrDeerly/vue-timeago3/compare/v2.1.1...v2.2.0) (2022-04-30) 11 | 12 | 13 | ### Features 14 | 15 | * add strict option for defaultConverter ([#231](https://github.com/MrDeerly/vue-timeago3/issues/231)) ([6d8097a](https://github.com/MrDeerly/vue-timeago3/commit/6d8097ab5a0d8101f719e31930c22b9870cea3f7)) 16 | 17 | ## [2.1.1](https://github.com/MrDeerly/vue-timeago3/compare/v2.1.0...v2.1.1) (2021-12-03) 18 | 19 | 20 | 21 | # [2.1.0](https://github.com/MrDeerly/vue-timeago3/compare/v2.1.0...v2.1.1) (2021-11-30) 22 | 23 | 24 | ### Features 25 | 26 | * add locale support ([01308ad](https://github.com/MrDeerly/vue-timeago3/commit/01308adab8313f3e5ddec4ea8e5e5c3c25d676fd)) 27 | 28 | 29 | 30 | # [2.0.0](https://github.com/MrDeerly/vue-timeago3/compare/v2.1.0...v2.1.1) (2021-11-29) 31 | 32 | 33 | ### Bug Fixes 34 | 35 | * add vue shims ([06c21cc](https://github.com/MrDeerly/vue-timeago3/commit/06c21ccf7580d1f3e65c241f1ad78fc5fbc82c61)) 36 | 37 | 38 | ### Reverts 39 | 40 | * Revert "initial release" ([bdebcc1](https://github.com/MrDeerly/vue-timeago3/commit/bdebcc17c9d7b8dcd92359235864db41405bfe09)) 41 | 42 | 43 | 44 | ## [1.0.1](https://github.com/MrDeerly/vue-timeago3/compare/v2.1.0...v2.1.1) (2021-09-18) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 MrDeerly 4 | Copyright (c) 2021 Sasha Koss and Lesha Koss https://kossnocorp.mit-license.org 5 | Copyright (c) EGOIST <0x142857@gmail.com> (github.com/egoist) 6 | 7 | 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⏳ vue-timeago3 2 | [![NPM version](https://img.shields.io/npm/v/vue-timeago3.svg?label=Version&color=brightgreen&logo=npm)](https://npmjs.com/package/vue-timeago3) [![NPM downloads](https://img.shields.io/npm/dm/vue-timeago3.svg?label=Downloads&logo=npm)](https://npmjs.com/package/vue-timeago3) ![code-coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/0x0dr1y/51a8c6c4f125bd6ec25a14a6f12e28bc/raw/vue-timeago3_coverage.json) [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2F0x0dr1y%2Fvue-timeago3%2Fbadge%3Fref%3Dmaster&style=flat)](https://actions-badge.atrox.dev/0x0dr1y/vue-timeago3/goto?ref=master) ![Vue version](https://img.shields.io/badge/vue-3.2.6-blue.svg) ![date-fns version](https://img.shields.io/npm/dependency-version/vue-timeago3/date-fns?color=blue) ![size](https://img.shields.io/bundlephobia/min/vue-timeago3) 3 | 4 | A time ago component for Vue.js 3 based on [vue-timeago for Vue 2 by egoist](https://github.com/egoist/vue-timeago). 5 | 6 | ## Table of Contents 7 | 8 | - [About](#sectionAbout) 9 | - [Usage](#sectionUsage) 10 | - [Installation](#sectionInstall) 11 | - [Register Plugin](#sectionRegister) 12 | - [Component](#sectionComponent) 13 | 14 | 15 | 16 | ## About 17 | 18 | **vue-timeago3** is a tiny component for Vue.js 3, to show the time passed since a specific date. You simply pass a date and get somewhat like `10 seconds ago`, `3 weeks ago`, `...` printed by the component 19 | 20 | ### Example 21 | 22 | | distance to now | result | 23 | | --------------- | ------------------------ | 24 | | 0 - 5 secs | less than 5 seconds ago | 25 | | 5 - 10 secs | less than 10 seconds ago | 26 | | 10 - 20 secs | less than 20 seconds ago | 27 | | 20 - 40 secs | half a minute ago | 28 | | 40 - 60 secs | less than a minute ago | 29 | 30 | See [date-fns/formatDistanceToNow](https://date-fns.org/v2.26.0/docs/formatDistanceToNow) for more details. 31 | 32 | 33 | 34 | ## Usage 35 | 36 | **Visit the [docs](https://0x0dr1y.github.io/vue-timeago3/) for more details!** 37 | 38 | 39 | 40 | ### Installation 41 | 42 | Currently the plugin is available via NPM and Yarn. To install it use one of the two package managers. 43 | 44 | ```javascript 45 | // NPM 46 | $ npm install vue-timeago3 47 | 48 | // Yarn 49 | $ yarn add vue-timeago3 50 | ``` 51 | 52 | 53 | 54 | ### Register Plugin 55 | 56 | To register the plugin simply import and register it using the new global vue3 api. As an alternative the plugin could be imported in specific components only. 57 | 58 | ```javascript 59 | // src/main.ts 60 | import { createApp } from 'vue' 61 | import timeago from 'vue-timeago3' 62 | 63 | const app = createApp(App) 64 | ... 65 | app.use(timeago) // register timeago 66 | ... 67 | app.mount('#app') 68 | ``` 69 | 70 | #### Plugin Options 71 | 72 | During the registration of the component you can specify a set of options, which will mutate the plugin **globally**. If you don't want to define global settings, skip this section and use props instead. To use options, simply pass them during the registration as an object: 73 | 74 | ```javascript 75 | // src/main.ts 76 | import { createApp } from 'vue' 77 | import timeago from 'vue-timeago3' 78 | 79 | const app = createApp(App) 80 | ... 81 | // define options 82 | const timeagoOptions = { 83 | converterOptions: { 84 | includeSeconds: false, 85 | } 86 | } 87 | 88 | app.use(timeago, timeagoOptions) // register timeago with options 89 | ... 90 | app.mount('#app') 91 | ``` 92 | 93 | As of version 1.0.0 the following options are available: 94 | 95 | | option | type | description | 96 | |-----------------------------|----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 97 | | **name** | `string` | Register the component with a custom name. Default is: `timeago` | 98 | | **locale** | `Locale` (see [date-fns/Locale](https://date-fns.org/v2.26.0/docs/Locale)) | The `locale` specifies the language which is used to render the date. All available `date-fns` locales are supported by default. | 99 | | **converter** | `(date, defaultConvertOptions \| strictConverterOptions) => string` | A **converter** that formats regular dates in `x Seconds ago`, or in `xxx` style. Check out the [default converter](ahttps://github.com/0x0dr1y/vue-timeago3/blob/master/src/defaultConverter.js) which uses [date-fns formatDistanceToNow](https://date-fns.org/v2.24.0/docs/formatDistanceToNow) | 100 | | **defaultConverterOptions** | `Object` | Pass some extra settings to the default converter mentioned above. It supports the main options of `date-fns`, namingly:

`includeSeconds` - `boolean` - distances less than a minute are more detailed
`addSuffix` - `boolean` - results specifies if now is earlier or later than the date passed
`useStrict` - `false` - **if `true` you need to use the `strictConverterOptions` (see below)** | 101 | | **strictConverterOptions** | `Object` | Pass some extra settings to the default converter mentioned above. It supports the main options of `date-fns` strict converter, namingly:

`useStrict` - `true` - needs to be `true`, otherwise the `defaultConverterOptions` have to be used (see above)
`addSuffix` - `boolean` - results specifies if now is earlier or later than the date passed
`unit` - `second, minute, hour, day, month, year` if specified, will force a unit
`roundingMethod` - `floor, ceil, round` which way to round partial units (default=round) | 102 | 103 |
104 | 105 | ### Component 106 | 107 | Once the plugin is registered you can straight up use it in your app. 108 | 109 | **Basic usage**: 110 | 111 | ```jsx 112 | 115 | 116 | This can be omitted by setting it to `0` or `false`.

The default value for `true` is `60`(seconds). Instead of passing `true` you can also pass a custom time. | 134 | | **locale** | `Locale` (see [date-fns/Locale](https://date-fns.org/v2.26.0/docs/Locale)) | :x: | `en` | The `locale` specifies the language which is used to render the date. All available `date-fns` locales are supported by default. | 135 | | **converter** | `date, defaultConverterOptions \| strictConverterOptions) => string` | :x: | | See plugin options above | 136 | | **defaultConverterOptions** | `Object` | :x: | | See plugin options above | 137 | | **strictConverterOptions** | `Object` | :x: | | See plugin options above | 138 | -------------------------------------------------------------------------------- /docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "⏳ vue-timeago3", 3 | description: 4 | "vue-timeago3 is a tiny component for Vue.js 3, to show the time passed since a specific date. You simply pass a date and get somewhat like 10 seconds ago, 3 weeks ago, ... printed by the component", 5 | sidebar: "auto", 6 | base: "/vue-timeago3/", 7 | 8 | themeConfig: { 9 | repo: "mrdeerly/vue-timeago3", 10 | docsDir: "docs", 11 | docsBranch: "master", 12 | editLinks: true, 13 | editLinkText: "Edit this page on GitHub", 14 | lastUpdated: "Last Updated", 15 | nav: [ 16 | { 17 | text: "Guide", 18 | link: "/guide/getting-started", 19 | activeMatch: "^/guide/getting-started", 20 | }, 21 | { 22 | text: "Config Reference", 23 | link: "/guide/configuration", 24 | activeMatch: "^/guide/configuration", 25 | }, 26 | ], 27 | 28 | sidebar: { 29 | "/guide/getting-started": getGuideSidebar(), 30 | "/": getGuideSidebar(), 31 | }, 32 | }, 33 | }; 34 | 35 | function getGuideSidebar() { 36 | return [ 37 | { 38 | text: "Home", 39 | link: "/", 40 | }, 41 | { 42 | text: "Introduction", 43 | children: [ 44 | { text: "Getting Started", link: "/guide/getting-started" }, 45 | { text: "Basic Usage", link: "/guide/basic-usage" }, 46 | { text: "Configuration", link: "/guide/configuration" }, 47 | ], 48 | }, 49 | { 50 | text: "Contributing", 51 | children: [ 52 | { text: "Dev Environment", link: "/contributing/environment" }, 53 | ], 54 | }, 55 | { 56 | text: "Hall of fame", 57 | children: [{ text: "Contributors", link: "hall-of-fame/contributors" }], 58 | }, 59 | ]; 60 | } 61 | -------------------------------------------------------------------------------- /docs/contributing/environment.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | - **Step 1:** Clone Repository 4 | ```bash 5 | git clone https://github.com/MrDeerly/vue-timeago3.git 6 | ``` 7 | - **Step 2:** Make your changes 8 | 9 | All files relevant files can be found in `./src` 10 | 11 | ```bash 12 | cd /src 13 | ``` 14 | 15 | - **Step 3:** Check your changes 16 | 17 | The project contains a simple example project which is located in `./examples`. To check your changes you can simply execute the following npm script in the **root** directory. 18 | 19 | ```bash 20 | npm run dev 21 | ``` 22 | 23 | This will compile your current files, install them in the example App, and start a dev server for the example app 24 | 25 | - **Step 4:** Repeat :) 26 | 27 | - **Step 5:** Submit PR 28 | 29 | Congratulations, it's time to submit a Pull Request so your changes can be reviewed and merged. Please explain your changes, and why they are neccessary. 30 | -------------------------------------------------------------------------------- /docs/guide/basic-usage.md: -------------------------------------------------------------------------------- 1 | # Basic Usage 2 | 3 | Once the plugin is registered you can straight up use it in your app. 4 | 5 | ```vue{2,10} 6 | 9 | 10 | 21 | ``` 22 | 23 | This will print something like `now` or `5 seconds ago`. 24 | 25 | By configuring the component, you can adjust a few more things like, auto updating for example. Please have a look at the next page. 26 | 27 | ## Custom language 28 | 29 | By default, vue-timeago3 uses date-fns under the hood. This means that over 80 languages can be used. To do so, you can simply import any of the `date-fns` language packs, and pass it down to vue-timeago. 30 | 31 | All available locales can be found [here](https://github.com/date-fns/date-fns/tree/master/src/locale)! 32 | 33 | ```vue{2-5,10,14} 34 | 40 | 41 | 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/guide/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | ::: tip 4 | The component can either be configured globally, or on component level. 5 | To avoid conflicts and enhance the adaptability it's suggested to configure `vue-timeago3` on component level. 6 | ::: 7 | 8 | ## Component options (local) 9 | 10 | As usual the component is configured using properties. 11 | 12 | ### Example 13 | 14 | ```vue{2-10,15,19} 15 | 27 | 28 | 40 | ``` 41 | 42 | ### Properties 43 | 44 | The following props are available and can be used: 45 | 46 | #### datetime 47 | 48 | > The `datetime` is used to compute the "timeago" as a word. It reflects a timestamp and can be either a `string`, `number` or a `Date`. Since it's a required property, the component won't work without it. 49 | 50 | - **type:** `string | number | Date` 51 | - **required:** :heavy_check_mark: 52 | 53 | --- 54 | 55 | #### autoUpdate 56 | 57 | > `autoUpdate` specifies the period of time to update the component, **in seconds**. This can be omitted by setting it to `0` or `false`. Instead of passing `true` to activate it, you can also pass a custom interval by passing a `number`. 58 | 59 | - **type:** `number | boolean` 60 | - **required:** :x: 61 | - **default:** `false` 62 | 63 | --- 64 | 65 | #### locale 66 | 67 | > The `locale` specifies the language which is used to render the date. All available `date-fns` locales are supported by default. 68 | 69 | - **type:** `Locale` (see [date-fns/Locale](https://date-fns.org/v2.26.0/docs/Locale)) 70 | - **required:** :x: 71 | - **default:** `en` 72 | 73 | --- 74 | 75 | #### converter 76 | 77 | > A **converter** that formats regular dates in `x Seconds ago`, or in `xxx` style. Check out the [default converter](https://github.com/MrDeerly/vue-timeago3/blob/master/src/converter/defaultConverter.ts) which uses [date-fns formatDistanceToNow](https://date-fns.org/v2.24.0/docs/formatDistanceToNow) 78 | 79 | - **type:** `(date, defaultConverterOptions | strictConverterOptions) => string` 80 | - **required:** :x: 81 | - **default:** `null` 82 | 83 | --- 84 | 85 | #### defaultConverterOptions 86 | 87 | > The defaultConverterOptions allow you to adjust the default converter' configuration. The default supports the main options of `date-fns`, naming: 88 | > 89 | > - `includeSeconds` - `boolean | undefined` - distances less than a minute are more detailed 90 | > - `addSuffix` - `boolean | undefined` - results specifies if now is earlier or later than the date passed 91 | > - `useStrict` - `false | undefined` - **if `true` you need to use the `strictConverterOptions` (see below)** 92 | 93 | - **type:** `Record` 94 | - **required:** :x: 95 | - **default:** `{}` 96 | 97 | --- 98 | 99 | #### strictConverterOptions 100 | 101 | > The strictConverterOptions allow you to use the defaultConverter in strict mode. The strict conversion supports the main options of `date-fns`, naming: 102 | > 103 | > - `useStrict` - `true` - enables strict converting 104 | > - `addSuffix` - `boolean | undefined` - results specifies if now is earlier or later than the date passed 105 | > - `unit` - `"second" | "minute" | "hour" | "day" | "month" | "year" | undefined` - defines which unit is used for the conversion 106 | > - `roundingMethod` - `"floor" | "ceil" | "round" | undefined` - defines which method is used to round the time 107 | - **type:** `Record` 108 | - **required:** :x: 109 | - **default:** `{}` 110 | 111 | ## Plugin options (global) 112 | 113 | Instead of configuring vue-timeago3 on component level, it can also be configured on a global level while registering the component. 114 | 115 | ### Example 116 | 117 | ```js{4,9-13,15} 118 | // src/main.ts 119 | import App from './App' 120 | import { createApp } from 'vue' 121 | import timeago from 'vue-timeago3' // import timeago 122 | import { es } from 'date-fns/locale' // import custom locale 123 | 124 | 125 | const app = createApp(App); 126 | ... 127 | // define options 128 | const timeagoOptions = { 129 | converterOptions: { 130 | includeSeconds: false, 131 | }, 132 | locale: es, 133 | } 134 | 135 | app.use(timeago, timeagoOptions) // register timeago with options 136 | ... 137 | app.mount('#app') 138 | ``` 139 | 140 | ### Options 141 | 142 | :::warning 143 | If both global and component options are used, the component options will be higher prioritized and used. 144 | ::: 145 | 146 | #### name 147 | 148 | > The `name` option allows you to customize the components name. Please keep in mind that with changing this value, you need to use the component with the specified name. 149 | 150 | - **type:** `string` 151 | - **required:** :x: 152 | - **default:** `Timeago` 153 | 154 | --- 155 | 156 | #### locale 157 | 158 | > The `locale` specifies the language which is used to render the date. All available `date-fns` locales are supported by default. 159 | 160 | - **type:** `Locale` (see [date-fns/Locale](https://date-fns.org/v2.26.0/docs/Locale)) 161 | - **required:** :x: 162 | - **default:** `en` 163 | 164 | --- 165 | 166 | #### converter 167 | 168 | > A **converter** that formats regular dates in `x Seconds ago`, or in `xxx` style. Check out the [default converter](https://github.com/MrDeerly/vue-timeago3/blob/master/src/converter/defaultConverter.ts) which uses [date-fns formatDistanceToNow](https://date-fns.org/v2.24.0/docs/formatDistanceToNow) 169 | 170 | - **type:** `(date, defaultConverterOptions | strictConverterOptions) => string` 171 | - **required:** :x: 172 | - **default:** `null` 173 | 174 | --- 175 | 176 | #### defaultConverterOptions 177 | 178 | > The defaultConverterOptions allow you to adjust the default converter' configuration. The default supports the main options of `date-fns`, naming: 179 | > 180 | > - `includeSeconds` - `boolean | undefined` - distances less than a minute are more detailed 181 | > - `addSuffix` - `boolean | undefined` - results specifies if now is earlier or later than the date passed 182 | > - `useStrict` - `false | undefined` - **if `true` you need to use the `strictConverterOptions` (see below)** 183 | 184 | - **type:** `Record` 185 | - **required:** :x: 186 | - **default:** `{}` 187 | 188 | --- 189 | 190 | #### strictConverterOptions 191 | 192 | > The strictConverterOptions allow you to use the defaultConverter in strict mode. The strict conversion supports the main options of `date-fns`, naming: 193 | > 194 | > - `useStrict` - `true` - enables strict converting 195 | > - `addSuffix` - `boolean | undefined` - results specifies if now is earlier or later than the date passed 196 | > - `unit` - `"second" | "minute" | "hour" | "day" | "month" | "year" | undefined` - defines which unit is used for the conversion 197 | > - `roundingMethod` - `"floor" | "ceil" | "round" | undefined` - defines which method is used to round the time 198 | - **type:** `Record` 199 | - **required:** :x: 200 | - **default:** `{}` 201 | -------------------------------------------------------------------------------- /docs/guide/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | This section will help you with the basic installment of vue-timeago3. 4 | 5 | - **Step. 1:** Install `vue-timeago3` 6 | 7 | Currently, the package is available via NPM and Yarn. To install it use one of the two package managers. 8 | 9 | **NPM** 10 | 11 | ```bash 12 | npm install vue-timeago3 13 | ``` 14 | 15 | **Yarn** 16 | 17 | ```bash 18 | yarn add vue-timeago3 19 | ``` 20 | 21 | - **Step. 2:** Register Plugin 22 | 23 | To register the plugin simply import and register it using the new global vue3 api. As an alternative the plugin could be imported in specific components only. 24 | 25 | ```js{4,8} 26 | // src/main.ts 27 | import App from './App' 28 | import { createApp } from 'vue' 29 | import timeago from 'vue-timeago3' 30 | 31 | const app = createApp(App) 32 | ... 33 | app.use(timeago) // register timeago 34 | ... 35 | app.mount('#app') 36 | ``` 37 | 38 | - **Step. 3:** Enjoy! 39 | 40 | Congratulations, you've successfully installed `vue-timeago3`. You can now start using the component, or configure it on a global level. 41 | -------------------------------------------------------------------------------- /docs/hall-of-fame/contributors.md: -------------------------------------------------------------------------------- 1 | ## Cui honorem, honorem! 2 | 3 | > _"honor to whom honor is due"_ 4 | 5 | ### egoist/vue-timeago 6 | 7 | This project is heavily inspired by [egoist](https://github.com/egoist)'s [vue-timeago](https://github.com/egoist/vue-timeago), a timeago plugin for Vue.js v2. Sadly he isn't maintaining the project anymore. So.. here we are! :) 8 | 9 | ### date-fns 10 | 11 | Having the `defaultConverter` based on [date-fns](https://github.com/date-fns/date-fns) basically means that the whole library is based on date-fns. So cheers to you guys! Thanks for your awesome framework. 12 | 13 | ## Contributors 14 | 15 | 16 | | ![gekkedev](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/17025257?v=4&h=100&w=100&fit=cover&mask=circle&maxage=7d) | 17 | | :-----------------------------------------------------------------------------------------------------------------------------------------: | 18 | | [gekkedev](https://github.com/gekkedev) | 19 | 20 | | ![srabouin](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/1645693?v=4&h=100&w=100&fit=cover&mask=circle&maxage=7d) | 21 | | :-----------------------------------------------------------------------------------------------------------------------------------------: | 22 | | [srabouin](https://github.com/srabouin) | 23 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroText: ⏳ vue-timeago3 4 | tagline: A super tiny timeago component for Vue.js 3 5 | actionText: Get Started 6 | actionLink: /guide/getting-started 7 | features: 8 | - title: TypeScript support 9 | details: The library was completely written in TypeScript, which brings a great support for Vue3' Composition API. 10 | - title: Performant 11 | details: Thanks to Vue.js 3.0 the component is both super tiny and fast. So fast, that you won't even notice it. 12 | - title: date-fns v2.26 13 | details: Thanks to date-fns you have the full control over everything, including the language and converter options. 14 | footer: MIT Licensed | Copyright © 2021-present MrDeerly 15 | --- 16 | 17 | --- 18 | 19 |
20 | 21 | ## Example Usage 22 | 23 | ``` 24 | Now was: less than 5 seconds ago 25 | ``` 26 | 27 | ```vue{3,12} 28 | 33 | 34 | 45 | ``` 46 | 47 |
48 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "uninstall-dev-package": "npm r vue-timeago3-2.3.2.tgz", 6 | "install-dev-package": "npm i ../vue-timeago3-2.3.2.tgz", 7 | "dev": "npm run uninstall-dev-package && npm run install-dev-package && vite", 8 | "build": "vue-tsc --noEmit && vite build", 9 | "serve": "vite preview" 10 | }, 11 | "dependencies": { 12 | "examples": "file:", 13 | "vue": "^3.2.16", 14 | "vue-timeago3": "file:../vue-timeago3-2.3.2.tgz" 15 | }, 16 | "devDependencies": { 17 | "@vitejs/plugin-vue": "^1.9.3", 18 | "typescript": "^4.4.3", 19 | "vite": "^2.9.16", 20 | "vue-tsc": "^0.3.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/src/App.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 89 | 90 | 117 | -------------------------------------------------------------------------------- /examples/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module "*.vue" { 4 | import { DefineComponent } from "vue"; 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 6 | const component: DefineComponent<{}, {}, any>; 7 | export default component; 8 | } 9 | -------------------------------------------------------------------------------- /examples/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | // @ts-ignore 4 | import timeago from "vue-timeago3"; 5 | 6 | createApp(App) 7 | .use(timeago) // register timeago 8 | .mount("#app"); 9 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "useDefineForClassFields": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "lib": ["esnext", "dom"] 13 | }, 14 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 15 | } 16 | -------------------------------------------------------------------------------- /examples/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import vue from "@vitejs/plugin-vue"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | }); 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-timeago3", 3 | "version": "2.3.2", 4 | "files": [ 5 | "dist" 6 | ], 7 | "main": "./dist/vue-timeago.umd.js", 8 | "module": "./dist/vue-timeago.es.js", 9 | "types": "./dist/vue-timeago.d.ts", 10 | "exports": { 11 | ".": { 12 | "import": "./dist/vue-timeago.es.js", 13 | "require": "./dist/vue-timeago.umd.js", 14 | "types": "./dist/vue-timeago.d.ts" 15 | } 16 | }, 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/MrDeerly/vue-timeago3.git" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/MrDeerly/vue-timeago3/issues" 24 | }, 25 | "homepage": "https://mrdeerly.github.io/vue-timeago3/", 26 | "keywords": [ 27 | "vue", 28 | "vue3", 29 | "timeago", 30 | "datefns", 31 | "date", 32 | "whileago", 33 | "currentTime", 34 | "distance", 35 | "time", 36 | "front-end" 37 | ], 38 | "scripts": { 39 | "dev": "vite build && npm --prefix ./dist pack && npm --prefix examples run dev", 40 | "build": "vue-tsc --declaration --emitDeclarationOnly && vite build", 41 | "serve": "vite preview", 42 | "docs:dev": "vitepress dev docs", 43 | "docs:build": "vitepress build docs", 44 | "docs:serve": "vitepress serve docs", 45 | "test": "vitest", 46 | "lint": "eslint src --ext .ts", 47 | "coverage": "vitest run --coverage" 48 | }, 49 | "dependencies": { 50 | "date-fns": "^2.28.0" 51 | }, 52 | "peerDependencies": { 53 | "vue": "^3.3.6" 54 | }, 55 | "devDependencies": { 56 | "@types/node": "^20.1.1", 57 | "@typescript-eslint/eslint-plugin": "^5.27.1", 58 | "@typescript-eslint/parser": "^5.27.1", 59 | "@vitest/coverage-v8": "^0.34.6", 60 | "@vue/test-utils": "^2.4.1", 61 | "eslint": "^8.17.0", 62 | "eslint-plugin-vue": "^9.1.1", 63 | "ts-node": "^10.8.1", 64 | "typescript": "^5.2.2", 65 | "vite": "^5.0.10", 66 | "vite-plugin-dts": "^3.6.0", 67 | "vitepress": "^1.6.3", 68 | "vitest": "^0.34.6", 69 | "vue-eslint-parser": "^9.0.2", 70 | "vue-tsc": "^1.0.9" 71 | }, 72 | "description": "vue-timeago3 is a tiny component for Vue.js 3, to show the time passed since a specific date. You simply pass a date and get somewhat like 10 seconds ago, 3 weeks ago, ... printed by the component", 73 | "author": "MrDeerly" 74 | } 75 | -------------------------------------------------------------------------------- /src/converter/defaultConverter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Locale, 3 | parseISO, 4 | formatDistanceToNow, 5 | formatDistanceToNowStrict, 6 | } from "date-fns"; 7 | 8 | interface DefaultConverterOptions { 9 | useStrict?: false; 10 | includeSeconds?: boolean; 11 | addSuffix?: boolean; 12 | } 13 | 14 | interface StrictConverterOptions { 15 | useStrict: true; 16 | addSuffix?: boolean; 17 | unit?: "second" | "minute" | "hour" | "day" | "month" | "year"; 18 | roundingMethod?: "floor" | "ceil" | "round"; 19 | } 20 | 21 | export type ConverterOptions = DefaultConverterOptions | StrictConverterOptions; 22 | 23 | export default ( 24 | date: string | Date, 25 | converterOptions: ConverterOptions = {}, 26 | locale?: Locale 27 | ): string => { 28 | if (typeof date === "string") { 29 | date = parseISO(date); 30 | } 31 | 32 | if (converterOptions.useStrict) { 33 | return formatDistanceToNowStrict(date, { 34 | addSuffix: converterOptions.addSuffix ?? true, 35 | locale, 36 | unit: converterOptions.unit, 37 | roundingMethod: converterOptions.roundingMethod, 38 | }); 39 | } 40 | 41 | return formatDistanceToNow(date, { 42 | includeSeconds: converterOptions.includeSeconds, 43 | addSuffix: converterOptions.addSuffix ?? true, 44 | locale, 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /src/install.ts: -------------------------------------------------------------------------------- 1 | import { App } from "vue"; 2 | import { createTimeago } from "./timeago"; 3 | import { ConverterOptions } from "./converter/defaultConverter"; 4 | 5 | export interface TimeagoOptions { 6 | name?: string; 7 | converter?: unknown; 8 | converterOptions?: ConverterOptions; 9 | locale?: Locale; 10 | } 11 | 12 | export default (app: App, options?: TimeagoOptions): void => { 13 | // check if component is registered 14 | if (app.config.globalProperties.$timeago) { 15 | return; 16 | } 17 | 18 | // check Vue version 19 | const version = Number(app.version.split(".")[0]); 20 | if (version < 3) { 21 | console.warn( 22 | "[vue-timeago3] This plugin requires at least Vue version 3.0" 23 | ); 24 | } 25 | 26 | const Component = createTimeago(options); 27 | // eslint-disable-next-line vue/multi-word-component-names 28 | app.component(Component.name, Component); 29 | }; 30 | -------------------------------------------------------------------------------- /src/timeago.ts: -------------------------------------------------------------------------------- 1 | import defaultConverter, { 2 | ConverterOptions, 3 | } from "./converter/defaultConverter"; 4 | import { 5 | defineComponent, 6 | h, 7 | onBeforeMount, 8 | onBeforeUnmount, 9 | PropType, 10 | ref, 11 | watch, 12 | } from "vue"; 13 | import { TimeagoOptions } from "./install"; 14 | import { Locale } from "date-fns"; 15 | 16 | const createTimeago = ( 17 | opts: TimeagoOptions = {} 18 | ): ReturnType => { 19 | const name = opts.name || "Timeago"; 20 | return defineComponent({ 21 | name: name, 22 | props: { 23 | datetime: { 24 | type: [String, Number, Date], 25 | required: true, 26 | }, 27 | title: { 28 | type: [String, Boolean], 29 | required: false, 30 | default: null, 31 | }, 32 | autoUpdate: { 33 | type: [Number, Boolean], 34 | required: false, 35 | default: null, 36 | }, 37 | converter: { 38 | type: Function, 39 | required: false, 40 | default: null, 41 | }, 42 | converterOptions: { 43 | type: Object as PropType, 44 | required: false, 45 | default: null, 46 | }, 47 | locale: { 48 | type: Object as PropType, 49 | required: false, 50 | default: null, 51 | }, 52 | }, 53 | setup(props) { 54 | const updateTimer = ref>(); 55 | 56 | // start the update timer 57 | onBeforeMount(() => { 58 | startUpdater(); 59 | }); 60 | 61 | // stop the update timer 62 | onBeforeUnmount(() => { 63 | stopUpdater(); 64 | }); 65 | 66 | // getTimeago calls the specified converter and converts the current time to a string "timeago" 67 | const getTimeago = ( 68 | datetime?: string | number | Date 69 | ): (( 70 | datetime: string, 71 | converter: ConverterOptions 72 | ) => string | unknown) => { 73 | const converter = props.converter || defaultConverter; 74 | return converter( 75 | datetime || props.datetime, 76 | props.converterOptions || opts.converterOptions, 77 | props.locale || opts.locale 78 | ); 79 | }; 80 | 81 | const timeago = ref(getTimeago()); 82 | 83 | // convert get's the current datetime as string based on the user's input 84 | const convert = (datetime?: string | number | Date): void => { 85 | timeago.value = getTimeago(datetime); 86 | }; 87 | 88 | // startUpdater starts a new update timer based on the user's input 89 | const startUpdater = (): void => { 90 | if (props.autoUpdate) { 91 | const autoUpdate = props.autoUpdate === true ? 60 : props.autoUpdate; 92 | updateTimer.value = setInterval(() => { 93 | convert(props.datetime); 94 | }, autoUpdate * 1000); 95 | } 96 | }; 97 | 98 | // stopUpdater stops the current update timer 99 | const stopUpdater = (): void => { 100 | if (updateTimer.value) { 101 | clearInterval(updateTimer.value); 102 | updateTimer.value = undefined; 103 | } 104 | }; 105 | 106 | // update converter if property changed 107 | watch( 108 | () => props.autoUpdate, 109 | (newValue) => { 110 | stopUpdater(); 111 | if (newValue) { 112 | startUpdater(); 113 | } 114 | } 115 | ); 116 | 117 | // update converter if property changed 118 | watch( 119 | () => [props.datetime, props.converter], 120 | () => { 121 | convert(); 122 | } 123 | ); 124 | 125 | // update converter if property changed 126 | watch( 127 | () => props.converterOptions, 128 | () => { 129 | convert(); 130 | }, 131 | { 132 | deep: true, 133 | } 134 | ); 135 | 136 | return { timeago, updateTimer }; 137 | }, 138 | render() { 139 | return h( 140 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 141 | // @ts-ignore 142 | "timeago", 143 | { 144 | attrs: { 145 | datetime: new Date(this.datetime).toISOString(), 146 | title: 147 | typeof this.title === "string" 148 | ? this.title 149 | : this.title === false 150 | ? null 151 | : this.timeago, 152 | }, 153 | }, 154 | [this.timeago] 155 | ); 156 | }, 157 | }); 158 | }; 159 | export { createTimeago }; 160 | -------------------------------------------------------------------------------- /tests/unit/defaultConverter.spec.ts: -------------------------------------------------------------------------------- 1 | import defaultConverter from "../../src/converter/defaultConverter"; 2 | import { 3 | formatDistanceToNow, 4 | formatDistanceToNowStrict, 5 | parseISO, 6 | } from "date-fns"; 7 | import { es } from "date-fns/locale"; 8 | import {vi, describe, expect, it} from 'vitest'; 9 | 10 | vi.mock("date-fns", () => ({ 11 | parseISO: vi.fn(), 12 | formatDistanceToNow: vi.fn(), 13 | formatDistanceToNowStrict: vi.fn(), 14 | })); 15 | 16 | const date = new Date(); 17 | 18 | describe("exports", () => { 19 | it("exports", () => { 20 | expect(typeof defaultConverter).toEqual("function"); 21 | }); 22 | }); 23 | 24 | describe("defaultConverter", () => { 25 | it("should call date-fns function with date", () => { 26 | defaultConverter(date); 27 | 28 | expect(formatDistanceToNow).toHaveBeenCalledWith(date, { 29 | addSuffix: true, 30 | includeSeconds: undefined, 31 | locale: undefined, 32 | }); 33 | }); 34 | 35 | it("should convert a ISO string to a Date", () => { 36 | const ISOString = date.toISOString(); 37 | 38 | defaultConverter(ISOString); 39 | 40 | expect(parseISO).toHaveBeenCalledWith(ISOString); 41 | }); 42 | 43 | it("should use the converterOptions", () => { 44 | defaultConverter(date, { addSuffix: false, includeSeconds: true }); 45 | 46 | expect(formatDistanceToNow).toHaveBeenCalledWith(date, { 47 | addSuffix: false, 48 | includeSeconds: true, 49 | locale: undefined, 50 | }); 51 | }); 52 | 53 | it("should use the specified locale", () => { 54 | defaultConverter(date, { addSuffix: false, includeSeconds: true }, es); 55 | 56 | expect(formatDistanceToNow).toHaveBeenCalledWith(date, { 57 | addSuffix: false, 58 | includeSeconds: true, 59 | locale: es, 60 | }); 61 | }); 62 | 63 | it("should use the strict mode", () => { 64 | defaultConverter(date, { addSuffix: true, useStrict: true }, es); 65 | 66 | expect(formatDistanceToNowStrict).toHaveBeenCalledWith(date, { 67 | addSuffix: true, 68 | locale: es, 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /tests/unit/install.spec.ts: -------------------------------------------------------------------------------- 1 | import * as vue from "vue"; 2 | import install from "../../src/install"; 3 | import {vi, describe, expect, it} from 'vitest'; 4 | 5 | const App = {}; 6 | 7 | describe("exports", () => { 8 | it("exports", () => { 9 | expect(typeof install).toEqual("function"); 10 | }); 11 | }); 12 | 13 | describe("plugin", () => { 14 | it("should load the plugin", () => { 15 | const app = vue.createApp(App); 16 | const componentSpy = vi.spyOn(app, "component"); 17 | expect(componentSpy).toHaveBeenCalledTimes(0); 18 | app.use(install); 19 | expect(componentSpy).toHaveBeenCalledTimes(1); 20 | }); 21 | 22 | it("should not load plugin twice", () => { 23 | const app = vue.createApp(App); 24 | const componentSpy = vi.spyOn(app, "component"); 25 | app.config.globalProperties.$timeago = "already defined"; 26 | app.use(install); 27 | expect(componentSpy).not.toHaveBeenCalled(); 28 | }); 29 | 30 | it("should throw a warning for a invalid vue version", () => { 31 | const app = vue.createApp(App); 32 | app.version = "2.1"; 33 | // eslint-disable-next-line @typescript-eslint/no-empty-function 34 | const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => { 35 | }); 36 | 37 | app.use(install); 38 | 39 | expect(consoleSpy).toHaveBeenCalledWith( 40 | "[vue-timeago3] This plugin requires at least Vue version 3.0" 41 | ); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/unit/timeago.spec.ts: -------------------------------------------------------------------------------- 1 | import * as vue from "vue"; 2 | import { createTimeago } from "../../src/timeago"; 3 | import { mount, config } from "@vue/test-utils"; 4 | import TimeAgo from "../../src/install"; 5 | import { defineComponent, nextTick, Plugin } from "vue"; 6 | import { de, es } from "date-fns/locale"; 7 | import {vi, describe, expect, it} from 'vitest'; 8 | 9 | const TestComponent = { 10 | name: "test-component", 11 | setup() { 12 | return () => vue.h("timeago", {}); 13 | }, 14 | }; 15 | 16 | const App = { 17 | components: { 18 | TestComponent, 19 | }, 20 | setup() { 21 | return () => 22 | vue.h("div", { class: "main" }, [ 23 | vue.h("div", { 24 | id: "timeago", 25 | }), 26 | vue.h(TestComponent), 27 | ]); 28 | }, 29 | }; 30 | 31 | describe("exports", () => { 32 | it("exports", () => { 33 | expect(typeof createTimeago).toEqual("function"); 34 | }); 35 | }); 36 | 37 | describe("global options", () => { 38 | it("should be created with a custom name", () => { 39 | const Component = createTimeago({ name: "test" }); 40 | 41 | const wrapper = mount(Component, { 42 | propsData: { 43 | datetime: new Date(), 44 | }, 45 | }); 46 | 47 | expect(wrapper.vm.$options.name).toBe("test"); 48 | }); 49 | 50 | it("should be with with a custom locale", () => { 51 | const Component = createTimeago({ locale: es }); 52 | 53 | const wrapper = mount(Component, { 54 | propsData: { 55 | datetime: new Date(), 56 | }, 57 | }); 58 | 59 | expect(wrapper.vm.timeago).toBe("hace menos de un minuto"); 60 | }); 61 | 62 | it("should use global converter options", () => { 63 | const Component = createTimeago({ 64 | converterOptions: { includeSeconds: true, addSuffix: false }, 65 | }); 66 | 67 | const wrapper = mount(Component, { 68 | propsData: { 69 | datetime: new Date(), 70 | }, 71 | }); 72 | expect(wrapper.vm.timeago).toBe("less than 5 seconds"); 73 | }); 74 | }); 75 | 76 | describe("props", () => { 77 | it("autoUpdate: should automatically update", () => { 78 | const Component = createTimeago(); 79 | 80 | const wrapper = mount(Component, { 81 | propsData: { 82 | datetime: new Date(), 83 | autoUpdate: true, 84 | converterOptions: { includeSeconds: true }, 85 | }, 86 | }); 87 | 88 | expect(wrapper.vm.timeago).toBe("less than 5 seconds ago"); 89 | setTimeout(() => { 90 | expect(wrapper.vm.timeago).toBe("less than 10 seconds ago"); 91 | }, 500); 92 | }); 93 | 94 | it("autoUpdate: should set a timer update", () => { 95 | const Component = createTimeago(); 96 | 97 | const wrapper = mount(Component, { 98 | propsData: { 99 | datetime: new Date(), 100 | autoUpdate: true, 101 | }, 102 | }); 103 | 104 | expect(wrapper.vm.updateTimer).not.toBeUndefined(); 105 | }); 106 | 107 | it("autoUpdate: should delete a timer before unmount", async () => { 108 | const Component = createTimeago(); 109 | 110 | const wrapper = mount(Component, { 111 | propsData: { 112 | datetime: new Date(), 113 | autoUpdate: true, 114 | }, 115 | }); 116 | expect(wrapper.vm.updateTimer).not.toBeUndefined(); 117 | await wrapper.unmount(); 118 | expect(wrapper.vm.updateTimer).toBeUndefined(); 119 | }); 120 | 121 | it("converterOptions: should include seconds", () => { 122 | const Component = createTimeago(); 123 | 124 | const wrapper = mount(Component, { 125 | propsData: { 126 | datetime: new Date(), 127 | converterOptions: { includeSeconds: true }, 128 | }, 129 | }); 130 | 131 | expect(wrapper.vm.timeago).toBe("less than 5 seconds ago"); 132 | }); 133 | 134 | it("converterOptions: remove a suffix", () => { 135 | const Component = createTimeago(); 136 | 137 | const wrapper = mount(Component, { 138 | propsData: { 139 | datetime: new Date(), 140 | converterOptions: { addSuffix: false }, 141 | }, 142 | }); 143 | 144 | expect(wrapper.vm.timeago).toBe("less than a minute"); 145 | }); 146 | 147 | it("locale: should use custom locale", () => { 148 | const Component = createTimeago(); 149 | 150 | const wrapper = mount(Component, { 151 | propsData: { 152 | datetime: new Date(), 153 | locale: es, 154 | }, 155 | }); 156 | 157 | expect(wrapper.vm.timeago).toBe("hace menos de un minuto"); 158 | }); 159 | 160 | it("should override global options", async () => { 161 | const Component = createTimeago({ 162 | locale: es, 163 | converterOptions: { addSuffix: false, includeSeconds: true }, 164 | }); 165 | 166 | const wrapper = mount(Component, { 167 | propsData: { 168 | datetime: new Date(), 169 | }, 170 | }); 171 | 172 | expect(wrapper.vm.timeago).toBe("menos de 5 segundos"); 173 | 174 | await wrapper.setProps({ 175 | locale: de, 176 | converterOptions: { addSuffix: true, includeSeconds: false }, 177 | datetime: new Date(), 178 | }); 179 | expect(wrapper.vm.timeago).toBe("vor weniger als 1 Minute"); 180 | }); 181 | }); 182 | 183 | describe("watcher", () => { 184 | it("props - converterOptions", async () => { 185 | const Component = createTimeago(); 186 | 187 | const wrapper = mount(Component, { 188 | propsData: { 189 | datetime: new Date(), 190 | }, 191 | }); 192 | expect(wrapper.vm.timeago).toBe("less than a minute ago"); 193 | await wrapper.setProps({ converterOptions: { addSuffix: false } }); 194 | expect(wrapper.vm.timeago).toBe("less than a minute"); 195 | }); 196 | 197 | it("props - datetime", async () => { 198 | const Component = createTimeago(); 199 | 200 | const wrapper = mount(Component, { 201 | propsData: { 202 | datetime: new Date("1.1.1990"), 203 | }, 204 | }); 205 | expect(wrapper.vm.timeago).not.toBe("less than a minute ago"); 206 | await wrapper.setProps({ datetime: new Date() }); 207 | expect(wrapper.vm.timeago).toBe("less than a minute ago"); 208 | }); 209 | 210 | it("props - autoUpdate", async () => { 211 | const Component = createTimeago(); 212 | 213 | const wrapper = mount(Component, { 214 | propsData: { 215 | datetime: new Date(), 216 | autoUpdate: false, 217 | }, 218 | }); 219 | setTimeout(() => { 220 | expect(wrapper.vm.timeago).toBe("less than a minute ago"); 221 | }, 5000); 222 | 223 | expect(wrapper.vm.updateTimer).toBe(undefined); 224 | 225 | await wrapper.setProps({ autoUpdate: true }); 226 | setTimeout(() => { 227 | expect(wrapper.vm.timeago).not.toBe("less than a minute ago"); 228 | }, 5000); 229 | 230 | expect(wrapper.vm.updateTimer).not.toBe(undefined); 231 | }); 232 | }); 233 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "target": "esnext", 5 | "module": "esnext", 6 | "strict": true, 7 | "jsx": "preserve", 8 | "importHelpers": true, 9 | "moduleResolution": "node", 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "sourceMap": true, 14 | "baseUrl": ".", 15 | "types": [ 16 | "node" 17 | ], 18 | "paths": { 19 | "@/*": ["src/*"] 20 | }, 21 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 22 | }, 23 | "include": ["src/**/*.ts"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import path from "path"; 4 | import { defineConfig } from "vite"; 5 | import dts from "vite-plugin-dts"; 6 | 7 | export default defineConfig({ 8 | build: { 9 | lib: { 10 | entry: path.resolve(__dirname, "src/install.ts"), 11 | name: "VueTimeago3", 12 | fileName: (format) => `vue-timeago.${format}.js`, 13 | }, 14 | rollupOptions: { 15 | external: ["vue"], 16 | output: { 17 | exports: "named", 18 | assetFileNames: "vue-timeago.[ext]", 19 | globals: { 20 | vue: "Vue", 21 | }, 22 | }, 23 | }, 24 | }, 25 | test: { 26 | environment: "jsdom", 27 | coverage: { 28 | provider: 'v8', 29 | reporter: ['text', 'json-summary'], 30 | }, 31 | }, 32 | plugins: [ 33 | dts({ 34 | entryRoot: "./src/", 35 | rollupTypes: true 36 | }), 37 | ], 38 | }); 39 | --------------------------------------------------------------------------------