├── .nvmrc ├── .npmrc ├── .husky └── pre-commit ├── .gitignore ├── assets ├── images │ ├── timer.png │ └── timer_long.png └── js │ ├── service.js │ └── syotimer.examples.js ├── .gitattributes ├── .prettierrc.json ├── tsconfig.lib.json ├── vite.config.ts ├── tsconfig.spec.json ├── index.d.ts ├── tsconfig.json ├── .github └── workflows │ ├── lint.yml │ └── issue_stale.yml ├── .eslintrc.json ├── source ├── utils.spec.ts ├── localization.ts ├── index.ts ├── constants.ts ├── types.ts ├── utils.ts └── SyoTimer.ts ├── LICENSE ├── rollup.config.mjs ├── package.json ├── CHANGELOG.md ├── resources └── default.css ├── README.md └── index.html /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/hydrogen -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | node_modules/ 4 | .idea/ 5 | npm-debug.log 6 | tmp/ -------------------------------------------------------------------------------- /assets/images/timer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrfratello/SyoTimer/HEAD/assets/images/timer.png -------------------------------------------------------------------------------- /assets/images/timer_long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrfratello/SyoTimer/HEAD/assets/images/timer_long.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | build/* text -diff 4 | yarn.lock text -diff 5 | package-lock.json text -diff 6 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/prettierrc", 3 | "printWidth": 100, 4 | "singleQuote": true, 5 | "trailingComma": "all" 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./tmp/out-tsc" 5 | }, 6 | "include": ["source/**/*.ts", "index.d.ts"], 7 | "exclude": ["vite.config.ts", "source/**/*.spec.ts", "source/**/*.test.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | cache: { 7 | dir: './node_modules/.vitest', 8 | }, 9 | environment: 'jsdom', 10 | include: ['source/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}'], 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./tmp/out-tsc", 5 | "types": ["vitest/globals", "vitest/importMeta", "vite/client"] 6 | }, 7 | "include": [ 8 | "vite.config.ts", 9 | "vitest.setup.ts", 10 | "source/**/*.test.ts", 11 | "source/**/*.spec.ts", 12 | "index.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | SyoTimerOptions, 3 | SyoTimerLocalization, 4 | SyoTimerMethods, 5 | SyoTimerOptionProps, 6 | SyoTimerOptionValues, 7 | } from './source/types'; 8 | 9 | export type { SyoTimerOptions, SyoTimerLocalization }; 10 | 11 | declare global { 12 | interface JQuery { 13 | syotimer(options: SyoTimerOptions): JQuery; 14 | syotimer( 15 | method: SyoTimerMethods, 16 | property: SyoTimerOptionProps, 17 | value: SyoTimerOptionValues, 18 | ): JQuery; 19 | } 20 | 21 | interface JQueryStatic { 22 | syotimerLang: SyoTimerLocalization; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "baseUrl": "./source", 7 | "isolatedModules": true, 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "resolveJsonModule": true, 12 | "strict": true, 13 | "noImplicitAny": true, 14 | "skipDefaultLibCheck": true, 15 | "skipLibCheck": true 16 | }, 17 | "files": [], 18 | "include": [], 19 | "references": [ 20 | { 21 | "path": "./tsconfig.lib.json" 22 | }, 23 | { 24 | "path": "./tsconfig.spec.json" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /assets/js/service.js: -------------------------------------------------------------------------------- 1 | jQuery(function ($) { 2 | $("pre") 3 | .hide() 4 | .before( 5 | '
' 6 | ); 7 | 8 | $(".toggle-source-code").on("click", function () { 9 | var button = $(this), 10 | code = button.next("pre"); 11 | if (button.hasClass("toggle-source-code_action_show")) { 12 | button 13 | .removeClass("toggle-source-code_action_show") 14 | .addClass("toggle-source-code_action_hide"); 15 | code.slideDown(); 16 | } else if (button.hasClass("toggle-source-code_action_hide")) { 17 | button 18 | .removeClass("toggle-source-code_action_hide") 19 | .addClass("toggle-source-code_action_show"); 20 | code.slideUp(); 21 | } 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | main: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: get sources 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | - name: node version 18 | id: node_info 19 | run: echo "version=$(cat .nvmrc)" >> $GITHUB_OUTPUT 20 | shell: bash 21 | - name: setup node lts version 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: '${{ steps.node_info.outputs.version }}' 25 | - name: install dependencies 26 | run: npm ci 27 | shell: bash 28 | 29 | - name: lint 30 | run: npm run lint 31 | - name: test 32 | run: npm run test 33 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "jquery": true 6 | }, 7 | "extends": ["airbnb-base", "airbnb-typescript/base", "prettier"], 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "ecmaVersion": 12, 11 | "sourceType": "module", 12 | "project": "./tsconfig.*?.json" 13 | }, 14 | "plugins": ["@typescript-eslint", "prettier"], 15 | "rules": { 16 | "prettier/prettier": "error" 17 | }, 18 | "overrides": [ 19 | { 20 | "files": "{rollup.config.mjs,vite.config.ts}", 21 | "rules": { 22 | "import/no-extraneous-dependencies": [ 23 | "error", 24 | { 25 | "devDependencies": true, 26 | "optionalDependencies": false 27 | } 28 | ] 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /source/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { format2 } from './utils'; 2 | 3 | describe('utils', () => { 4 | describe('format2', () => { 5 | test.each([ 6 | { input: 0, output: '0' }, 7 | { input: 3, output: '3' }, 8 | { input: 10, output: '10' }, 9 | { input: 21, output: '21' }, 10 | { input: 99, output: '99' }, 11 | ])('should return without lead zero: from $input to $output', ({ input, output }) => { 12 | expect(format2(input)).toBe(output); 13 | }); 14 | 15 | test.each([ 16 | { input: 0, output: '00' }, 17 | { input: 3, output: '03' }, 18 | { input: 10, output: '10' }, 19 | { input: 21, output: '21' }, 20 | { input: 99, output: '99' }, 21 | ])('should return with lead zero: from $input to $output', ({ input, output }) => { 22 | expect(format2(input, true)).toBe(output); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /.github/workflows/issue_stale.yml: -------------------------------------------------------------------------------- 1 | name: Stale 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | # This runs every day 20 minutes before midnight: https://crontab.guru/#40_23_*_*_* 7 | - cron: '40 23 * * *' 8 | 9 | jobs: 10 | stale: 11 | runs-on: ubuntu-latest 12 | if: github.repository_owner == 'mrfratello' 13 | permissions: 14 | issues: write 15 | pull-requests: write 16 | 17 | steps: 18 | - uses: actions/stale@v9 19 | name: 'Close stale issues' 20 | with: 21 | stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.' 22 | stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.' 23 | close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' 24 | close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.' 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /source/localization.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | 3 | $.syotimerLang = { 4 | rus: { 5 | second: ['секунда', 'секунды', 'секунд'], 6 | minute: ['минута', 'минуты', 'минут'], 7 | hour: ['час', 'часа', 'часов'], 8 | day: ['день', 'дня', 'дней'], 9 | handler: function rusNumeral(number, words) { 10 | const cases = [2, 0, 1, 1, 1, 2]; 11 | if (number % 100 > 4 && number % 100 < 20) { 12 | return words[2]; 13 | } 14 | const index = cases[number % 10 < 5 ? number % 10 : 5]; 15 | return words[index]; 16 | }, 17 | }, 18 | eng: { 19 | second: ['second', 'seconds'], 20 | minute: ['minute', 'minutes'], 21 | hour: ['hour', 'hours'], 22 | day: ['day', 'days'], 23 | }, 24 | por: { 25 | second: ['segundo', 'segundos'], 26 | minute: ['minuto', 'minutos'], 27 | hour: ['hora', 'horas'], 28 | day: ['dia', 'dias'], 29 | }, 30 | spa: { 31 | second: ['segundo', 'segundos'], 32 | minute: ['minuto', 'minutos'], 33 | hour: ['hora', 'horas'], 34 | day: ['día', 'días'], 35 | }, 36 | heb: { 37 | second: ['שניה', 'שניות'], 38 | minute: ['דקה', 'דקות'], 39 | hour: ['שעה', 'שעות'], 40 | day: ['יום', 'ימים'], 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /source/index.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import './localization'; 3 | import mapSyoTimer from './SyoTimer'; 4 | import type { 5 | SyoTimerOptions, 6 | SyoTimerMethods, 7 | SyoTimerOptionProps, 8 | SyoTimerOptionValues, 9 | } from './types'; 10 | 11 | const methods: RecordThe countdown is finished!
'); 50 | }, 51 | itemTypes: ['day', 'hour', 'minute', 'second'], 52 | itemsHas: $.extend({}, defaultItemsHas), 53 | }; 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-syotimer", 3 | "projectName": "SyoTimer", 4 | "version": "3.1.2", 5 | "description": "jquery countdown plugin", 6 | "main": "./build/jquery.syotimer.js", 7 | "types": "./build/jquery.syotimer.d.ts", 8 | "files": [ 9 | "build", 10 | "resources" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/mrfratello/SyoTimer.git" 15 | }, 16 | "scripts": { 17 | "start": "rollup -c -w", 18 | "build": "rollup -c", 19 | "format": "prettier --write \"./source/\"", 20 | "format:check": "prettier --check \"./source/\"", 21 | "eslint": "eslint source/**", 22 | "tslint": "tsc -b --incremental", 23 | "lint": "npm run format:check && npm run tslint && npm run eslint", 24 | "test": "vitest --run", 25 | "test:ui": "vitest --ui", 26 | "release:before": "npm run lint && npm run build && git add .", 27 | "release": "standard-version -a", 28 | "prepare": "husky install" 29 | }, 30 | "keywords": [ 31 | "countdown", 32 | "jquery", 33 | "plugin" 34 | ], 35 | "author": "John SyomochkinThe countdown is finished 00:00 2000.12.31
'); 21 | syotimer.headBlock.html('/* Simple Timer. The countdown to 20:30 2035.05.09 */ 111 | $("#simple-timer").syotimer({ 112 | date: new Date(2035, 4, 9, 20, 30), 113 | }); 114 |115 |
/* Timer with Head and Foot. Countdown is over */ 122 | $("#expired-timer").syotimer({ 123 | date: new Date(1990, 0), 124 | headTitle: "<h3>Timer with header and footer. Countdown is over</h3>", 125 | footTitle: '<i style="color: brown;">Footer of timer.</i>', 126 | }); 127 |128 |
/* Callback after the end of the countdown timer */ 135 | $("#expired-timer_event").syotimer({ 136 | date: new Date(2000, 11, 31), 137 | headTitle: "<h3>Timer with header and footer. Countdown is over</h3>", 138 | footTitle: '<i style="color: brown;">Footer of timer.</i>', 139 | afterDeadline: function (syotimer) { 140 | syotimer.bodyBlock.html( 141 | "<p>The countdown is finished 00:00 2000.12.31</p>" 142 | ); 143 | syotimer.headBlock.html( 144 | "<h3>Callback after the end of the countdown timer</h3>" 145 | ); 146 | syotimer.footBlock.html( 147 | '<em style="color:brown;">' + 148 | "Footer of timer after countdown is finished" + 149 | "</em>" 150 | ); 151 | }, 152 | }); 153 |154 |
/* Periodic Timer. Period is equal 3 minutes. Effect of fading in */ 164 | $("#periodic-timer_period_minutes").syotimer({ 165 | date: new Date(2015, 0, 1), 166 | layout: "hms", 167 | doubleNumbers: false, 168 | effectType: "opacity", 169 | 170 | periodUnit: "m", 171 | periodic: true, 172 | periodInterval: 3, 173 | }); 174 |175 |
The date equal 20:00 2015.01.01
182 | 183 | 184 |/* Periodic Timer. Period is equal 10 days */ 187 | $("#periodic-timer_period_days").syotimer({ 188 | date: new Date(2015, 0, 1, 20), 189 | layout: "hms", 190 | periodic: true, 191 | periodInterval: 10, 192 | periodUnit: "d", 193 | }); 194 |195 |
Demonstrate layout. Period is equal 2 hours.
200 | 201 | 202 |/* Demonstrate layout. Period is equal 2 hours. Display only seconds */ 205 | $("#layout-timer_only-seconds").syotimer({ 206 | layout: "s", 207 | periodic: true, 208 | periodInterval: 2, 209 | periodUnit: "h", 210 | }); 211 |212 |
Demonstrate layout. Period is equal 2 days and 5 hours.
217 | 218 | 219 |/* Demonstrate layout. Period is equal 2 days and 5 hours. 222 | Units of countdown in reverse order */ 223 | $("#layout-timer_reversed-units").syotimer({ 224 | layout: "smhd", 225 | effectType: "opacity", 226 | 227 | periodic: true, 228 | periodInterval: 53, 229 | periodUnit: "h", 230 | }); 231 |232 |
237 | Demonstrate layout. Period is equal 1 days, 5 hours and 37 minutes. 238 |
239 | 240 | 241 |/* Demonstrate layout. Period is equal 2 days and 5 hours. 244 | Display only days and minutes in reverse order */ 245 | $("#layout-timer_mixed-units").syotimer({ 246 | layout: "md", 247 | 248 | periodic: true, 249 | periodInterval: 1777, 250 | periodUnit: "m", 251 | }); 252 |253 |
The deadline is 18:00:00 by the time Rome, Italy (UTC+2)
258 | 259 | 260 |/* Periodic Timer. Countdown timer with given time zone */ 263 | $("#periodic-timer_timezone_given").syotimer({ 264 | date: new Date("2000-07-01T18:00:00.000+02:00"), 265 | layout: "hms", 266 | 267 | periodic: true, 268 | periodInterval: 1, 269 | periodUnit: "d", 270 | }); 271 |272 |
/** 304 | * Periodic Timer. 305 | * Change options: doubleNumbers, effect type, language 306 | */ 307 | var changeOptionsTimer = $('#periodic-timer_change-options'); 308 | var effectTypeEl = $('#select-effect'); 309 | var formatNumberEl = $('#select-format-number'); 310 | var languageEl = $('#select-language'); 311 | 312 | changeOptionsTimer.syotimer({ 313 | periodic: true, 314 | periodInterval: 10, 315 | periodUnit: 'd', 316 | }); 317 | 318 | effectTypeEl.on('change', function () { 319 | var effectType = $('option:selected', this).val(); 320 | changeOptionsTimer.syotimer('setOption', 'effectType', effectType); 321 | }); 322 | 323 | formatNumberEl.on('change', function () { 324 | var formatNumberValue = $('option:selected', this).val(); 325 | var doubleNumbers = formatNumberValue === 'true'; 326 | changeOptionsTimer.syotimer('setOption', 'doubleNumbers', doubleNumbers); 327 | }); 328 | 329 | languageEl.on('change', function () { 330 | var language = $('option:selected', this).val(); 331 | changeOptionsTimer.syotimer('setOption', 'lang', language); 332 | });333 |
Demonstrate adding the new language of signatures.
338 | 339 |340 |357 | 358 |341 | Nenglish is a synthetic language. It is very similar to English but there are significant 342 | differences in the spelling of nouns after numerals. Namely, the difference in the 343 | suffixes of these nouns: 344 |
345 |346 |
356 |- 347 | if the number ends with the digit
350 |1then to the noun added the suffix "one" 348 | (21 secondone, 1 minuteone, ...); 349 |- 351 | if the number ends with the digit
354 |5then the suffix is equal "five" (35 352 | hourfive, 5 secondfive); 353 |- otherwise the suffix is equal to "s" (24 minutes, 3 days).
355 |
/** 359 | * Localization in timer. 360 | * Add new language 361 | */ 362 | 363 | // Adding of a words for signatures of countdown 364 | $.syotimerLang.neng = { 365 | second: ["secondone", "secondfive", "seconds"], 366 | minute: ["minuteone", "minutefive", "minutes"], 367 | hour: ["hourone", "hourfive", "hours"], 368 | day: ["dayone", "dayfive", "days"], 369 | // Adding of the handler that selects an index from the list of words 370 | // based on ahead the going number 371 | handler: function nengNumeral(number, words) { 372 | var lastDigit = number % 10; 373 | var index = 2; 374 | if (lastDigit === 1) { 375 | index = 0; 376 | } else if (lastDigit === 5) { 377 | index = 1; 378 | } 379 | return words[index]; 380 | }, 381 | }; 382 | 383 | $("#periodic-timer_localization_new-english").syotimer({ 384 | lang: "neng", 385 | layout: "ms", 386 | 387 | periodic: true, 388 | periodInterval: 6, 389 | periodUnit: "m", 390 | }); 391 |392 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
--------------------------------------------------------------------------------