diff --git a/.gitattributes b/.gitattributes index dc8ca9c..34859ed 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,12 +7,13 @@ composer.lock export-ignore docker-compose.yml export-ignore Dockerfile export-ignore Makefile export-ignore -contribute.md export-ignore +CONTRIBUTING.md export-ignore /.github export-ignore -nacc2/include-stripper.php export-ignore -nacc2/index.html export-ignore -nacc2/index.php export-ignore -nacc2/LICENSE.txt export-ignore -nacc2/unit_test.html export-ignore nacc2/src/ export-ignore +nacc2/package.json export-ignore +nacc2/package-lock.json export-ignore +nacc2/node_modules/ export-ignore +nacc2/vite.config.js export-ignore +nacc2/LICENSE.txt export-ignore nacc2/README.md export-ignore +nacc2/index.html export-ignore diff --git a/.github/workflows/latest.yml b/.github/workflows/latest.yml index 2591900..e6159eb 100644 --- a/.github/workflows/latest.yml +++ b/.github/workflows/latest.yml @@ -11,39 +11,31 @@ jobs: id-token: write contents: write env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} BUILD_DIR: build - DIST_DIR_S3_JS: dist_naccjs/s3 - DIST_DIR_S3: dist/s3 - DIST_DIR_GITHUB_JS: dist_naccjs/github - DIST_DIR_GITHUB: dist/github - GITHUB_RELEASE_FILENAME_JS: naccjs.zip - GITHUB_RELEASE_FILENAME: nacc-wordpress-plugin.zip + ZIP_FILENAME_JS: naccjs.zip + ZIP_FILENAME: nacc-wordpress-plugin.zip S3_KEY_JS: naccjs S3_KEY: nacc-wordpress-plugin steps: - name: Check out code 🛒 uses: actions/checkout@v6 - - name: Build nacc zip file 🔧 + - name: Setup Node.js 📦 + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Build nacc2 JavaScript 🔨 + working-directory: nacc2 run: | - export ZIP_FILENAME_JS=naccjs-build${GITHUB_RUN_NUMBER}-${GITHUB_SHA}.zip - echo "ZIP_FILENAME_JS=${ZIP_FILENAME_JS}" >> $GITHUB_ENV - export ZIP_FILENAME=nacc-wordpress-plugin-build${GITHUB_RUN_NUMBER}-${GITHUB_SHA}.zip - echo "ZIP_FILENAME=${ZIP_FILENAME}" >> $GITHUB_ENV - find ./ -type d | xargs chmod 755 - find ./ -name '*.php' | xargs chmod 644 - git archive --format=zip --output=${ZIP_FILENAME} ${GITHUB_SHA} - zip -r ${ZIP_FILENAME_JS} nacc2/* - TAG="${GITHUB_REF##*/}" - mkdir -p $BUILD_DIR && mv $ZIP_FILENAME_JS $BUILD_DIR/ && mv $ZIP_FILENAME $BUILD_DIR/ - mkdir -p $DIST_DIR_S3_JS && cp $BUILD_DIR/$ZIP_FILENAME_JS $DIST_DIR_S3_JS/$ZIP_FILENAME_JS - mkdir -p $DIST_DIR_GITHUB_JS && cp $BUILD_DIR/$ZIP_FILENAME_JS $DIST_DIR_GITHUB_JS/$GITHUB_RELEASE_FILENAME_JS - mkdir -p $DIST_DIR_S3 && cp $BUILD_DIR/$ZIP_FILENAME $DIST_DIR_S3/$ZIP_FILENAME - mkdir -p $DIST_DIR_GITHUB && cp $BUILD_DIR/$ZIP_FILENAME $DIST_DIR_GITHUB/$GITHUB_RELEASE_FILENAME + npm ci + npm run build + + - name: Build nacc zip file 🔧 + run: make build - name: configure aws credentials - uses: aws-actions/configure-aws-credentials@v5 + uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions/gh-ci-s3-artifact role-session-name: gh-actions-nacc @@ -51,13 +43,13 @@ jobs: - name: copy nacc artifacts to s3 🪣 run: | - aws s3 cp ${DIST_DIR_S3_JS}/${ZIP_FILENAME_JS} s3://${{ secrets.S3_BUCKET }}/${S3_KEY_JS}/${ZIP_FILENAME_JS} - aws s3 cp ${DIST_DIR_S3}/${ZIP_FILENAME} s3://${{ secrets.S3_BUCKET }}/${S3_KEY}/${ZIP_FILENAME} + aws s3 cp $BUILD_DIR/$ZIP_FILENAME_JS s3://${{ secrets.S3_BUCKET }}/$S3_KEY_JS/naccjs-${{ github.run_number }}-${{ github.sha }}.zip + aws s3 cp $BUILD_DIR/$ZIP_FILENAME s3://${{ secrets.S3_BUCKET }}/$S3_KEY/nacc-wordpress-plugin-${{ github.run_number }}-${{ github.sha }}.zip - name: Publish Release to Latest WP 🎉 id: publish_latest uses: bmlt-enabled/wordpress-releases-github-action@v2 with: - file: ${{ env.DIST_DIR_S3 }}/${{ env.ZIP_FILENAME }} + file: ${{ env.BUILD_DIR }}/${{ env.ZIP_FILENAME }} aws_account_id: ${{ secrets.AWS_ACCOUNT_ID }} s3_key: ${{ env.S3_KEY }} diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 6388aef..f8d5243 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -12,6 +12,17 @@ jobs: uses: actions/checkout@v6 id: code-checkout + - name: Setup Node.js 📦 + uses: actions/setup-node@v6 + with: + node-version: '22' + + - name: Build nacc2 JavaScript 🔨 + working-directory: nacc2 + run: | + npm ci + npm run build + - name: Validate composer.json and composer.lock 🎼 id: composer-validate run: composer validate diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a7c51b8..f82da03 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,49 +11,31 @@ jobs: id-token: write contents: write env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - WORDPRESS_USERNAME: ${{ secrets.WORDPRESS_USERNAME }} - WORDPRESS_PASSWORD: ${{ secrets.WORDPRESS_PASSWORD }} BUILD_DIR: build - DIST_DIR_S3_JS: dist_naccjs/s3 - DIST_DIR_S3: dist/s3 - DIST_DIR_GITHUB_JS: dist_naccjs/github - DIST_DIR_GITHUB: dist/github - GITHUB_RELEASE_FILENAME_JS: naccjs.zip - GITHUB_RELEASE_FILENAME: nacc-wordpress-plugin.zip - S3_KEY_JS: naccjs - S3_KEY: nacc-wordpress-plugin - PLUGIN: nacc-wordpress-plugin - MAINFILE: nacc-wordpress-plugin.php + ZIP_FILENAME_JS: naccjs.zip + ZIP_FILENAME: nacc-wordpress-plugin.zip steps: - name: Check out code 🛒 uses: actions/checkout@v6 - - name: Build nacc zip file 🔧 + - name: Setup Node.js 📦 + uses: actions/setup-node@v6 + with: + node-version: '22' + + - name: Build nacc2 JavaScript 🔨 + working-directory: nacc2 run: | - export ZIP_FILENAME_JS=naccjs-build${GITHUB_RUN_NUMBER}-${GITHUB_SHA}.zip - echo "ZIP_FILENAME_JS=${ZIP_FILENAME_JS}" >> $GITHUB_ENV - export ZIP_FILENAME=nacc-wordpress-plugin-build${GITHUB_RUN_NUMBER}-${GITHUB_SHA}.zip - echo "ZIP_FILENAME=${ZIP_FILENAME}" >> $GITHUB_ENV - echo "GITHUB_RELEASE_FILENAME_JS=${GITHUB_RELEASE_FILENAME_JS}" >> $GITHUB_ENV - echo "GITHUB_RELEASE_FILENAME=${GITHUB_RELEASE_FILENAME}" >> $GITHUB_ENV - echo "GITHUB_RELEASE_PATH_JS=${DIST_DIR_GITHUB_JS}/${GITHUB_RELEASE_FILENAME_JS}" >> $GITHUB_ENV - echo "GITHUB_RELEASE_PATH=${DIST_DIR_GITHUB}/${GITHUB_RELEASE_FILENAME}" >> $GITHUB_ENV - find ./ -type d | xargs chmod 755 - find ./ -name '*.php' | xargs chmod 644 - git archive --format=zip --output=${ZIP_FILENAME} ${GITHUB_SHA} - zip -r ${ZIP_FILENAME_JS} nacc2/* - TAG="${GITHUB_REF##*/}" - mkdir -p $BUILD_DIR && mv $ZIP_FILENAME_JS $BUILD_DIR/ && mv $ZIP_FILENAME $BUILD_DIR/ - mkdir -p $DIST_DIR_S3_JS && cp $BUILD_DIR/$ZIP_FILENAME_JS $DIST_DIR_S3_JS/$ZIP_FILENAME_JS - mkdir -p $DIST_DIR_GITHUB_JS && cp $BUILD_DIR/$ZIP_FILENAME_JS $DIST_DIR_GITHUB_JS/$GITHUB_RELEASE_FILENAME_JS - mkdir -p $DIST_DIR_S3 && cp $BUILD_DIR/$ZIP_FILENAME $DIST_DIR_S3/$ZIP_FILENAME - mkdir -p $DIST_DIR_GITHUB && cp $BUILD_DIR/$ZIP_FILENAME $DIST_DIR_GITHUB/$GITHUB_RELEASE_FILENAME + npm ci + npm run build + + - name: Build nacc zip file 🔧 + run: make build - name: Generate Release Notes 📝 if: github.ref_type == 'tag' run: | - curl -LO https://raw.githubusercontent.com/bmlt-enabled/release-notes-tool/master/gh-release-notes.sh + curl -LO https://raw.githubusercontent.com/bmlt-enabled/release-notes-tool/main/gh-release-notes.sh chmod +x gh-release-notes.sh ./gh-release-notes.sh readme.txt "wp" RELEASE_TYPE=$(if [[ "$GITHUB_REF_NAME" =~ "beta" ]]; then echo "true"; else echo "false"; fi) @@ -63,7 +45,7 @@ jobs: uses: ncipollo/release-action@v1 if: github.ref_type == 'tag' with: - artifacts: ${{ env.GITHUB_RELEASE_PATH }},${{ env.GITHUB_RELEASE_PATH_JS }} + artifacts: build/${{ env.ZIP_FILENAME }},build/${{ env.ZIP_FILENAME_JS }} bodyFile: "changelog.txt" prerelease: ${{ env.RELEASE_TYPE }} @@ -71,9 +53,13 @@ jobs: if: "!contains(github.ref, 'beta')" id: wordpress-stable-release env: - WORDPRESS_USERNAME : ${{ secrets.WORDPRESS_USERNAME }} - WORDPRESS_PASSWORD : ${{ secrets.WORDPRESS_PASSWORD }} + WORDPRESS_USERNAME: ${{ secrets.WORDPRESS_USERNAME }} + WORDPRESS_PASSWORD: ${{ secrets.WORDPRESS_PASSWORD }} + DIST_DIR_GITHUB: ${{ env.BUILD_DIR }} + GITHUB_RELEASE_FILENAME: ${{ env.ZIP_FILENAME }} + PLUGIN: nacc-wordpress-plugin + MAINFILE: nacc-wordpress-plugin.php run: | - curl -LO https://raw.githubusercontent.com/bmlt-enabled/bmlt-wordpress-deploy/master/deploy-wordpress.sh + curl -LO https://raw.githubusercontent.com/bmlt-enabled/bmlt-wordpress-deploy/main/deploy-wordpress.sh chmod +x deploy-wordpress.sh ./deploy-wordpress.sh diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 5507551..996e2ff 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -23,10 +23,17 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + - name: Build nacc2 JavaScript + working-directory: nacc2 + run: | + npm ci + npm run build - name: Setup Pages uses: actions/configure-pages@v5 -# - name: Build -# run: make docs - name: Upload artifact uses: actions/upload-pages-artifact@v4 with: diff --git a/.gitignore b/.gitignore index 6d3b042..dfcb712 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ build/ *.zip .phpdoc/ docs/ +nacc2/node_modules/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9172962..deca876 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,20 +12,79 @@ Take a look at the [issues](https://github.com/bmlt-enabled/nacc/issues) for bug Once your pull request is merged, it will be released in the next version. -## Local Development Setup +## Prerequisites -To get things going in your local environment: +- PHP 8.0+ +- [Composer](https://getcomposer.org/) +- [Node.js](https://nodejs.org/) v18+ and npm +- [Docker](https://www.docker.com/) (optional, for local WordPress environment) -1. Run the following command to start the development environment: - ```bash - docker compose up - ``` +## Setup -2. Set up your WordPress installation and remember your admin password. +```bash +# Install PHP dev dependencies (PHPCS) +composer install + +# Build nacc2 JavaScript +cd nacc2 && npm install && npm run build && cd .. +``` + +## Local Development + +### WordPress Environment (Docker) + +```bash +make dev # starts WordPress + MariaDB via Docker +make mysql # connect to the database +make bash # shell into the WordPress container +``` -3. Once it's running, log in to the admin panel and activate the "NACC WordPress Plugin" plugin. +The plugin is mounted into the container. After starting, activate it in the WordPress admin and add the `[nacc]` shortcode to a page. -4. You can now make edits to the `nacc-wordpress-plugin.php` file and changes will take effect instantly. +### nacc2 JavaScript + +The calculator UI lives in `nacc2/` as ES6 modules built with [Vite](https://vite.dev/). + +```bash +cd nacc2 +npm install +npm run build # builds nacc2/nacc.js (IIFE bundle) +``` + +Source files are in `nacc2/src/`. After editing, run `npm run build` to regenerate `nacc2/nacc.js`. The built file is committed to the repo so the WordPress plugin works without a build step during development. + +### Project Structure + +``` +nacc-wordpress-plugin.php # WordPress plugin (shortcode, settings, asset loading) +readme.txt # WordPress.org plugin readme +nacc2/ + nacc.js # built output (committed) + nacc.css # styles and themes + images/ # keytag images by language + src/ # ES6 source modules + nacc.js # main NACC class + calc.js # date calculation logic + sprintf.js # string formatting + lang/ # one file per language + package.json + vite.config.js +``` + +### How the Plugin Integrates nacc2 + +The WordPress plugin: +1. Enqueues `nacc2/nacc.css` and `nacc2/nacc.js` via `wp_enqueue_style`/`wp_enqueue_script` +2. Renders a `
` via the `[nacc]` shortcode +3. Injects `new NACC('nacc_container', theme, lang, layout, special, siteURI)` as inline JS +4. `siteURI` is the full URL to `nacc2/` so keytag images resolve correctly + +### Adding a New Language + +1. Create `nacc2/src/lang/xx.js` following the pattern in `nacc2/src/lang/en.js` (all string keys must be present) +2. Add the export to `nacc2/src/lang/index.js` +3. Add keytag images to `nacc2/images/xx/` (same filenames as other language directories) +4. Rebuild with `cd nacc2 && npm run build` ## Code Standards @@ -35,19 +94,30 @@ Please make note of the `.editorconfig` file and adhere to it, as this will mini This project uses PHP CodeSniffer (phpcs) for code style enforcement. The coding standards are configured in `.phpcs.xml`. -To check your code for style violations: -```bash -make lint -``` - -To automatically fix code style issues: ```bash -make fmt +make lint # check PHP code style +make fmt # auto-fix PHP style issues ``` Make sure to run these commands before submitting your pull request to ensure your code adheres to the project's coding standards. -## Release Tagging +## Release Process + +Releases are triggered by pushing a git tag: + +1. Update the version in `nacc-wordpress-plugin.php` header and `readme.txt` stable tag +2. Add a changelog entry to `readme.txt` +3. Commit, tag, and push: `git tag 5.2.0 && git push origin 5.2.0` + +The `release.yml` workflow will: +- Build `nacc2/nacc.js` from source +- Create a WordPress plugin zip (`nacc-wordpress-plugin.zip`) +- Create a standalone JS zip (`naccjs.zip`) +- Upload both to S3 and create a GitHub Release +- Deploy to WordPress.org (non-beta tags only) + +Tags containing "beta" (e.g. `5.2.0-beta1`) create pre-releases and skip WordPress.org deployment. + +## What Ships in the WordPress Plugin -- If a release is tagged with `beta`, it will be pushed as a zip file in the GitHub release -- If it's not tagged as beta, it will be published to the WordPress directory as a release in addition to GitHub +The `.gitattributes` file controls what's included in the distribution zip. Dev files (`nacc2/src/`, `nacc2/package.json`, `nacc2/vite.config.js`, `nacc2/node_modules/`, etc.) are excluded. Only `nacc2/nacc.js`, `nacc2/nacc.css`, `nacc2/images/`, and `nacc2/icon.png` are shipped. diff --git a/Makefile b/Makefile index 72585e4..bd48dbc 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,29 @@ COMMIT := $(shell git rev-parse --short=8 HEAD) ZIP_FILENAME := $(or $(ZIP_FILENAME), $(shell echo "$${PWD\#\#*/}.zip")) +ZIP_FILENAME_JS := $(or $(ZIP_FILENAME_JS), naccjs.zip) BUILD_DIR := $(or $(BUILD_DIR),"build") VENDOR_AUTOLOAD := vendor/autoload.php help: ## Print the help documentation @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' +.PHONY: nacc2 +nacc2: ## Build nacc2 JavaScript + cd nacc2 && npm install && npm run build + +.PHONY: build-js +build-js: ## Build nacc2 JS-only zip (run make nacc2 first) + zip -r ${ZIP_FILENAME_JS} nacc2/* -x "nacc2/node_modules/*" "nacc2/src/*" "nacc2/package.json" "nacc2/package-lock.json" "nacc2/vite.config.js" + mkdir -p ${BUILD_DIR} && mv ${ZIP_FILENAME_JS} ${BUILD_DIR}/ + .PHONY: build -build: ## Build +build: build-js ## Build WordPress plugin zip (run make nacc2 first) git archive --format=zip --output=${ZIP_FILENAME} $(COMMIT) mkdir -p ${BUILD_DIR} && mv ${ZIP_FILENAME} ${BUILD_DIR}/ +.PHONY: all +all: nacc2 build ## Build everything + .PHONY: clean clean: ## clean rm -rf build dist diff --git a/nacc-wordpress-plugin.php b/nacc-wordpress-plugin.php index 0a32d98..2efa774 100644 --- a/nacc-wordpress-plugin.php +++ b/nacc-wordpress-plugin.php @@ -6,7 +6,7 @@ * Install: Drop this directory in the "wp-content/plugins/" directory and activate it. You need to specify "[NACC]" in the code section of a page or a post. * Contributors: BMLTGuy, pjaudiomv, bmltenabled * Authors: bmltenabled - * Version: 5.1.1 + * Version: 5.2.0 * Requires PHP: 8.0 * Requires at least: 5.3 * License: GPL v2 or later diff --git a/nacc2/README.md b/nacc2/README.md index bcfe956..fe99419 100644 --- a/nacc2/README.md +++ b/nacc2/README.md @@ -1,184 +1,93 @@ DESCRIPTION =========== -This version of the NA Cleantime Calculator has been redesigned from the ground up to be entirely browser-based, which means that no PHP server is required (good for site builders, like [Wix](http://wix.com), [Weebly](http://weebly.com), [Sitebuilder](http://sitebuilder.com) or [SquareSpace](http://squarespace.com)). +The NA Cleantime Calculator (NACC) is a browser-based calculator for Narcotics Anonymous cleantime. It requires no server — just include the built `nacc.js` and `nacc.css` files. -It is extremely fast and complete. +The visitor uses three dropdown menus to select a clean date and clicks "Calculate." They are shown a summary of their cleantime (total days, and years/months/days), along with the keytags they would have earned. Tags can be displayed in two layouts: -The site visitor uses three popup menus to select a clean date, and then they activate a "Calculate" button. +- **Linear** — tag fronts shown in a row (usually the NA logo). +- **Tabular** — all tags side-by-side showing the rear (with text). -At that point, they are presented with a statement that summarizes their cleantime (how many total days, years, months and days, accounting for the intervening years). - -They are also shown a list of the tags they would have earned by now. There are two layout for these tags: - -- Linear, in which the tag front (usually a simple NA logo) is displayed. -- Tabular, in which all of the tags are laid out side-by-side, and this layout displays the rear of the tags (with the text). - -The NACC is localized. The current version supports only the standard [Gregorian calendar](https://en.wikipedia.org/wiki/Gregorian_calendar) (so that means it is not useful for Iran). -The tag images are provided for the given localization. - -There are a number of new "specialty" tags, for long-term cleantime (like the purple "Decades" tag). The visitor can choose to display these. - -[You can see it in action here.](http://littlegreenviper.net/nacc/index.html) +The NACC is localized and supports the standard [Gregorian calendar](https://en.wikipedia.org/wiki/Gregorian_calendar). Specialty tags for long-term cleantime (e.g. the "Decades" tag) can be optionally shown. User preferences (clean date, layout, special tags) are persisted in `localStorage`. IMPORTANT NOTE ============== -The name "Narcotics Anonymous" and the stylized NA symbol (displayed on the front of the tags) are registered trademarks of [Narcotics Anonymous World Services, Inc. (NAWS)](http://na.org), and cannot be used without [permission from NAWS](http://na.org/?ID=legal-bulletins-fipt). If you are using this Web app as part of a registered Service body site, then this permission is implicit. However, be aware that any other use needs permission from NAWS. +The name "Narcotics Anonymous" and the stylized NA symbol (displayed on the front of the tags) are registered trademarks of [Narcotics Anonymous World Services, Inc. (NAWS)](http://na.org), and cannot be used without [permission from NAWS](https://na.org/service-material/fipt/fipt-faq/). If you are using this web app as part of a registered Service body site, then this permission is implicit. -Basically, you need to be a Registered NA Service Body to use this Web app as is. If you are not a Registered NA Service Body, then you should replace the "*XX*_Front.png" files with the equivalent "*XX*.png" file (Delete the "*XX*_Front.png" file, duplicate the "*XX*.png" file, and rename the duplicate "*XX*_Front.png", which will result in the same image in two different files). This needs to be done in order to prevent display of the tag fronts, which contain the trademarked NA Symbol. +If you are not a Registered NA Service Body, replace each `XX_Front.png` file with a copy of the corresponding `XX.png` file to avoid displaying the trademarked NA symbol on tag fronts. -NACC is not, in itself, an official NA Service, but is designed specifically to be implemented by NA Service bodies. +NACC is not an official NA Service, but is designed specifically to be implemented by NA Service bodies. REQUIREMENTS ============ -This requires a late-version browser. It doesn't have a lot of frou-frou to support older browsers. -It does not require an active-tech server (like [PHP](http://php.net) or [ASP](http://asp.net)), but will allow you to use PHP to optimize the page load. -If you like, you can certainly overload the CSS to support older browsers. It would not be so easy to modify the JavaScript. +A modern browser is required. No server-side technology is needed. + +BUILDING FROM SOURCE +==================== +The JavaScript is built using [Vite](https://vitejs.dev/). Node.js 20+ is required. + + cd nacc2 + npm install + npm run build + +This produces `nacc.js` in the `nacc2/` directory. INSTALLATION ============ -Standard Installation ---------------------- -This is a "pure browser" implementation of NACC. It requires absolutely no server-based components (but will support PHP, if provided). -In order to use this, you link to the "nacc.css" and import the "nacc.js" file in the `` element: +Include `nacc.css` and `nacc.js` in your page, create a container element, and instantiate the calculator: - . - . - . - . - . - . - -In the `` element, you then create an empty block-level element (usually a div), with a unique page ID, then reference that ID upon instantiating a new NACC() instance. - -Example: - - . - . -
- . - . +
- . - . -Look at the "index.html" file for a more verbose version of this. - -You can call it with up to 9 input parameters, which must be called in the following order: - -1. A string, with the DOM ID of the DOM element that will contain this instance. It will usually be an empty `
` element, but can be any block-level DOM element. This is the only **required** parameter. -2. A string, indicating the style. Leave blank/null for default gray. The "BT" style would be 'NA-BT', as that is the CSS name it has been given. -3. A string, with the language selector (Example: 'en' -the default-, 'es' -Spanish-, etc.). Currently, these languages are supported: - - English ("en"). This is also the default. - - Portuguese ("pt"). - - Spanish ("es"). - - Simplified Chinese ("zh-Hans"). - - Traditional Chinese ("zh-Hant"). -4. A string, with the tag layout (either "linear" or "tabular" -default is "linear"). -5. A Boolean ("1", '' or "0"). If true, then the "specialty" (over 2 years) tags are displayed. Default is false. -6. A string, containing a path (relative to the execution path) to the main directory. Leave as '' or null for standard implementations. -7. If you want the instance to immediately appear with a calculation, then ALL 3 of these must be specified. - - An initial calculation year (integer -entire year, like "1953"). - - An initial calculation month (integer 1-12). - - An initial calculation day (integer 1-31). - -You can leave unused _successive_ parameters out, but _interim_ ones need to be specified as empty, 0 or null. - -For example, these are all valid: - - NACC('some_DOM_ID'); - NACC('some_DOM_ID', 'NA-BT'); - NACC('some_DOM_ID', 'NA-BT', 'es'); - NACC('some_DOM_ID', 'NA-BT', 'es', 'tabular', 1); - NACC('some_DOM_ID', 'NA-BT', 'es', 'tabular', 1, 'http://littlegreenviper.net/nacc/', 1980, 9, 1); - NACC('some_DOM_ID', null, 'es'); - NACC('some_DOM_ID', '', 'es'); - NACC('some_DOM_ID', null, 'es', 'tabular', 1); - NACC('some_DOM_ID', null, '', 'tabular', 1); - NACC('some_DOM_ID', 0, '', null, 1); - NACC('some_DOM_ID', 0, 0, 0, 0, 0, 1980, 9, 1); - -But these are not: - - NACC('some_DOM_ID', 'es'); - NACC('some_DOM_ID', 'tabular'); - NACC('some_DOM_ID', 'NA-BT', 'es', 1); - -This is valid, but won't do anything (because the date is not complete): - - NACC('some_DOM_ID', null, null, null, null, null, 1980, 9); - -However, if you specified the previous, and then called it with the following URI: - - /index.html?NACC-day=1 - -It would show an initial search result (for September 1, 1980). - -You can also call the file with GET (not POST) parameters (These can be provided in any order): - -- "NACC-style" for Style -- "NACC-lang" for Language -- "NACC-tag-layout" for Tag Layout -- "NACC-special-tags" for Show Special Tags -- "NACC-dir-root" for the Directory Root -- "NACC-year" for Year -- "NACC-month" for Month -- "NACC-day" for Day - -If these are provided, they will override any parameters passed into the function when it was created. - -Here is an example: [http://littlegreenviper.net/nacc/index.html?NACC-style=NACC-BT&NACC-tag-layout=tabular&NACC-special-tags=1](http://littlegreenviper.net/nacc/index.html?NACC-style=NACC-BT&NACC-tag-layout=tabular&NACC-special-tags=1) - -If You Have A PHP Server ------------------------- -If you have a PHP server available, you can optimize the page load by using the PHP "file optimizer script," which is invoked from within the `` element, like so: +See `index.html` for a working example with language and theme selectors. - - . - . - - . - . - - . - . - +CONSTRUCTOR PARAMETERS +====================== +`new NACC(containerId, style, lang, tagLayout, showSpecialTags, directoryRoot, year, month, day)` -Look at the "index.php" file for a more verbose version of this. - -For security reasons, this script must be in the same directory as the files it will include. +1. **containerId** *(required)* — DOM ID of the container element. +2. **style** — Theme class name (e.g. `'NACC-BT'`). Leave null/empty for default gray. +3. **lang** — Language code. Supported: `'en'` (default), `'es'`, `'pt'`, `'it'`, `'zh-Hans'`, `'zh-Hant'`. +4. **tagLayout** — `'linear'` (default) or `'tabular'`. +5. **showSpecialTags** — Boolean. Show specialty long-term tags. Default `false`. +6. **directoryRoot** — Path to the `nacc2/` directory. Leave null/empty for standard use. +7. **year**, **month**, **day** — Initial date for an immediate calculation. All three must be provided together. -It can substantially reduce the size of the transmitted JavaScript and CSS files. +Later parameters can be omitted, but intermediate ones must be passed as `null` or `''`. -TESTING -======= +GET PARAMETERS +============== +These URL parameters override constructor arguments and can be provided in any order: -There is [a very simple unit test](http://littlegreenviper.net/nacc/unit_test.html) available. This is the "unit_test.html" file. -It tests the NACC by sending GET parameters. +- `NACC-style` — Theme +- `NACC-lang` — Language +- `NACC-tag-layout` — Tag layout +- `NACC-special-tags` — Show specialty tags +- `NACC-dir-root` — Directory root +- `NACC-year`, `NACC-month`, `NACC-day` — Initial date LICENSING ========= -Most of the project is a [GPL V3](http://www.gnu.org/licenses/licenses.html#GPL) license. It is 100% open source, and the repository is available in full on [Bitbucket](https://bitbucket.org/bmlt/nacc2). - -Please read the "IMPORTANT NOTE", above, for information about [NA trademarks](http://na.org/?ID=legal-bulletins-fipt). These cannot be reassigned via the GPL. +Most of the project is licensed under [GPL V3](http://www.gnu.org/licenses/licenses.html#GPL). The repository is available on [GitHub](https://github.com/bmlt-enabled/nacc). -NACC is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by [the Free Software Foundation](http://fsf.org/), either version 3 of the License, or (at your option) any later version. - -NACC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See [the GNU General Public License](http://www.gnu.org/licenses/licenses.html#GPL) for more details. - -You should have received a copy of the GNU General Public License along with the code. If not, see [the GPL License Page](http://www.gnu.org/licenses/). +Please read the **IMPORTANT NOTE** above regarding [NA trademarks](http://na.org/?ID=legal-bulletins-fipt), which cannot be reassigned via the GPL. CHANGELIST ========== -***Version 2.0.1* ** *-November 26, 2025* +***Version 2.1.0*** + +- Migrated to ES6 modules, built with Vite +- Added localStorage persistence for layout and special tags settings + +***Version 2.0.1** — November 26, 2025* -- Added localStorage support to automatically save and restore the user's clean date +- Added localStorage support to save and restore the user's clean date -***Version 2.0.0* ** *-TBD* +***Version 2.0.0*** - Initial Version diff --git a/nacc2/include-stripper.php b/nacc2/include-stripper.php deleted file mode 100644 index 8113510..0000000 --- a/nacc2/include-stripper.php +++ /dev/null @@ -1,49 +0,0 @@ -. -*/ -//define ( '__DEBUG_MODE__', 1 ); // Uncomment to make the CSS and JavaScript easier to trace (and less efficient). - $pathname = $_GET['filename']; -if ( ! preg_match( '|/|', $pathname ) ) { - if ( preg_match( '/.*?\.(js|css)$/', $pathname ) ) { - $pathname = __DIR__ . "/$pathname"; - $opt = file_get_contents( $pathname ); - if ( ! defined( '__DEBUG_MODE__' ) ) { - $opt = preg_replace( '|\/\*.*?\*\/|s', '', $opt ); - $opt = preg_replace( '#(?
@@ -73,7 +73,7 @@ - +
- +
- - - -
- - - - diff --git a/nacc2/nacc.js b/nacc2/nacc.js index 4e492b7..ddae6ab 100644 --- a/nacc2/nacc.js +++ b/nacc2/nacc.js @@ -1,1669 +1 @@ -/***************************************************************************/ -/** - The NA Cleatime Calculator (NACC) - - This is the second version of the NACC. It is designed to be a browser-based - cleantime calculator (meaning it relies solely on HTML, JavaScript and CSS). - - NACC is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - NACC is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this code. If not, see . -*/ - -/******************************************************************************************** -######################################### MAIN CONTEXT ###################################### -********************************************************************************************/ -/** - \brief This is the main class function for the NACC. - - \param inContainerElementID A DOM ID to the DOM element that will contain this instance. It will usually be an empty div element, but can be any block-level DOM element. - \param inStyle This is the style (leave blank/null for default gray. The "BT" style would be 'NA-BT', as that is the CSS name it has been given). - \param inLang A string, with the language selector (Example: 'en' -the default-, 'es' -Spanish-, etc.). - \param inTagLayout The tag layout (either 'linear' or 'tabular' -default is 'linear') - \param inShowSpecialTags If true, then the "specialty" (over 2 years) tags are displayed. Default is false. - \param inDirectoryRoot This is a path (relative to the execution path) to the main directory. Leave as '' or null for standard implementations. - \param inYear An initial calculation year (integer -entire year, like '1953'). If you want the instance to immediately appear with a calculation, then ALL 3 of these must be specified. - \param inMonth An initial calculation month (integer 1-12). If you want the instance to immediately appear with a calculation, then ALL 3 of these must be specified. - \param inDay An initial calculation day (integer 1-31). If you want the instance to immediately appear with a calculation, then ALL 3 of these must be specified. -*/ -function NACC(inContainerElementID, inStyle, inLang, inTagLayout, inShowSpecialTags, inDirectoryRoot, inYear, inMonth, inDay) { - - /******************************************************************************************** - ########################################## PROPERTIES ####################################### - ********************************************************************************************/ - /// This contains any GET parameters. - var m_getParameters = null; - /// This contains any directory offset (used when in plugin situations). - var m_relative_directory_root = null; - /// This is the style selector. - var m_style_selector = null; - /// This is the language selector. - var m_lang_selector = null; - /// This is an array, with all the language-specific strings. - var m_lang = null; - /// This specifies the keytag layout. - var m_keytag_layout = null; - /// This specifies whether the specialty keytags are displayed. - var m_keytag_special = null; - /// This is the object that "owns" this instance. - var m_my_container = null; - /// This is the form that contains the popups. - var m_my_form = null; - /// This is the prompt above the popups. - var m_my_prompt = null; - /// This is the fieldset that contains the popups and the results. - var m_my_fieldset = null; - /// This is the fieldset legend that contains the popups. - var m_my_legend = null; - /// This is the fieldset legend internal div that contains the popups. - var m_my_legend_div = null; - /// This is a div that will contain the popups. - var m_popup_container = null; - /// This is the month popup. - var m_month_popup = null; - /// This is the day of the month popup. - var m_day_popup = null; - /// This is the year popup. - var m_year_popup = null; - /// This is the calculate button. - var m_calculate_button = null; - /// This is the calculate results div. - var m_calculation_results_div = null; - /// This is the calculate results display toggle button. - var m_calculation_results_display_toggle_button = null; - /// This is the calculate results special tags display checkbox. - var m_calculation_results_show_special_tags_checkbox = null; - /// This is the label for the calculate results special tags display checkbox. - var m_calculation_results_show_special_tags_checkbox_label = null; - /// This is the calculate results text div. - var m_calculation_results_text_div = null; - /// This is the calculate results keytags div. - var m_calculation_results_keytags_div = null; - - /************************************/ - /* LOCALIZATION */ - /************************************/ - this.m_lang = Array(); - - // Make sure that we have a valid container element. - if ( inContainerElementID && document.getElementById(inContainerElementID) ) { - /************************************/ - /* ENGLISH */ - /************************************/ - /** We need to instantiate an object for each language. The key needs to be the language selector. */ - this.m_lang['en'] = new Object(); - - /* The following are strings to be assigned to each language. */ - - /** This is the header, at the top. */ - this.m_lang['en'].section_title = 'NA Cleantime Calculator'; - /** This is the prompt over the popup menus. */ - this.m_lang['en'].prompt = 'Please enter your Clean Date'; - /** This is the text for the calculate button. */ - this.m_lang['en'].calculate_button_text = 'Calculate'; - /** This is the text for the change layout button. */ - this.m_lang['en'].change_layout_button_text = 'Change Tag Layout'; - /** This is the text for the show special tags checkbox. */ - this.m_lang['en'].change_use_special_tags_label = 'Show Special Tags'; - /** These are the months, spelled out. */ - this.m_lang['en'].months = Array( - "ERROR", - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December" - ); - /** These are for the top (days) blurb. */ - this.m_lang['en'].result_invalid = 'Please select a valid clean date!'; - - this.m_lang['en'].result_1_day = 'You have been clean for 1 day!'; - this.m_lang['en'].result_days_format = 'You have been clean for %d days!'; - - /** These are for the second (months, years and days) blurb. */ - this.m_lang['en'].result_months_format = 'This is %d months.'; - this.m_lang['en'].result_months_and_1_day_format = 'This is %d months and 1 day.'; - this.m_lang['en'].result_months_and_days_format = 'This is %d months and %d days.'; - this.m_lang['en'].result_1_year = 'This is 1 year.'; - this.m_lang['en'].result_1_year_and_1_day = 'This is 1 year and 1 day.'; - this.m_lang['en'].result_1_year_and_1_month = 'This is 1 year and 1 month.'; - this.m_lang['en'].result_1_year_1_month_and_1_day = 'This is 1 year, 1 month and 1 day.'; - this.m_lang['en'].result_1_year_months_and_1_day_format = 'This is 1 year, %d months and 1 day.'; - this.m_lang['en'].result_1_year_months_and_days_format = 'This is 1 year, %d months and %d days.'; - this.m_lang['en'].result_1_year_days_format = 'This is 1 year and %d days.'; - this.m_lang['en'].result_years_format = 'This is %d years.'; - this.m_lang['en'].result_years_months_format = 'This is %d years and %d months.'; - this.m_lang['en'].result_years_1_month_and_1_day_format = 'This is %d years, 1 month and 1 day.'; - this.m_lang['en'].result_years_months_and_1_day_format = 'This is %d years, %d months and 1 day.'; - this.m_lang['en'].result_years_and_1_month_format = 'This is %d years and 1 month.'; - this.m_lang['en'].result_years_and_1_day_format = 'This is %d years and 1 day.'; - this.m_lang['en'].result_years_and_days_format = 'This is %d years and %d days.'; - this.m_lang['en'].result_years_1_month_and_days_format = 'This is %d years, 1 month and %d days.'; - this.m_lang['en'].result_years_months_and_days_format = 'This is %d years, %d months and %d days.'; - - /************************************/ - /* SPANISH */ - /************************************/ - this.m_lang['es'] = new Object(); - - /* The following are strings to be assigned to each language. */ - - /** This is the header, at the top. */ - this.m_lang['es'].section_title = 'Calculadora de Tiempo Limpio NA'; - /** This is the prompt over the popup menus. */ - this.m_lang['es'].prompt = 'Por favor, entra tu fecha de inicio.'; - /** This is the text for the calculate button. */ - this.m_lang['es'].calculate_button_text = 'Calcular'; - /** This is the text for the change layout button. */ - this.m_lang['es'].change_layout_button_text = 'Cambiar los llaveros de posición.'; - /** This is the text for the show special tags checkbox. */ - this.m_lang['es'].change_use_special_tags_label = 'Mostrar Llaveros Especiales.'; - /** These are the months, spelled out. */ - this.m_lang['es'].months = Array( - "ERROR", - "Enero", - "Febrero", - "Marzo", - "Abril", - "Mayo", - "Junio", - "Julio", - "Agosto", - "Septiembre", - "Octubre", - "Noviembre", - "Diciembre" - ); - this.m_lang['es'].result_invalid = '¡Por favor, entra una fecha de inicio válido!'; - - this.m_lang['es'].result_1_day = '¡Has estado limpio por 1 día!'; - this.m_lang['es'].result_days_format = '¡Has estado limpio por %d días!'; - - /** These are for the second (months, years and days) blurb. */ - this.m_lang['es'].result_months_format = 'Esto suma un total de %d meses.'; - this.m_lang['es'].result_months_and_1_day_format = 'Esto suma un total de %d meses y un día.'; - this.m_lang['es'].result_months_and_days_format = 'Esto suma un total de %d meses y %d días.'; - this.m_lang['es'].result_1_year = 'Esto suma un total de 1 año.'; - this.m_lang['es'].result_1_year_and_1_day = 'Esto suma un total de 1 año y 1 día.'; - this.m_lang['es'].result_1_year_and_1_month = 'Esto suma un total de 1 año y 1 mes.'; - this.m_lang['es'].result_1_year_1_month_and_1_day = 'Esto suma un total de 1 año, 1 mes y 1 día.'; - this.m_lang['es'].result_1_year_months_and_1_day_format = 'Esto suma un total de 1 año, %d meses y 1 día.'; - this.m_lang['es'].result_1_year_months_and_days_format = 'Esto suma un total de 1 año, %d meses y %d días.'; - this.m_lang['es'].result_1_year_days_format = 'Esto suma un total de 1 año y %d días.'; - this.m_lang['es'].result_years_format = 'Esto suma un total de %d años.'; - this.m_lang['es'].result_years_months_format = 'Esto suma un total de %d años y %d meses.'; - this.m_lang['es'].result_years_1_month_and_1_day_format = 'Esto suma un total de %d años, 1 mes y 1 día.'; - this.m_lang['es'].result_years_months_and_1_day_format = 'Esto suma un total de %d años, %d meses y 1 día.'; - this.m_lang['es'].result_years_and_1_month_format = 'Esto suma un total de %d años y 1 mes.'; - this.m_lang['es'].result_years_and_1_day_format = 'Esto suma un total de %d años y 1 día.'; - this.m_lang['es'].result_years_and_days_format = 'Esto suma un total de %d años y %d días.'; - this.m_lang['es'].result_years_1_month_and_days_format = 'Esto suma un total de %d años, 1 mes y %d días.'; - this.m_lang['es'].result_years_months_and_days_format = 'Esto suma un total de %d años, %d meses y %d días.'; - - /************************************/ - /* SIMPLIFIED CHINESE */ - /************************************/ - this.m_lang['zh-Hans'] = new Object(); - - /* The following are strings to be assigned to each language. */ - - /** This is the header, at the top. */ - this.m_lang['zh-Hans'].section_title = 'NA 清醒时间计算器'; - /** This is the prompt over the popup menus. */ - this.m_lang['zh-Hans'].prompt = '请输入您的清醒日期'; - /** This is the text for the calculate button. */ - this.m_lang['zh-Hans'].calculate_button_text = '计算'; - /** This is the text for the change layout button. */ - this.m_lang['zh-Hans'].change_layout_button_text = '更改钥匙扣设置'; - /** This is the text for the show special tags checkbox. */ - this.m_lang['zh-Hans'].change_use_special_tags_label = '显示特殊钥匙扣'; - /** These are the months, spelled out. */ - this.m_lang['zh-Hans'].months = Array( - "错误", - "1月", - "2月", - "3月", - "4月", - "5月", - "6月", - "7月", - "8月", - "9月", - "10月", - "11月", - "12月" - ); - this.m_lang['zh-Hans'].result_invalid = '请选择一个有效的清醒日期'; - - this.m_lang['zh-Hans'].result_1_day = '您已经清醒一天了!'; - this.m_lang['zh-Hans'].result_days_format = '您已经清醒%d天了! '; - - /** These are for the second (months, years and days) blurb. */ - this.m_lang['zh-Hans'].result_months_format = '这是%d个月了'; - this.m_lang['zh-Hans'].result_months_and_1_day_format = '这是%d个月零一天了.'; - this.m_lang['zh-Hans'].result_months_and_days_format = '这是%d个月%d天了.'; - this.m_lang['zh-Hans'].result_1_year = '这是一年了.'; - this.m_lang['zh-Hans'].result_1_year_and_1_day = '这是一年零一天了.'; - this.m_lang['zh-Hans'].result_1_year_and_1_month = '这是一年一个月了. '; - this.m_lang['zh-Hans'].result_1_year_1_month_and_1_day = '这是一年一个月零一天了. '; - this.m_lang['zh-Hans'].result_1_year_months_and_1_day_format = '这是一年,%d个月零一天了.'; - this.m_lang['zh-Hans'].result_1_year_days_format = '这是一年,%d天了. '; - this.m_lang['zh-Hans'].result_years_format = '这是%d年了. '; - this.m_lang['zh-Hans'].result_years_months_format = '这是%d年%d月了. '; - this.m_lang['zh-Hans'].result_years_1_month_and_1_day_format = '这是%d年,一个月零一天了.'; - this.m_lang['zh-Hans'].result_years_months_and_1_day_format = '这是%d年,%d月零一天了.'; - this.m_lang['zh-Hans'].result_years_and_1_month_format = '这是%d年一个月了. '; - this.m_lang['zh-Hans'].result_years_and_1_day_format = '这是%d年零一天了. '; - this.m_lang['zh-Hans'].result_years_and_days_format = '这是%d年%d天了.'; - this.m_lang['zh-Hans'].result_years_1_month_and_days_format = '这是%d年,一个月%d天了.'; - this.m_lang['zh-Hans'].result_years_months_and_days_format = '这是%d个年,%d个月,%d天了.'; - - /************************************/ - /* TRADITIONAL CHINESE */ - /************************************/ - this.m_lang['zh-Hant'] = new Object(); - - /* The following are strings to be assigned to each language. */ - - /** This is the header, at the top. */ - this.m_lang['zh-Hant'].section_title = 'NA 清醒時間計算器'; - /** This is the prompt over the popup menus. */ - this.m_lang['zh-Hant'].prompt = '請輸入您的清醒日期'; - /** This is the text for the calculate button. */ - this.m_lang['zh-Hant'].calculate_button_text = '計算'; - /** This is the text for the change layout button. */ - this.m_lang['zh-Hant'].change_layout_button_text = '更改鑰匙扣設置'; - /** This is the text for the show special tags checkbox. */ - this.m_lang['zh-Hant'].change_use_special_tags_label = '顯示特殊鑰匙扣'; - /** These are the months, spelled out. */ - this.m_lang['zh-Hant'].months = Array( - "錯誤", - "1月", - "2月", - "3月", - "4月", - "5月", - "6月", - "7月", - "8月", - "9月", - "10月", - "11月", - "12月" - ); - this.m_lang['zh-Hant'].result_invalid = '請選擇一個有效的清醒日期'; - - this.m_lang['zh-Hant'].result_1_day = '您已經清醒一天了!'; - this.m_lang['zh-Hant'].result_days_format = '您已经清醒%d天了! '; - - /** These are for the second (months, years and days) blurb. */ - this.m_lang['zh-Hant'].result_months_format = '這是%d個月了'; - this.m_lang['zh-Hant'].result_months_and_1_day_format = '這是%d個月零一天了.'; - this.m_lang['zh-Hant'].result_months_and_days_format = '這是%d個月%d天了.'; - this.m_lang['zh-Hant'].result_1_year = '這是一年了.'; - this.m_lang['zh-Hant'].result_1_year_and_1_day = '這是一年零一天了.'; - this.m_lang['zh-Hant'].result_1_year_and_1_month = '這是一年一個月了. '; - this.m_lang['zh-Hant'].result_1_year_1_month_and_1_day = '這是一年一個月零一天了. '; - this.m_lang['zh-Hant'].result_1_year_months_and_1_day_format = '這是一年,%d個月零一天了.'; - this.m_lang['zh-Hant'].result_1_year_days_format = '這是一年,%d天了. '; - this.m_lang['zh-Hant'].result_years_format = '這是%d年了. '; - this.m_lang['zh-Hant'].result_years_months_format = '這是%d年%d月了. '; - this.m_lang['zh-Hant'].result_years_1_month_and_1_day_format = '這是%d年,一個月零一天了.'; - this.m_lang['zh-Hant'].result_years_months_and_1_day_format = '這是%d年,%d月零一天了.'; - this.m_lang['zh-Hant'].result_years_and_1_month_format = '這是%d年一個月了. '; - this.m_lang['zh-Hant'].result_years_and_1_day_format = '這是%d年零一天了. '; - this.m_lang['zh-Hant'].result_years_and_days_format = '這是%d年%d天了.'; - this.m_lang['zh-Hant'].result_years_1_month_and_days_format = '這是%d年,一個月%d天了.'; - this.m_lang['zh-Hant'].result_years_months_and_days_format = '這是%d個年,%d個月,%d天了.'; - - /************************************/ - /* ITALIAN */ - /************************************/ - this.m_lang['it'] = new Object(); - - /** This is the header, at the top. */ - this.m_lang['it'].section_title = 'NA Tempo di Pulizia'; - /** This is the prompt over the popup menus. */ - this.m_lang['it'].prompt = 'Inserisci la tua data di pulizia'; - /** This is the text for the calculate button. */ - this.m_lang['it'].calculate_button_text = 'Calcola'; - /** This is the text for the change layout button. */ - this.m_lang['it'].change_layout_button_text = 'Cambia presentazione dei portachiavi'; - /** This is the text for the show special tags checkbox. */ - this.m_lang['it'].change_use_special_tags_label = 'Mostra Tag Speciali'; - /** These are the months, spelled out. */ - this.m_lang['it'].months = Array( - "ERRORE", - "gennaio", - "febbraio", - "marzo", - "aprile", - "maggio", - "giugno", - "luglio", - "agosto", - "settembre", - "ottobre", - "novembre", - "dicembre" - ); - /** These are for the top (days) blurb. */ - this.m_lang['it'].result_invalid = 'Si prega di selezionare una data pulita valida!'; - - this.m_lang['it'].result_1_day = 'Sei stato pulito per 1 giorno!'; - this.m_lang['it'].result_days_format = 'Sei pulito da %d giorni!'; - - /** These are for the second (months, years and days) blurb. */ - this.m_lang['it'].result_months_format = '%d mesi.'; - this.m_lang['it'].result_months_and_1_day_format = '%d mesi e 1 giorno.'; - this.m_lang['it'].result_months_and_days_format = '%d mesi e %d giorni.'; - this.m_lang['it'].result_1_year = '1 anno.'; - this.m_lang['it'].result_1_year_and_1_day = '1 anno e 1 giorno.'; - this.m_lang['it'].result_1_year_and_1_month = '1 anno e 1 mese.'; - this.m_lang['it'].result_1_year_1_month_and_1_day = '1 anno, 1 mese e 1 giorno.'; - this.m_lang['it'].result_1_year_months_and_1_day_format = '1 anno, %d mesi e 1 giorno.'; - this.m_lang['it'].result_1_year_months_and_days_format = '1 anno, %d mesi e %d giorni.'; - this.m_lang['it'].result_1_year_days_format = '1 year e %d giorni.'; - this.m_lang['it'].result_years_format = '%d anni.'; - this.m_lang['it'].result_years_months_format = '%d anni e %d mesi.'; - this.m_lang['it'].result_years_1_month_and_1_day_format = '%d anni, 1 mese e 1 giorno.'; - this.m_lang['it'].result_years_months_and_1_day_format = '%d anni, %d mesi e 1 giorno.'; - this.m_lang['it'].result_years_and_1_month_format = '%d anni e 1 mese.'; - this.m_lang['it'].result_years_and_1_day_format = '%d anni e 1 giorno.'; - this.m_lang['it'].result_years_and_days_format = '%d anni e %d giorni.'; - this.m_lang['it'].result_years_1_month_and_days_format = '%d anni, 1 mese e %d giorni.'; - this.m_lang['it'].result_years_months_and_days_format = '%d anni, %d mesi e %d giorni.'; - - /************************************/ - /* Portuguese (Brazilian) */ - /************************************/ - this.m_lang['pt'] = new Object(); - - /** This is the header, at the top. */ - this.m_lang['pt'].section_title = 'Calculadora de Tempo Limpo'; - /** This is the prompt over the popup menus. */ - this.m_lang['pt'].prompt = 'Entre com a data que ficou limpo'; - /** This is the text for the calculate button. */ - this.m_lang['pt'].calculate_button_text = 'Calcular'; - /** This is the text for the change layout button. */ - this.m_lang['pt'].change_layout_button_text = 'Mudar Layout das Fichas'; - /** This is the text for the show special tags checkbox. */ - this.m_lang['pt'].change_use_special_tags_label = 'Mostrar fichas especiais'; - /** These are the months, spelled out. */ - this.m_lang['pt'].months = Array( - "ERROR", - "Janeiro", - "Fevereiro", - "Março", - "Abril", - "Maio", - "Junho", - "Julho", - "Agosto", - "Setembro", - "Outubro", - "Novembro", - "Dezembro" - ); - /** These are for the top (days) blurb. */ - this.m_lang['pt'].result_invalid = 'Selecione uma data válida!'; - - this.m_lang['pt'].result_1_day = 'Você está limpo há 1 dia!'; - this.m_lang['pt'].result_days_format = 'Você está limpo %d dias!'; - - /** These are for the second (months, years and days) blurb. */ - this.m_lang['pt'].result_months_format = 'Que são %d meses.'; - this.m_lang['pt'].result_months_and_1_day_format = 'Que são %d meses e 1 dia.'; - this.m_lang['pt'].result_months_and_days_format = 'Que são %d meses e %d dias.'; - this.m_lang['pt'].result_1_year = 'Que é 1 ano.'; - this.m_lang['pt'].result_1_year_and_1_day = 'Que é 1 ano e 1 dia.'; - this.m_lang['pt'].result_1_year_and_1_month = 'Que é 1 ano e 1 mês.'; - this.m_lang['pt'].result_1_year_1_month_and_1_day = 'Que é 1 ano, 1 mês e 1 dia.'; - this.m_lang['pt'].result_1_year_months_and_1_day_format = 'Que é 1 ano, %d meses e 1 dia.'; - this.m_lang['pt'].result_1_year_months_and_days_format = 'Que é 1 ano, %d meses e %d dias.'; - this.m_lang['pt'].result_1_year_days_format = 'Que é 1 ano e %d dias.'; - this.m_lang['pt'].result_years_format = 'Que são %d anos.'; - this.m_lang['pt'].result_years_months_format = 'Que são %d anos e %d meses.'; - this.m_lang['pt'].result_years_1_month_and_1_day_format = 'Que são %d anos, 1 mês e 1 dia.'; - this.m_lang['pt'].result_years_months_and_1_day_format = 'Que são %d anos, %d meses e 1 dia.'; - this.m_lang['pt'].result_years_and_1_month_format = 'Que são %d anos e 1 mês.'; - this.m_lang['pt'].result_years_and_1_day_format = 'Que são %d anos e 1 dia.'; - this.m_lang['pt'].result_years_and_days_format = 'Que são %d anos e %d dias.'; - this.m_lang['pt'].result_years_1_month_and_days_format = 'Que são %d anos, 1 mês e %d dias.'; - this.m_lang['pt'].result_years_months_and_days_format = 'Que são %d anos, %d meses e %d dias.'; - /************************************/ - /* MAIN CODE */ - /************************************/ - - // First, see if we have any GET parameters supplied. - // GET parameters trump arguments passed in, and arguments trump defaults. - this.m_getParameters = this.getParameters(); - - // See if a root directory was specified. - if ( this.m_getParameters['NACC-dir-root'] ) { - this.m_relative_directory_root = this.m_getParameters['NACC-dir-root'] + (!this.m_getParameters['NACC-dir-root'].toString().match(/\/$/) ? '/' : ''); - } else { - if ( inDirectoryRoot ) { - this.m_relative_directory_root = inDirectoryRoot + (!inDirectoryRoot.toString().match(/\/$/) ? '/' : ''); - } else { - this.m_relative_directory_root = ''; - }; - }; - - // See if a style was specified. - if ( this.m_getParameters['NACC-style'] ) { - this.m_style_selector = this.m_getParameters['NACC-style']; - } else { - if ( inStyle ) { - this.m_style_selector = inStyle; - } else { - this.m_style_selector = null; - }; - }; - - // See if a language was specified. - if ( this.m_getParameters['NACC-lang'] ) { - this.m_lang_selector = this.m_getParameters['NACC-lang']; - } else { - if ( inLang ) { - this.m_lang_selector = inLang; - } else { - this.m_lang_selector = 'en'; - }; - }; - - // See if an initial tag layout was specified. - if ( this.m_getParameters['NACC-tag-layout'] ) { - this.m_keytag_layout = this.m_getParameters['NACC-tag-layout']; - } else { - if ( inTagLayout ) { - this.m_keytag_layout = inTagLayout; - } else { - // Try to load from localStorage - if ( typeof(Storage) !== 'undefined' ) { - var savedLayout = localStorage.getItem('nacc_keytag_layout'); - this.m_keytag_layout = savedLayout ? savedLayout : 'linear'; - } else { - this.m_keytag_layout = 'linear'; - }; - }; - }; - - // See if we want to initially display the "specialty" tags. - if ( this.m_getParameters['NACC-special-tags'] ) { - this.m_keytag_special = true; - } else { - if ( inShowSpecialTags ) { - this.m_keytag_special = true; - } else { - // Try to load from localStorage - if ( typeof(Storage) !== 'undefined' ) { - var savedSpecial = localStorage.getItem('nacc_keytag_special'); - this.m_keytag_special = savedSpecial === 'true' ? true : false; - } else { - this.m_keytag_special = false; - }; - }; - }; - - // The first thing that we do, is reference the container element. - this.m_my_container = document.getElementById(inContainerElementID); - - // Link this NACC instance with the container element. - this.m_my_container.nacc_instance = this; - - // Make sure the container is tagged with the NACC-Instance class. - if ( this.m_my_container.className ) { - this.m_my_container.className += ' NACC-Instance'; // Appending to any existing class. - } else { - this.m_my_container.className = 'NACC-Instance'; // From scratch. - }; - - // If a style was specified, we add that, as well. - if ( this.m_style_selector ) { - this.m_my_container.className += ' ' + this.m_style_selector; - }; - - // Clear the decks for action, matey! - this.m_my_container.innerHTML = ''; - - // This is where we actually create everything. - this.createHeader(); - this.createForm(); - - // This is a function that makes sure that the days of the month popup menu is valid. - this.evaluateMonthDays(); - - // See if a date was supplied. If so, we immediately calculate. - var year = parseInt(this.m_getParameters['NACC-year']); - var month = parseInt(this.m_getParameters['NACC-month']); - var day = parseInt(this.m_getParameters['NACC-day']); - - // Look for any passed-in parameters. - if ( !year ) { - year = parseInt(inYear); - }; - - if ( !month ) { - month = parseInt(inMonth); - }; - - if ( !day ) { - year = parseInt(inDay); - }; - - // Try to load from localStorage if no parameters were supplied - if ( !year && !month && !day && typeof(Storage) !== 'undefined' ) { - var savedDate = localStorage.getItem('nacc_clean_date'); - if ( savedDate ) { - try { - var dateObj = JSON.parse(savedDate); - year = parseInt(dateObj.year); - month = parseInt(dateObj.month); - day = parseInt(dateObj.day); - } catch(e) { - // Invalid saved data, ignore it - } - } - }; - - // We have to have all 3 to do an immediate calculation. - if ( year && month && day ) { - // Set up the popups to reflect the given date. - this.m_month_popup.selectedIndex = month - 1; - - for ( var i = 0; i < this.m_year_popup.options.length; i++ ) { - if ( parseInt(this.m_year_popup.options[i].value) == parseInt(year) ) { - this.m_year_popup.selectedIndex = i; - break; - }; - }; - - this.m_day_popup.selectedIndex = day - 1; - - // Make sure we're kosher. - this.evaluateMonthDays(); - - // Just run a calculation, as if the button were hit. The form will be evaluated. - this.calculateCleantime(this.m_calculate_button); - }; - } else { - // Let folks know they borked it. - alert('NACC ERROR: INVALID CONTAINER ELEMENT ID'); - }; -}; - -/******************************************************************************************** -################################### INTERNAL UTILITY METHODS ################################ -********************************************************************************************/ -/** - \brief This allows us to compare today to a given date. - - Cribbed from here: https://gist.github.com/clecuona/2945438 - - \param inFromDate The date that will be the one we compare against. - This date should always be earlier than the current date. - - \returns an object, with the total number of days, and the span, in years, months and days. -*/ -NACC.prototype.dateSpan = function(inFromDate) { - var difference = new Object; - - difference.totalDays = 0; - difference.years = 0; - difference.months = 0; - difference.days = 0; - - var dt2 = new Date(); - var dt1 = inFromDate; - - if ( dt2 > dt1 ) { - difference.totalDays = parseInt((dt2.getTime() - dt1.getTime()) / 86400000); - - var year1 = dt1.getFullYear(); - var year2 = dt2.getFullYear(); - - var month1 = dt1.getMonth(); - var month2 = dt2.getMonth(); - - var day1 = dt1.getDate(); - var day2 = dt2.getDate(); - - difference.years = year2 - year1; - difference.months = month2 - month1; - difference.days = day2 - day1; - - if ( difference.days < 0 ) { - // Use temporary date to get the number of days remaining in the month - var numDays = new Date(year1, month1 + 1, 1, 0, 0, -1).getDate(); - - difference.months -= 1; - difference.days += numDays; - }; - - if ( difference.months < 0 ) { - difference.months += 12; - difference.years -= 1; - }; - }; - - return difference; -}; - -/***********************************************************************/ -/** - \brief This simply generates a random numerical ID. - - \param inPrefix If this is supplied, it is a prefix that is prepended. - - \returns a random ID as a string, with the prefix (or 'NACC') prepended. -*/ -NACC.prototype.generateID = function(inPrefix) { - if ( !inPrefix ) { // We always have some kind of prefix. - inPrefix = 'NACC'; - }; - - return inPrefix + '-' + Math.random().toString(36).substr(2, 10); -}; - -/***********************************************************************/ -/** - \brief This checks the month and year, and disables any month days - that are not available for the given month. It will also - move the selection, if a selected day is too far down. -*/ -NACC.prototype.evaluateMonthDays = function() { - var dtmp1 = new Date(this.m_year_popup.value, this.m_month_popup.value, 1, 0, 0, -1); - - var numDays = parseInt(dtmp1.getDate()); - this.m_day_popup.selectedIndex = Math.min(this.m_day_popup.selectedIndex + 1, numDays) - 1; - - for ( var i = 0; i < this.m_day_popup.options.length; i++ ) { - this.m_day_popup.options[i].disabled = (i >= numDays); - }; -}; - -/***********************************************************************/ -/** - \brief This displays the results of the calculation. -*/ -NACC.prototype.displayCalculationResults = function(inCalculationResults) { - - var days_blurb = ''; - if ( 1 == inCalculationResults.totalDays ) { - days_blurb = this.m_lang[this.m_lang_selector].result_1_day; - } else { - days_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_days_format, inCalculationResults.totalDays); - }; - - var main_blurb = ''; - - // We select the appropriate format to display the result, based on how many day, months and years are in the result. - // We count days until 90, then months and years. - if ( 0 < inCalculationResults.totalDays ) { - if ( 90 < inCalculationResults.totalDays ) { // Have to have more than 90 days to start counting months, years and days. - if ( 0 < inCalculationResults.years ) { - if ( (1 == inCalculationResults.years) && (0 == inCalculationResults.months) && (0 == inCalculationResults.days) ) { - main_blurb = this.m_lang[this.m_lang_selector].result_1_year; - } else if ( (1 == inCalculationResults.years) && (1 == inCalculationResults.months) && (0 == inCalculationResults.days) ) { - main_blurb = this.m_lang[this.m_lang_selector].result_1_year_and_1_month; - } else if ( (1 == inCalculationResults.years) && (1 == inCalculationResults.months) && (1 == inCalculationResults.days) ) { - main_blurb = this.m_lang[this.m_lang_selector].result_1_year_1_month_and_1_day; - } else if ( (1 == inCalculationResults.years) && (0 == inCalculationResults.months) && (1 == inCalculationResults.days) ) { - main_blurb = this.m_lang[this.m_lang_selector].result_1_year_and_1_day; - } else if ( (1 == inCalculationResults.years) && (0 == inCalculationResults.months) && (1 < inCalculationResults.days) ) { - main_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_1_year_days_format, inCalculationResults.days); - } else if ( (1 == inCalculationResults.years) && (1 < inCalculationResults.months) && (1 < inCalculationResults.days) ) { - main_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_1_year_months_and_days_format, inCalculationResults.months, inCalculationResults.days); - } else if ( (0 == inCalculationResults.months) && (0 == inCalculationResults.days) ) { - main_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_years_format, inCalculationResults.years); - } else if ( (1 == inCalculationResults.months) && (0 == inCalculationResults.days) ) { - main_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_years_and_1_month_format, inCalculationResults.years); - } else if ( (0 == inCalculationResults.months) && (1 == inCalculationResults.days) ) { - main_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_years_and_1_day_format, inCalculationResults.years); - } else if ( 1 == inCalculationResults.months ) { - main_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_years_1_month_and_days_format, inCalculationResults.years, inCalculationResults.days); - } else if ( 1 == inCalculationResults.days ) { - main_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_years_months_and_1_day_format, inCalculationResults.years, inCalculationResults.months); - } else if ( (0 == inCalculationResults.days) ) { - main_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_years_months_format, inCalculationResults.years, inCalculationResults.months); - } else if ( (0 == inCalculationResults.months) ) { - main_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_years_and_days_format, inCalculationResults.years, inCalculationResults.days); - } else { - main_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_years_months_and_days_format, inCalculationResults.years, inCalculationResults.months, inCalculationResults.days); - }; - } else { // If we have less than a year, we have fewer choices. - if ( (1 == inCalculationResults.months) && (1 == inCalculationResults.days) ) { - main_blurb = this.m_lang[this.m_lang_selector].result_1_month_and_1_day; - } else if ( (1 == inCalculationResults.months) && (1 < inCalculationResults.days) ) { - main_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_1_month_days_format, inCalculationResults.days); - } else if ( (1 < inCalculationResults.months) && (1 < inCalculationResults.days) ) { - main_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_months_and_days_format, inCalculationResults.months, inCalculationResults.days); - } else if ( (1 < inCalculationResults.months) && (1 == inCalculationResults.days) ) { - main_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_months_and_1_day_format, inCalculationResults.months); - } else if ( (1 < inCalculationResults.months) && (0 == inCalculationResults.days) ) { - main_blurb = this.sprintf(this.m_lang[this.m_lang_selector].result_months_format, inCalculationResults.months); - }; - }; - }; - } else { - days_blurb = this.m_lang[this.m_lang_selector].result_invalid; - }; - - var months = (inCalculationResults.years * 12) + inCalculationResults.months; - this.createResultsDiv(inCalculationResults.totalDays, months, days_blurb, main_blurb); -}; - -/******************************************************************************************** -####################################### CALLBACK METHODS #################################### -********************************************************************************************/ -/** - \brief This is called when the month or year popup is changed. - The day popup is adjusted to reflect the available days. - - \param inObject This is the popup object. We use it to get our main instance. -*/ -NACC.prototype.monthOrYearPopupChanged = function(inObject) { - inObject.owner.evaluateMonthDays(); -}; - -/***********************************************************************/ -/** - \brief This is called when the Calculate button is hit. - - \param inObject This is the button object. We use it to get our main instance. -*/ -NACC.prototype.calculateCleantime = function(inObject) { - var owner = inObject.owner; - var year = parseInt(owner.m_year_popup.value); - var month = parseInt(owner.m_month_popup.value) - 1; - var day = parseInt(owner.m_day_popup.value); - - // If this was triggered by the rearrange layout button, we toggle the layout. - if ( inObject == owner.m_calculation_results_display_toggle_button ) { - owner.m_keytag_layout = (owner.m_keytag_layout == 'linear') ? 'tabular' : 'linear'; - // Save the layout preference to localStorage - if ( typeof(Storage) !== 'undefined' ) { - localStorage.setItem('nacc_keytag_layout', owner.m_keytag_layout); - }; - }; - - // If it was triggered by the checkbox, then we togle the special tags state. - if ( inObject == owner.m_calculation_results_show_special_tags_checkbox ) { - owner.m_keytag_special = !owner.m_keytag_special; - // Save the special tags preference to localStorage - if ( typeof(Storage) !== 'undefined' ) { - localStorage.setItem('nacc_keytag_special', owner.m_keytag_special.toString()); - }; - }; - - // Save the clean date to localStorage when calculate button is clicked - if ( inObject == owner.m_calculate_button && typeof(Storage) !== 'undefined' ) { - var dateToSave = { - year: year, - month: month + 1, // Store as 1-12 for consistency - day: day - }; - localStorage.setItem('nacc_clean_date', JSON.stringify(dateToSave)); - }; - - owner.calculate(year, month, day); -}; - -/***********************************************************************/ -/** - \brief This actually performs the calculation. Called when the - Calculate button is hit. - - \param inYear This is the year for the date. - \param inMonth This is the year for the date (0-based). - \param inDay This is the year for the date. -*/ -NACC.prototype.calculate = function(inYear, inMonth, inDay) { - this.displayCalculationResults(this.dateSpan(new Date(inYear, inMonth, inDay, 0, 0, 0, 0))); -}; - -/******************************************************************************************** -################################### DOM OBJECT INSTANTIATION ################################ -********************************************************************************************/ -/** - \brief This creates a DOM object, and returns it. If a DOM object - is passed in as a container, then the created object is added - to that container as a child. - - \param inObjectName A string. The type of object (i.e. 'div', 'img', etc.) - \param inClass A string. One or more classnames for the object. If left nil - or blank, then no class is applied. - \param inContainer A DOM element reference. If non-nil, the parent for the new object. - - \returns a new DOM object. -*/ -NACC.prototype.createDOMObject = function(inObjectName, inClass, inContainer) { - var newObject = document.createElement(inObjectName); - - // Make sure we got something, first. - if ( null != newObject ) { - // See if we were given a CSS class. This may have whitespace. - var objectID = inClass ? inClass : ''; - - if ( objectID ) { - newObject.className = objectID; - }; - - // Make sure we don't have whitespace in our ID (not allowed). - objectID = objectID.replace(/\s+?/, '-'); - - // If we are contained, then our ID derives from the container's ID. - if ( (null != inContainer) && inContainer.id ) { - objectID = inContainer.id + '-' + objectID; - } - - newObject.id = this.generateID(objectID); - - // See if we were given a container. If so, we append into that. - if ( null != inContainer ) { - inContainer.appendChild(newObject); - }; - }; - - return newObject; -}; - -/***********************************************************************/ -/** - \brief This creates the a single select option, and appends it into the given select. - - \param inSelectObject This is the select element that will contain the option. - \param inDisplayString This is the string to be displayed. - \param inValue This is the value for the option. - \param inDisabled if true, then the option is disabled. - - \returns the option object (which is automatically added to the select). -*/ -NACC.prototype.createOptionObject = function(inSelectObject, inDisplayString, inValue, inDisabled) { - var newObject = null; - - if ( inSelectObject && inDisplayString ) { // We can do without a value, but need a select and a display string. - newObject = this.createDOMObject('option', 'NACC-Option', inSelectObject); - - if ( newObject ) { - newObject.value = inValue; - newObject.innerHTML = inDisplayString; - if ( inDisabled ) { - newObject.enabled = false; - }; - }; - }; - - return newObject; -}; - -/***********************************************************************/ -/** - \brief This creates and returns one keytag object, as an img. - - \brief inTag The name of the tag to be created. - \param isClosed if true, then the top of the ring will be closed. - - \returns an img object. -*/ -NACC.prototype.createOneKeytag = function(inTag, isClosed) { - var tagSrc = this.m_relative_directory_root + 'images/' + this.m_lang_selector + '/' + inTag + '.png'; - return this.createKeytag(tagSrc, isClosed); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one keytag object, as an img. - - \param inSrc The path to the source image. - \param isClosed if true, then the top of the ring will be closed. - - \returns an img object. -*/ -NACC.prototype.createKeytag = function(inSrc, isClosed) { - // We use CSS to create the closure at the top. - var className = 'NACC-Keytag' + ((isClosed || (this.m_keytag_layout != 'linear')) ? ' NACC-Keytag-Ringtop' : ''); - var imgObject = this.createDOMObject('img', className, this.m_calculation_results_keytags_div); - - if ( null != imgObject ) { - imgObject.src = inSrc; - }; - - return imgObject; -}; - -/***********************************************************************/ -/** - \brief This creates and returns one white keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.createWhiteKeytag = function(inFace) { - // White keytag will always be closed. - var ret = this.createOneKeytag('01' + (inFace ? '_Front' : ''), true); - - if ( null != ret ) { - ret.className += ' NACC-White-Tag'; - }; - - return ret; -}; - -/***********************************************************************/ -/** - \brief This creates and returns one orange keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.createOrangeKeytag = function(inFace) { - return this.createOneKeytag('02' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one green keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.createGreenKeytag = function(inFace) { - return this.createOneKeytag('03' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one red keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.createRedKeytag = function(inFace) { - return this.createOneKeytag('04' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one blue keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.createBlueKeytag = function(inFace) { - return this.createOneKeytag('05' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one yellow keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.createYellowKeytag = function(inFace) { - return this.createOneKeytag('06' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one year keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.createYearKeytag = function(inFace) { - return this.createOneKeytag('07' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one gray keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.createGrayKeytag = function(inFace) { - return this.createOneKeytag('08' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one black keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.createBlackKeytag = function(inFace) { - return this.createOneKeytag('09' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one granite keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.createDecadeKeytag = function(inFace) { - return this.createOneKeytag('10' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one decades keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.createDecadesKeytag = function(inFace) { - return this.createOneKeytag('11' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one 25-year keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.create25YearKeytag = function(inFace) { - return this.createOneKeytag('12' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one 10,000 day keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.create10KKeytag = function(inFace) { - return this.createOneKeytag('13' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one 30-year keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.create30YearKeytag = function(inFace) { - return this.createOneKeytag('14' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one 10,000 day keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.create05Keytag = function(inFace) { - return this.createOneKeytag('15' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates and returns one 10,000 day keytag object, as an img. - - \param inFace If this will be the logo side, this should be true. - - \returns an img object. -*/ -NACC.prototype.create15Keytag = function(inFace) { - return this.createOneKeytag('16' + (inFace ? '_Front' : ''), !inFace); -}; - -/***********************************************************************/ -/** - \brief This creates the header at the top of the div. -*/ -NACC.prototype.createHeader = function() { - var newObject = this.createDOMObject('div', 'NACC-Header', this.m_my_container); - - if ( null != newObject ) { - newObject.innerHTML = this.m_lang[this.m_lang_selector].section_title; - }; -}; - -/***********************************************************************/ -/** - \brief This creates the form that wraps the fieldset. -*/ -NACC.prototype.createForm = function() { - this.m_my_form = this.createDOMObject('form', 'NACC-Form', this.m_my_container); - - if ( null != this.m_my_form ) { - this.createFieldset(); - }; -}; - -/***********************************************************************/ -/** - \brief This creates the fieldset that contains the popups and results. -*/ -NACC.prototype.createFieldset = function() { - this.m_my_fieldset = this.createDOMObject('fieldset', 'NACC-Fieldset', this.m_my_form); - if ( null != this.m_my_fieldset ) { - this.createLegend(); - }; -}; - -/***********************************************************************/ -/** - \brief This creates the fieldset legend that contains the popups. -*/ -NACC.prototype.createLegend = function() { - this.m_my_legend = this.createDOMObject('legend', 'NACC-Legend', this.m_my_fieldset); - - if ( null != this.m_my_legend ) { - this.m_my_legend_div = this.createDOMObject('legend', 'NACC-Legend-div', this.m_my_legend); - if ( null != this.m_my_legend ) { - this.createPrompt(); - this.createPopupContainer(); - }; - }; -}; - -/***********************************************************************/ -/** - \brief This creates the prompt above the popups. -*/ -NACC.prototype.createPrompt = function() { - this.m_my_prompt = this.createDOMObject('label', 'NACC-Prompt-Label', this.m_my_legend_div); - - if ( null != this.m_my_prompt ) { - this.m_my_prompt.innerHTML = this.m_lang[this.m_lang_selector].prompt; - }; -}; - -/***********************************************************************/ -/** - \brief This creates the popup container and the popups. -*/ -NACC.prototype.createPopupContainer = function() { - this.m_popup_container = this.createDOMObject('div', 'NACC-Popups', this.m_my_legend_div); - - if ( null != this.m_popup_container ) { - this.createMonthPopup(); - this.createDayPopup(); - this.createYearPopup(); - this.createCalculateButton(); - this.createDOMObject('div', 'breaker', this.m_popup_container); - }; -}; - -/***********************************************************************/ -/** - \brief This creates the month popup. -*/ -NACC.prototype.createMonthPopup = function() { - this.m_month_popup = this.createDOMObject('select', 'NACC-Month', this.m_popup_container); - - if ( null != this.m_month_popup ) { - var nowMonth = new Date().getMonth(); - this.m_my_prompt.setAttribute('for', this.m_month_popup.id); - for ( var i = 1; i < 13; i++ ) { - var selectedMonth = this.m_lang[this.m_lang_selector].months[i]; - selectedOption = this.createOptionObject(this.m_month_popup, selectedMonth, i.toString(), false); - }; - this.m_month_popup.selectedIndex = nowMonth; - this.m_month_popup.owner = this; - this.m_month_popup.onchange = function(){NACC.prototype.monthOrYearPopupChanged(this)}; - }; -}; - -/***********************************************************************/ -/** - \brief This creates the day of the month popup. -*/ -NACC.prototype.createDayPopup = function() { - this.m_day_popup = this.createDOMObject('select', 'NACC-Day', this.m_popup_container); - - if ( null != this.m_day_popup ) { - var nowDay = new Date().getDate(); - for ( var day = 1; day < 32; day++ ) { - selectedOption = this.createOptionObject(this.m_day_popup, day.toString(), day.toString(), false); - }; - this.m_day_popup.selectedIndex = nowDay - 1; - }; -}; - -/***********************************************************************/ -/** - \brief This creates the year popup. -*/ -NACC.prototype.createYearPopup = function() { - this.m_year_popup = this.createDOMObject('select', 'NACC-Year', this.m_popup_container); - - if ( null != this.m_year_popup ) { - var nowYear = new Date().getFullYear(); - for ( var year = 1953; year <= nowYear; year++ ) { - selectedOption = this.createOptionObject(this.m_year_popup, year, year, false); - }; - this.m_year_popup.selectedIndex = this.m_year_popup.options.length - 1; - this.m_year_popup.owner = this; - this.m_year_popup.onchange = function(){NACC.prototype.monthOrYearPopupChanged(this)}; - }; -}; - -/***********************************************************************/ -/** - \brief This creates the calculate button. -*/ -NACC.prototype.createCalculateButton = function() { - this.m_calculate_button = this.createDOMObject('input', 'NACC-Calculate-Button', this.m_popup_container); - - if ( null != this.m_calculate_button ) { - this.m_calculate_button.setAttribute('type', 'button'); - this.m_calculate_button.value = this.m_lang[this.m_lang_selector].calculate_button_text; - this.m_calculate_button.owner = this; - this.m_calculate_button.onclick = function(){NACC.prototype.calculateCleantime(this)}; - }; -}; - -/***********************************************************************/ -/** - \brief This creates the results div. - - \brief inNumDays The total number of days (used to determine keytags). - \brief inMonths The number of months. - \brief inDays The string to display the number of days. - \brief inMain The string to display the main blurb. -*/ -NACC.prototype.createResultsDiv = function(inNumDays, inMonths, inDays, inMain) { - if ( this.m_calculation_results_div ) { - this.m_calculation_results_div.innerHTML = ''; - this.m_calculation_results_text_div = null; - this.m_calculation_results_keytags_div = null; - } else { - this.m_calculation_results_div = this.createDOMObject('div', 'NACC-Results', this.m_my_fieldset); - }; - - if ( this.m_calculation_results_div ) { - this.createResultsTextDiv(inDays, inMain); - this.createTagsDiv(inNumDays, inMonths); - }; -}; - -/***********************************************************************/ -/** - \brief This creates the results text div. - - \brief inDays The string to display the number of days. - \brief inMain The string to display the main blurb. -*/ -NACC.prototype.createResultsTextDiv = function(inDays, inMain) { - if ( inDays ) { - this.m_calculation_results_text_div = this.createDOMObject('div', 'NACC-Results-Text', this.m_calculation_results_div); - - if ( this.m_calculation_results_text_div ) { - this.createResultsDays(inDays); - this.createResultsMain(inMain); - }; - }; -}; - -/***********************************************************************/ -/** - \brief This creates the first line of the results (days). - - \brief inBlurb The string to display. -*/ -NACC.prototype.createResultsDays = function(inBlurb) { - if ( inBlurb ) { - var newObject = this.createDOMObject('div', 'NACC-Days', this.m_calculation_results_text_div); - - if ( null != newObject ) { - newObject.innerHTML = inBlurb; - }; - }; -}; - -/***********************************************************************/ -/** - \brief This creates the second line of the results (main blurb). - - \brief inBlurb The string to display. -*/ -NACC.prototype.createResultsMain = function(inBlurb) { - if ( inBlurb ) { - var newObject = this.createDOMObject('div', 'NACC-MainBlurb', this.m_calculation_results_text_div); - - if ( null != newObject ) { - newObject.innerHTML = inBlurb; - }; - }; -}; - -/***********************************************************************/ -/** - \brief This creates the tags div. - - \param inNumDays The total number of days (used to determine keytags). - \param inMonths The total number of months (used to determine keytags). -*/ -NACC.prototype.createTagsDiv = function(inNumDays, inMonths) { - if ( 0 < inNumDays ) { - if ( this.m_calculation_results_keytags_div ) { - while ( this.m_calculation_results_keytags_div.hasChildNodes() ) { - this.m_calculation_results_keytags_div.removeChild(this.m_calculation_results_keytags_div.firstChild); - }; - } else { - this.m_calculation_results_keytags_div = this.createDOMObject('div', 'NACC-Keytags' + ((this.m_keytag_layout != 'linear') ? ' NACC-Keytag-Tabular' : ''), this.m_calculation_results_div); - }; - - if ( this.m_calculation_results_keytags_div ) { - this.createTagsArray(inNumDays, inMonths); - }; - }; -}; - -/***********************************************************************/ -/** - \brief This creates the tags images. - - \param inNumDays The total number of days (used to determine keytags). - \param inMonths The total number of months (used to determine keytags). -*/ -NACC.prototype.createTagsArray = function(inNumDays, inMonths) { - var isFace = this.m_keytag_layout == 'linear'; - - if ( 0 < inNumDays ) { - this.createWhiteKeytag(isFace); - }; - - if ( 29 < inNumDays ) { - this.createOrangeKeytag(isFace); - }; - - if ( 59 < inNumDays ) { - this.createGreenKeytag(isFace); - }; - - if ( 89 < inNumDays ) { - this.createRedKeytag(isFace); - }; - - if ( 90 < inNumDays ) { - if ( 5 < inMonths ) { - this.createBlueKeytag(isFace); - }; - - if ( 8 < inMonths ) { - this.createYellowKeytag(isFace); - }; - - if ( 11 < inMonths ) { - this.createYearKeytag(isFace); - }; - - if ( 17 < inMonths ) { - this.createGrayKeytag(isFace); - }; - - if ( 23 < inMonths ) { - this.createBlackKeytag(isFace); - }; - - for ( var i = 24; i <= (inMonths - 12); i += 12 ) { - var comp = i + 12; - var specialTag = false; - - if ( this.m_keytag_special ) { - if ( comp == 60 ) { - specialTag = true; - this.create05Keytag(isFace); - }; - - if ( comp == 120 ) { - specialTag = true; - this.createDecadeKeytag(isFace); - }; - - if ( comp == 180 ) { - specialTag = true; - this.create15Keytag(isFace); - }; - - if ( !specialTag && (comp == 300) ) { - specialTag = true; - this.create25YearKeytag(isFace); - }; - - if ( !specialTag && (comp == 360) ) { - specialTag = true; - this.create30YearKeytag(isFace); - }; - - if ( !specialTag && (0 == (comp % 120)) ) { - specialTag = true; - this.createDecadesKeytag(isFace); - }; - }; - - if ( !specialTag ) { - this.createBlackKeytag(isFace); - }; - - if ( this.m_keytag_special && (comp == 324) && (9999 < inNumDays) ) { - this.create10KKeytag(isFace); - }; - }; - }; - - this.createRearrangeButton(); -}; - -/***********************************************************************/ -/** - \brief This creates the change layout button. -*/ -NACC.prototype.createRearrangeButton = function() { - if ( null != this.m_calculation_results_display_toggle_button ) { - this.m_calculation_results_display_toggle_button.parentNode.removeChild(this.m_calculation_results_display_toggle_button); - }; - - this.m_calculation_results_display_toggle_button = this.createDOMObject('input', 'NACC-Change-Layout-Button', this.m_popup_container); - - if ( null != this.m_calculation_results_display_toggle_button ) { - this.m_calculation_results_display_toggle_button.setAttribute('type', 'button'); - this.m_calculation_results_display_toggle_button.value = this.m_lang[this.m_lang_selector].change_layout_button_text; - this.m_calculation_results_display_toggle_button.owner = this; - this.m_calculation_results_display_toggle_button.onclick = function(){NACC.prototype.calculateCleantime(this)}; - this.createSpecialTagsCheckbox(); - }; -}; - -/***********************************************************************/ -/** - \brief This creates the show special tags checkbox. -*/ -NACC.prototype.createSpecialTagsCheckbox = function() { - if ( null != this.m_calculation_results_show_special_tags_checkbox ) { - this.m_calculation_results_show_special_tags_checkbox.parentNode.removeChild(this.m_calculation_results_show_special_tags_checkbox); - }; - - this.m_calculation_results_show_special_tags_checkbox = this.createDOMObject('input', 'NACC-Show-Special-Tags-Checkbox', this.m_popup_container); - - if ( null != this.m_calculation_results_show_special_tags_checkbox ) { - this.m_calculation_results_show_special_tags_checkbox.setAttribute('type', 'checkbox'); - this.m_calculation_results_show_special_tags_checkbox.checked = this.m_keytag_special ? true : false; - this.m_calculation_results_show_special_tags_checkbox.value = 1; - this.m_calculation_results_show_special_tags_checkbox.owner = this; - this.m_calculation_results_show_special_tags_checkbox.onclick = function(){NACC.prototype.calculateCleantime(this)}; - }; - - if ( null != this.m_calculation_results_show_special_tags_checkbox_label ) { - this.m_calculation_results_show_special_tags_checkbox_label.parentNode.removeChild(this.m_calculation_results_show_special_tags_checkbox_label); - }; - - this.m_calculation_results_show_special_tags_checkbox_label = this.createDOMObject('label', 'NACC-Show-Special-Tags-Checkbox-Label', this.m_popup_container); - - if ( null != this.m_calculation_results_show_special_tags_checkbox_label ) { - this.m_calculation_results_show_special_tags_checkbox_label.setAttribute('for', this.m_calculation_results_show_special_tags_checkbox.id); - this.m_calculation_results_show_special_tags_checkbox_label.innerHTML = this.m_lang[this.m_lang_selector].change_use_special_tags_label; - this.createDOMObject('div', 'breaker', this.m_popup_container); - }; -}; - -/******************************************************************************************** -####################################### THIRD-PARTY CODE #################################### -********************************************************************************************/ -/** - \brief This returns the GET parameters as an associative array. - From here: http://stackoverflow.com/a/5448635/879365 - - \returns An associative array, with any GET parameters. -*/ -NACC.prototype.getParameters = function() { - function transformToAssocArray( prmstr ) { - function urldecode(str) { - return decodeURIComponent((str+'').replace(/\+/g, '%20')); - }; - - var params = {}; - var prmarr = prmstr.split('&'); - for ( var i = 0; i < prmarr.length; i++) { - var tmparr = prmarr[i].split('='); - params[urldecode(tmparr[0])] = urldecode(tmparr[1]); - }; - return params; - }; - - var prmstr = window.location.search.substr(1); - return prmstr != null && prmstr != '' ? transformToAssocArray(prmstr) : {}; -}; - -/***********************************************************************/ -/** -sprintf() for JavaScript 0.6 - -Copyright (c) Alexandru Marasteanu -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of sprintf() for JavaScript nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -Changelog: -2007.04.03 - 0.1: - - initial release -2007.09.11 - 0.2: - - feature: added argument swapping -2007.09.17 - 0.3: - - bug fix: no longer throws exception on empty paramenters (Hans Pufal) -2007.10.21 - 0.4: - - unit test and patch (David Baird) -2010.05.09 - 0.5: - - bug fix: 0 is now preceeded with a + sign - - bug fix: the sign was not at the right position on padded results (Kamal Abdali) - - switched from GPL to BSD license -2010.05.22 - 0.6: - - reverted to 0.4 and fixed the bug regarding the sign of the number 0 - Note: - Thanks to Raphael Pigulla (http://www.n3rd.org/) - who warned me about a bug in 0.5, I discovered that the last update was - a regress. I appologize for that. -**/ - -NACC.prototype.sprintf = function () { - function str_repeat(i, m) { - for (var o = []; m > 0; o[--m] = i); - return o.join(''); - }; - - var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = ''; - while (f) { - if (m = /^[^\x25]+/.exec(f)) { - o.push(m[0]); - } - else if (m = /^\x25{2}/.exec(f)) { - o.push('%'); - } - else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) { - if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) { - throw('Too few arguments.'); - }; - if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) { - throw('Expecting number but found ' + typeof(a)); - }; - switch (m[7]) { - case 'b': a = a.toString(2); break; - case 'c': a = String.fromCharCode(a); break; - case 'd': a = parseInt(a,10); break; - case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break; - case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break; - case 'o': a = a.toString(8); break; - case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break; - case 'u': a = Math.abs(a); break; - case 'x': a = a.toString(16); break; - case 'X': a = a.toString(16).toUpperCase(); break; - }; - a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+'+ a : a); - c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' '; - x = m[5] - String(a).length - s.length; - p = m[5] ? str_repeat(c, x) : ''; - o.push(s + (m[4] ? a + p : p + a)); - } - else { - throw('Huh ?!'); - }; - f = f.substring(m[0].length); - }; - return o.join(''); -}; - +var NACC=(function(){var e={en:{section_title:`NA Cleantime Calculator`,prompt:`Please enter your Clean Date`,calculate_button_text:`Calculate`,change_layout_button_text:`Change Tag Layout`,change_use_special_tags_label:`Show Special Tags`,months:[`ERROR`,`January`,`February`,`March`,`April`,`May`,`June`,`July`,`August`,`September`,`October`,`November`,`December`],result_invalid:`Please select a valid clean date!`,result_1_day:`You have been clean for 1 day!`,result_days_format:`You have been clean for %d days!`,result_months_format:`This is %d months.`,result_months_and_1_day_format:`This is %d months and 1 day.`,result_months_and_days_format:`This is %d months and %d days.`,result_1_year:`This is 1 year.`,result_1_year_and_1_day:`This is 1 year and 1 day.`,result_1_year_and_1_month:`This is 1 year and 1 month.`,result_1_year_1_month_and_1_day:`This is 1 year, 1 month and 1 day.`,result_1_year_months_and_1_day_format:`This is 1 year, %d months and 1 day.`,result_1_year_months_and_days_format:`This is 1 year, %d months and %d days.`,result_1_year_days_format:`This is 1 year and %d days.`,result_years_format:`This is %d years.`,result_years_months_format:`This is %d years and %d months.`,result_years_1_month_and_1_day_format:`This is %d years, 1 month and 1 day.`,result_years_months_and_1_day_format:`This is %d years, %d months and 1 day.`,result_years_and_1_month_format:`This is %d years and 1 month.`,result_years_and_1_day_format:`This is %d years and 1 day.`,result_years_and_days_format:`This is %d years and %d days.`,result_years_1_month_and_days_format:`This is %d years, 1 month and %d days.`,result_years_months_and_days_format:`This is %d years, %d months and %d days.`},es:{section_title:`Calculadora de Tiempo Limpio NA`,prompt:`Por favor, entra tu fecha de inicio.`,calculate_button_text:`Calcular`,change_layout_button_text:`Cambiar los llaveros de posición.`,change_use_special_tags_label:`Mostrar Llaveros Especiales.`,months:[`ERROR`,`Enero`,`Febrero`,`Marzo`,`Abril`,`Mayo`,`Junio`,`Julio`,`Agosto`,`Septiembre`,`Octubre`,`Noviembre`,`Diciembre`],result_invalid:`¡Por favor, entra una fecha de inicio válido!`,result_1_day:`¡Has estado limpio por 1 día!`,result_days_format:`¡Has estado limpio por %d días!`,result_months_format:`Esto suma un total de %d meses.`,result_months_and_1_day_format:`Esto suma un total de %d meses y un día.`,result_months_and_days_format:`Esto suma un total de %d meses y %d días.`,result_1_year:`Esto suma un total de 1 año.`,result_1_year_and_1_day:`Esto suma un total de 1 año y 1 día.`,result_1_year_and_1_month:`Esto suma un total de 1 año y 1 mes.`,result_1_year_1_month_and_1_day:`Esto suma un total de 1 año, 1 mes y 1 día.`,result_1_year_months_and_1_day_format:`Esto suma un total de 1 año, %d meses y 1 día.`,result_1_year_months_and_days_format:`Esto suma un total de 1 año, %d meses y %d días.`,result_1_year_days_format:`Esto suma un total de 1 año y %d días.`,result_years_format:`Esto suma un total de %d años.`,result_years_months_format:`Esto suma un total de %d años y %d meses.`,result_years_1_month_and_1_day_format:`Esto suma un total de %d años, 1 mes y 1 día.`,result_years_months_and_1_day_format:`Esto suma un total de %d años, %d meses y 1 día.`,result_years_and_1_month_format:`Esto suma un total de %d años y 1 mes.`,result_years_and_1_day_format:`Esto suma un total de %d años y 1 día.`,result_years_and_days_format:`Esto suma un total de %d años y %d días.`,result_years_1_month_and_days_format:`Esto suma un total de %d años, 1 mes y %d días.`,result_years_months_and_days_format:`Esto suma un total de %d años, %d meses y %d días.`},it:{section_title:`NA Tempo di Pulizia`,prompt:`Inserisci la tua data di pulizia`,calculate_button_text:`Calcola`,change_layout_button_text:`Cambia presentazione dei portachiavi`,change_use_special_tags_label:`Mostra Tag Speciali`,months:[`ERRORE`,`gennaio`,`febbraio`,`marzo`,`aprile`,`maggio`,`giugno`,`luglio`,`agosto`,`settembre`,`ottobre`,`novembre`,`dicembre`],result_invalid:`Si prega di selezionare una data pulita valida!`,result_1_day:`Sei stato pulito per 1 giorno!`,result_days_format:`Sei pulito da %d giorni!`,result_months_format:`%d mesi.`,result_months_and_1_day_format:`%d mesi e 1 giorno.`,result_months_and_days_format:`%d mesi e %d giorni.`,result_1_year:`1 anno.`,result_1_year_and_1_day:`1 anno e 1 giorno.`,result_1_year_and_1_month:`1 anno e 1 mese.`,result_1_year_1_month_and_1_day:`1 anno, 1 mese e 1 giorno.`,result_1_year_months_and_1_day_format:`1 anno, %d mesi e 1 giorno.`,result_1_year_months_and_days_format:`1 anno, %d mesi e %d giorni.`,result_1_year_days_format:`1 year e %d giorni.`,result_years_format:`%d anni.`,result_years_months_format:`%d anni e %d mesi.`,result_years_1_month_and_1_day_format:`%d anni, 1 mese e 1 giorno.`,result_years_months_and_1_day_format:`%d anni, %d mesi e 1 giorno.`,result_years_and_1_month_format:`%d anni e 1 mese.`,result_years_and_1_day_format:`%d anni e 1 giorno.`,result_years_and_days_format:`%d anni e %d giorni.`,result_years_1_month_and_days_format:`%d anni, 1 mese e %d giorni.`,result_years_months_and_days_format:`%d anni, %d mesi e %d giorni.`},pt:{section_title:`Calculadora de Tempo Limpo`,prompt:`Entre com a data que ficou limpo`,calculate_button_text:`Calcular`,change_layout_button_text:`Mudar Layout das Fichas`,change_use_special_tags_label:`Mostrar fichas especiais`,months:[`ERROR`,`Janeiro`,`Fevereiro`,`Março`,`Abril`,`Maio`,`Junho`,`Julho`,`Agosto`,`Setembro`,`Outubro`,`Novembro`,`Dezembro`],result_invalid:`Selecione uma data válida!`,result_1_day:`Você está limpo há 1 dia!`,result_days_format:`Você está limpo %d dias!`,result_months_format:`Que são %d meses.`,result_months_and_1_day_format:`Que são %d meses e 1 dia.`,result_months_and_days_format:`Que são %d meses e %d dias.`,result_1_year:`Que é 1 ano.`,result_1_year_and_1_day:`Que é 1 ano e 1 dia.`,result_1_year_and_1_month:`Que é 1 ano e 1 mês.`,result_1_year_1_month_and_1_day:`Que é 1 ano, 1 mês e 1 dia.`,result_1_year_months_and_1_day_format:`Que é 1 ano, %d meses e 1 dia.`,result_1_year_months_and_days_format:`Que é 1 ano, %d meses e %d dias.`,result_1_year_days_format:`Que é 1 ano e %d dias.`,result_years_format:`Que são %d anos.`,result_years_months_format:`Que são %d anos e %d meses.`,result_years_1_month_and_1_day_format:`Que são %d anos, 1 mês e 1 dia.`,result_years_months_and_1_day_format:`Que são %d anos, %d meses e 1 dia.`,result_years_and_1_month_format:`Que são %d anos e 1 mês.`,result_years_and_1_day_format:`Que são %d anos e 1 dia.`,result_years_and_days_format:`Que são %d anos e %d dias.`,result_years_1_month_and_days_format:`Que são %d anos, 1 mês e %d dias.`,result_years_months_and_days_format:`Que são %d anos, %d meses e %d dias.`},"zh-Hans":{section_title:`NA 清醒时间计算器`,prompt:`请输入您的清醒日期`,calculate_button_text:`计算`,change_layout_button_text:`更改钥匙扣设置`,change_use_special_tags_label:`显示特殊钥匙扣`,months:[`错误`,`1月`,`2月`,`3月`,`4月`,`5月`,`6月`,`7月`,`8月`,`9月`,`10月`,`11月`,`12月`],result_invalid:`请选择一个有效的清醒日期`,result_1_day:`您已经清醒一天了!`,result_days_format:`您已经清醒%d天了!`,result_months_format:`这是%d个月了`,result_months_and_1_day_format:`这是%d个月零一天了.`,result_months_and_days_format:`这是%d个月%d天了.`,result_1_year:`这是一年了.`,result_1_year_and_1_day:`这是一年零一天了.`,result_1_year_and_1_month:`这是一年一个月了.`,result_1_year_1_month_and_1_day:`这是一年一个月零一天了.`,result_1_year_months_and_1_day_format:`这是一年,%d个月零一天了.`,result_1_year_months_and_days_format:`这是一年,%d个月%d天了.`,result_1_year_days_format:`这是一年,%d天了.`,result_years_format:`这是%d年了.`,result_years_months_format:`这是%d年%d月了.`,result_years_1_month_and_1_day_format:`这是%d年,一个月零一天了.`,result_years_months_and_1_day_format:`这是%d年,%d月零一天了.`,result_years_and_1_month_format:`这是%d年一个月了.`,result_years_and_1_day_format:`这是%d年零一天了.`,result_years_and_days_format:`这是%d年%d天了.`,result_years_1_month_and_days_format:`这是%d年,一个月%d天了.`,result_years_months_and_days_format:`这是%d个年,%d个月,%d天了.`},"zh-Hant":{section_title:`NA 清醒時間計算器`,prompt:`請輸入您的清醒日期`,calculate_button_text:`計算`,change_layout_button_text:`更改鑰匙扣設置`,change_use_special_tags_label:`顯示特殊鑰匙扣`,months:[`錯誤`,`1月`,`2月`,`3月`,`4月`,`5月`,`6月`,`7月`,`8月`,`9月`,`10月`,`11月`,`12月`],result_invalid:`請選擇一個有效的清醒日期`,result_1_day:`您已經清醒一天了!`,result_days_format:`您已经清醒%d天了!`,result_months_format:`這是%d個月了`,result_months_and_1_day_format:`這是%d個月零一天了.`,result_months_and_days_format:`這是%d個月%d天了.`,result_1_year:`這是一年了.`,result_1_year_and_1_day:`這是一年零一天了.`,result_1_year_and_1_month:`這是一年一個月了.`,result_1_year_1_month_and_1_day:`這是一年一個月零一天了.`,result_1_year_months_and_1_day_format:`這是一年,%d個月零一天了.`,result_1_year_months_and_days_format:`這是一年,%d個月%d天了.`,result_1_year_days_format:`這是一年,%d天了.`,result_years_format:`這是%d年了.`,result_years_months_format:`這是%d年%d月了.`,result_years_1_month_and_1_day_format:`這是%d年,一個月零一天了.`,result_years_months_and_1_day_format:`這是%d年,%d月零一天了.`,result_years_and_1_month_format:`這是%d年一個月了.`,result_years_and_1_day_format:`這是%d年零一天了.`,result_years_and_days_format:`這是%d年%d天了.`,result_years_1_month_and_days_format:`這是%d年,一個月%d天了.`,result_years_months_and_days_format:`這是%d個年,%d個月,%d天了.`}};function t(e,t=new Date){let n={totalDays:0,years:0,months:0,days:0};if(t<=e)return n;if(n.totalDays=Math.floor((t.getTime()-e.getTime())/864e5),n.years=t.getFullYear()-e.getFullYear(),n.months=t.getMonth()-e.getMonth(),n.days=t.getDate()-e.getDate(),n.days<0){let t=new Date(e.getFullYear(),e.getMonth()+1,0).getDate();--n.months,n.days+=t}return n.months<0&&(n.months+=12,--n.years),n}function n(e,...t){let n=0;return e.replace(/%d/g,()=>{let e=t[n++];return e==null?``:String(Math.floor(Number(e)))})}var r=class r{resultsDiv=null;resultsTextDiv=null;keytagsDiv=null;layoutToggleButton=null;specialTagsCheckbox=null;specialTagsLabel=null;constructor(t,n=null,i=null,a=null,o=!1,s=null,c=null,l=null,u=null){let d=document.getElementById(t);if(!d)throw alert(`NACC ERROR: INVALID CONTAINER ELEMENT ID`),Error(`NACC: no element with id "${t}"`);this.container=d;let f=r.getParameters(),p=f[`NACC-dir-root`]??s??``;p&&!p.endsWith(`/`)&&(p+=`/`),this.directoryRoot=p,this.styleSelector=f[`NACC-style`]??n??null,this.langCode=f[`NACC-lang`]??i??`en`,this.lang=e[this.langCode]??e.en;let m=f[`NACC-tag-layout`]??a;m===`linear`||m===`tabular`?this.tagLayout=m:this.tagLayout=localStorage.getItem(`nacc_keytag_layout`)===`tabular`?`tabular`:`linear`,f[`NACC-special-tags`]||o?this.showSpecialTags=!0:this.showSpecialTags=localStorage.getItem(`nacc_keytag_special`)===`true`,this.container.className?this.container.className+=` NACC-Instance`:this.container.className=`NACC-Instance`,this.styleSelector&&(this.container.className+=` `+this.styleSelector),this.container.innerHTML=``,this.createHeader(),this.createForm(),this.evaluateMonthDays();let h=parseInt(f[`NACC-year`])||(c?Number(c):0),g=parseInt(f[`NACC-month`])||(l?Number(l):0),_=parseInt(f[`NACC-day`])||(u?Number(u):0);if(!h&&!g&&!_){let e=localStorage.getItem(`nacc_clean_date`);if(e)try{let t=JSON.parse(e);h=parseInt(t.year)||0,g=parseInt(t.month)||0,_=parseInt(t.day)||0}catch{}}if(h&&g&&_){this.monthPopup.selectedIndex=g-1;for(let e=0;e90&&(i=e.years>0?this.formatYearsBlurb(e):this.formatMonthsBlurb(e)));let a=e.years*12+e.months;this.createResultsDiv(e.totalDays,a,r,i)}formatYearsBlurb(e){let t=this.lang,{years:r,months:i,days:a}=e;if(r===1){if(i===0&&a===0)return t.result_1_year;if(i===1&&a===0)return t.result_1_year_and_1_month;if(i===1&&a===1)return t.result_1_year_1_month_and_1_day;if(i===0&&a===1)return t.result_1_year_and_1_day;if(i===0&&a>1)return n(t.result_1_year_days_format,a);if(i>1&&a>1)return n(t.result_1_year_months_and_days_format,i,a);if(i>1&&a===1)return n(t.result_1_year_months_and_1_day_format,i);if(i>1&&a===0)return n(t.result_years_months_format,1,i)}return i===0&&a===0?n(t.result_years_format,r):i===1&&a===0?n(t.result_years_and_1_month_format,r):i===0&&a===1?n(t.result_years_and_1_day_format,r):i===0&&a>1?n(t.result_years_and_days_format,r,a):i>1&&a===0?n(t.result_years_months_format,r,i):i===1&&a===1?n(t.result_years_1_month_and_1_day_format,r):i===1&&a>1?n(t.result_years_1_month_and_days_format,r,a):i>1&&a===1?n(t.result_years_months_and_1_day_format,r,i):n(t.result_years_months_and_days_format,r,i,a)}formatMonthsBlurb(e){let t=this.lang,{months:r,days:i}=e;return r>1&&i===0?n(t.result_months_format,r):r>1&&i===1?n(t.result_months_and_1_day_format,r):r>1&&i>1?n(t.result_months_and_days_format,r,i):``}evaluateMonthDays(){let e=new Date(parseInt(this.yearPopup.value),parseInt(this.monthPopup.value),0).getDate();this.dayPopup.selectedIndex=Math.min(this.dayPopup.selectedIndex+1,e)-1;for(let t=0;t=e}el(e,t,n){let r=document.createElement(e);return t&&(r.className=t),r.id=t.replace(/\s+/g,`-`)+`-`+Math.random().toString(36).substring(2,12),n&&n.appendChild(r),r}createHeader(){let e=this.el(`div`,`NACC-Header`,this.container);e.innerHTML=this.lang.section_title}createForm(){let e=this.el(`form`,`NACC-Form`,this.container),t=this.el(`fieldset`,`NACC-Fieldset`,e),n=this.el(`legend`,`NACC-Legend`,t),r=this.el(`div`,`NACC-Legend-div`,n);this.promptLabel=this.el(`label`,`NACC-Prompt-Label`,r),this.promptLabel.innerHTML=this.lang.prompt,this.popupContainer=this.el(`div`,`NACC-Popups`,r),this.createMonthPopup(),this.createDayPopup(),this.createYearPopup(),this.createCalculateButton(),this.el(`div`,`breaker`,this.popupContainer)}createMonthPopup(){this.monthPopup=this.el(`select`,`NACC-Month`,this.popupContainer),this.promptLabel.setAttribute(`for`,this.monthPopup.id);let e=new Date().getMonth();for(let e=1;e<=12;e++){let t=this.el(`option`,`NACC-Option`,this.monthPopup);t.value=String(e),t.innerHTML=this.lang.months[e]}this.monthPopup.selectedIndex=e,this.monthPopup.onchange=()=>this.evaluateMonthDays()}createDayPopup(){this.dayPopup=this.el(`select`,`NACC-Day`,this.popupContainer);let e=new Date().getDate();for(let e=1;e<=31;e++){let t=this.el(`option`,`NACC-Option`,this.dayPopup);t.value=String(e),t.innerHTML=String(e)}this.dayPopup.selectedIndex=e-1}createYearPopup(){this.yearPopup=this.el(`select`,`NACC-Year`,this.popupContainer);let e=new Date().getFullYear();for(let t=1953;t<=e;t++){let e=this.el(`option`,`NACC-Option`,this.yearPopup);e.value=String(t),e.innerHTML=String(t)}this.yearPopup.selectedIndex=this.yearPopup.options.length-1,this.yearPopup.onchange=()=>this.evaluateMonthDays()}createCalculateButton(){this.calculateButton=this.el(`input`,`NACC-Calculate-Button`,this.popupContainer),this.calculateButton.type=`button`,this.calculateButton.value=this.lang.calculate_button_text,this.calculateButton.onclick=()=>this.onCalculate(`button`)}createResultsDiv(e,t,n,r){if(this.resultsDiv?(this.resultsDiv.innerHTML=``,this.resultsTextDiv=null,this.keytagsDiv=null):this.resultsDiv=this.el(`div`,`NACC-Results`,this.container.querySelector(`.NACC-Fieldset`)),n){this.resultsTextDiv=this.el(`div`,`NACC-Results-Text`,this.resultsDiv);let e=this.el(`div`,`NACC-Days`,this.resultsTextDiv);if(e.innerHTML=n,r){let e=this.el(`div`,`NACC-MainBlurb`,this.resultsTextDiv);e.innerHTML=r}}if(e>0){let n=this.tagLayout===`linear`?``:` NACC-Keytag-Tabular`;this.keytagsDiv=this.el(`div`,`NACC-Keytags`+n,this.resultsDiv),this.createTagsArray(e,t)}}createKeytag(e,t,n){let r=`NACC-Keytag`+((n??!t)||this.tagLayout!==`linear`?` NACC-Keytag-Ringtop`:``),i=this.el(`img`,r,this.keytagsDiv),a=t?`_Front`:``;return i.src=`${this.directoryRoot}images/${this.langCode}/${e}${a}.png`,i}createTagsArray(e,t){let n=this.tagLayout===`linear`;if(e>=1){let e=this.createKeytag(`01`,n,!0);e.className+=` NACC-White-Tag`}if(e>=30&&this.createKeytag(`02`,n),e>=60&&this.createKeytag(`03`,n),e>=90&&this.createKeytag(`04`,n),e>90){t>5&&this.createKeytag(`05`,n),t>8&&this.createKeytag(`06`,n),t>11&&this.createKeytag(`07`,n),t>17&&this.createKeytag(`08`,n),t>23&&this.createKeytag(`09`,n);for(let r=24;r<=t-12;r+=12){let t=r+12,i=!1;this.showSpecialTags&&(t===60&&(i=!0,this.createKeytag(`15`,n)),t===120&&(i=!0,this.createKeytag(`10`,n)),t===180&&(i=!0,this.createKeytag(`16`,n)),!i&&t===300&&(i=!0,this.createKeytag(`12`,n)),!i&&t===360&&(i=!0,this.createKeytag(`14`,n)),!i&&t%120==0&&(i=!0,this.createKeytag(`11`,n))),i||this.createKeytag(`09`,n),this.showSpecialTags&&t===324&&e>9999&&this.createKeytag(`13`,n)}}this.createLayoutToggle()}createLayoutToggle(){this.layoutToggleButton?.parentNode?.removeChild(this.layoutToggleButton),this.specialTagsCheckbox?.parentNode?.removeChild(this.specialTagsCheckbox),this.specialTagsLabel?.parentNode?.removeChild(this.specialTagsLabel),this.layoutToggleButton=this.el(`input`,`NACC-Change-Layout-Button`,this.popupContainer),this.layoutToggleButton.type=`button`,this.layoutToggleButton.value=this.lang.change_layout_button_text,this.layoutToggleButton.onclick=()=>this.onCalculate(`layout`),this.specialTagsCheckbox=this.el(`input`,`NACC-Show-Special-Tags-Checkbox`,this.popupContainer),this.specialTagsCheckbox.type=`checkbox`,this.specialTagsCheckbox.checked=this.showSpecialTags,this.specialTagsCheckbox.value=`1`,this.specialTagsCheckbox.onclick=()=>this.onCalculate(`specialTags`),this.specialTagsLabel=this.el(`label`,`NACC-Show-Special-Tags-Checkbox-Label`,this.popupContainer),this.specialTagsLabel.setAttribute(`for`,this.specialTagsCheckbox.id),this.specialTagsLabel.innerHTML=this.lang.change_use_special_tags_label,this.el(`div`,`breaker`,this.popupContainer)}};return window.NACC=r,r})(); \ No newline at end of file diff --git a/nacc2/package-lock.json b/nacc2/package-lock.json new file mode 100644 index 0000000..83cfd02 --- /dev/null +++ b/nacc2/package-lock.json @@ -0,0 +1,902 @@ +{ + "name": "nacc2", + "version": "2.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nacc2", + "version": "2.1.0", + "devDependencies": { + "vite": "^8.0.8" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", + "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/vite": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.15", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/nacc2/package.json b/nacc2/package.json new file mode 100644 index 0000000..843a8ac --- /dev/null +++ b/nacc2/package.json @@ -0,0 +1,12 @@ +{ + "name": "nacc2", + "version": "2.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "vite build" + }, + "devDependencies": { + "vite": "^8.0.8" + } +} diff --git a/nacc2/src/CleantimeKeyTag.psd b/nacc2/src/CleantimeKeyTag.psd deleted file mode 100644 index 1be4ddc..0000000 Binary files a/nacc2/src/CleantimeKeyTag.psd and /dev/null differ diff --git a/nacc2/src/calc.js b/nacc2/src/calc.js new file mode 100644 index 0000000..94d6433 --- /dev/null +++ b/nacc2/src/calc.js @@ -0,0 +1,29 @@ +/** + * @param {Date} fromDate + * @param {Date} [now] + * @returns {{ totalDays: number, years: number, months: number, days: number }} + */ +export function dateSpan(fromDate, now = new Date()) { + const diff = { totalDays: 0, years: 0, months: 0, days: 0 }; + + if (now <= fromDate) return diff; + + diff.totalDays = Math.floor((now.getTime() - fromDate.getTime()) / 86400000); + + diff.years = now.getFullYear() - fromDate.getFullYear(); + diff.months = now.getMonth() - fromDate.getMonth(); + diff.days = now.getDate() - fromDate.getDate(); + + if (diff.days < 0) { + const numDays = new Date(fromDate.getFullYear(), fromDate.getMonth() + 1, 0).getDate(); + diff.months -= 1; + diff.days += numDays; + } + + if (diff.months < 0) { + diff.months += 12; + diff.years -= 1; + } + + return diff; +} diff --git a/nacc2/src/lang/en.js b/nacc2/src/lang/en.js new file mode 100644 index 0000000..70a3295 --- /dev/null +++ b/nacc2/src/lang/en.js @@ -0,0 +1,33 @@ +export const en = { + section_title: 'NA Cleantime Calculator', + prompt: 'Please enter your Clean Date', + calculate_button_text: 'Calculate', + change_layout_button_text: 'Change Tag Layout', + change_use_special_tags_label: 'Show Special Tags', + months: [ + 'ERROR', 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December', + ], + result_invalid: 'Please select a valid clean date!', + result_1_day: 'You have been clean for 1 day!', + result_days_format: 'You have been clean for %d days!', + result_months_format: 'This is %d months.', + result_months_and_1_day_format: 'This is %d months and 1 day.', + result_months_and_days_format: 'This is %d months and %d days.', + result_1_year: 'This is 1 year.', + result_1_year_and_1_day: 'This is 1 year and 1 day.', + result_1_year_and_1_month: 'This is 1 year and 1 month.', + result_1_year_1_month_and_1_day: 'This is 1 year, 1 month and 1 day.', + result_1_year_months_and_1_day_format: 'This is 1 year, %d months and 1 day.', + result_1_year_months_and_days_format: 'This is 1 year, %d months and %d days.', + result_1_year_days_format: 'This is 1 year and %d days.', + result_years_format: 'This is %d years.', + result_years_months_format: 'This is %d years and %d months.', + result_years_1_month_and_1_day_format: 'This is %d years, 1 month and 1 day.', + result_years_months_and_1_day_format: 'This is %d years, %d months and 1 day.', + result_years_and_1_month_format: 'This is %d years and 1 month.', + result_years_and_1_day_format: 'This is %d years and 1 day.', + result_years_and_days_format: 'This is %d years and %d days.', + result_years_1_month_and_days_format: 'This is %d years, 1 month and %d days.', + result_years_months_and_days_format: 'This is %d years, %d months and %d days.', +}; diff --git a/nacc2/src/lang/es.js b/nacc2/src/lang/es.js new file mode 100644 index 0000000..4065f32 --- /dev/null +++ b/nacc2/src/lang/es.js @@ -0,0 +1,33 @@ +export const es = { + section_title: 'Calculadora de Tiempo Limpio NA', + prompt: 'Por favor, entra tu fecha de inicio.', + calculate_button_text: 'Calcular', + change_layout_button_text: 'Cambiar los llaveros de posición.', + change_use_special_tags_label: 'Mostrar Llaveros Especiales.', + months: [ + 'ERROR', 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', + 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre', + ], + result_invalid: '¡Por favor, entra una fecha de inicio válido!', + result_1_day: '¡Has estado limpio por 1 día!', + result_days_format: '¡Has estado limpio por %d días!', + result_months_format: 'Esto suma un total de %d meses.', + result_months_and_1_day_format: 'Esto suma un total de %d meses y un día.', + result_months_and_days_format: 'Esto suma un total de %d meses y %d días.', + result_1_year: 'Esto suma un total de 1 año.', + result_1_year_and_1_day: 'Esto suma un total de 1 año y 1 día.', + result_1_year_and_1_month: 'Esto suma un total de 1 año y 1 mes.', + result_1_year_1_month_and_1_day: 'Esto suma un total de 1 año, 1 mes y 1 día.', + result_1_year_months_and_1_day_format: 'Esto suma un total de 1 año, %d meses y 1 día.', + result_1_year_months_and_days_format: 'Esto suma un total de 1 año, %d meses y %d días.', + result_1_year_days_format: 'Esto suma un total de 1 año y %d días.', + result_years_format: 'Esto suma un total de %d años.', + result_years_months_format: 'Esto suma un total de %d años y %d meses.', + result_years_1_month_and_1_day_format: 'Esto suma un total de %d años, 1 mes y 1 día.', + result_years_months_and_1_day_format: 'Esto suma un total de %d años, %d meses y 1 día.', + result_years_and_1_month_format: 'Esto suma un total de %d años y 1 mes.', + result_years_and_1_day_format: 'Esto suma un total de %d años y 1 día.', + result_years_and_days_format: 'Esto suma un total de %d años y %d días.', + result_years_1_month_and_days_format: 'Esto suma un total de %d años, 1 mes y %d días.', + result_years_months_and_days_format: 'Esto suma un total de %d años, %d meses y %d días.', +}; diff --git a/nacc2/src/lang/index.js b/nacc2/src/lang/index.js new file mode 100644 index 0000000..489ddf9 --- /dev/null +++ b/nacc2/src/lang/index.js @@ -0,0 +1,15 @@ +import { en } from './en.js'; +import { es } from './es.js'; +import { it } from './it.js'; +import { pt } from './pt.js'; +import { zhHans } from './zh-Hans.js'; +import { zhHant } from './zh-Hant.js'; + +export const languages = { + en, + es, + it, + pt, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant, +}; diff --git a/nacc2/src/lang/it.js b/nacc2/src/lang/it.js new file mode 100644 index 0000000..6e72493 --- /dev/null +++ b/nacc2/src/lang/it.js @@ -0,0 +1,33 @@ +export const it = { + section_title: 'NA Tempo di Pulizia', + prompt: 'Inserisci la tua data di pulizia', + calculate_button_text: 'Calcola', + change_layout_button_text: 'Cambia presentazione dei portachiavi', + change_use_special_tags_label: 'Mostra Tag Speciali', + months: [ + 'ERRORE', 'gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', + 'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre', + ], + result_invalid: 'Si prega di selezionare una data pulita valida!', + result_1_day: 'Sei stato pulito per 1 giorno!', + result_days_format: 'Sei pulito da %d giorni!', + result_months_format: '%d mesi.', + result_months_and_1_day_format: '%d mesi e 1 giorno.', + result_months_and_days_format: '%d mesi e %d giorni.', + result_1_year: '1 anno.', + result_1_year_and_1_day: '1 anno e 1 giorno.', + result_1_year_and_1_month: '1 anno e 1 mese.', + result_1_year_1_month_and_1_day: '1 anno, 1 mese e 1 giorno.', + result_1_year_months_and_1_day_format: '1 anno, %d mesi e 1 giorno.', + result_1_year_months_and_days_format: '1 anno, %d mesi e %d giorni.', + result_1_year_days_format: '1 year e %d giorni.', + result_years_format: '%d anni.', + result_years_months_format: '%d anni e %d mesi.', + result_years_1_month_and_1_day_format: '%d anni, 1 mese e 1 giorno.', + result_years_months_and_1_day_format: '%d anni, %d mesi e 1 giorno.', + result_years_and_1_month_format: '%d anni e 1 mese.', + result_years_and_1_day_format: '%d anni e 1 giorno.', + result_years_and_days_format: '%d anni e %d giorni.', + result_years_1_month_and_days_format: '%d anni, 1 mese e %d giorni.', + result_years_months_and_days_format: '%d anni, %d mesi e %d giorni.', +}; diff --git a/nacc2/src/lang/pt.js b/nacc2/src/lang/pt.js new file mode 100644 index 0000000..c2028f4 --- /dev/null +++ b/nacc2/src/lang/pt.js @@ -0,0 +1,33 @@ +export const pt = { + section_title: 'Calculadora de Tempo Limpo', + prompt: 'Entre com a data que ficou limpo', + calculate_button_text: 'Calcular', + change_layout_button_text: 'Mudar Layout das Fichas', + change_use_special_tags_label: 'Mostrar fichas especiais', + months: [ + 'ERROR', 'Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', + 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro', + ], + result_invalid: 'Selecione uma data válida!', + result_1_day: 'Você está limpo há 1 dia!', + result_days_format: 'Você está limpo %d dias!', + result_months_format: 'Que são %d meses.', + result_months_and_1_day_format: 'Que são %d meses e 1 dia.', + result_months_and_days_format: 'Que são %d meses e %d dias.', + result_1_year: 'Que é 1 ano.', + result_1_year_and_1_day: 'Que é 1 ano e 1 dia.', + result_1_year_and_1_month: 'Que é 1 ano e 1 mês.', + result_1_year_1_month_and_1_day: 'Que é 1 ano, 1 mês e 1 dia.', + result_1_year_months_and_1_day_format: 'Que é 1 ano, %d meses e 1 dia.', + result_1_year_months_and_days_format: 'Que é 1 ano, %d meses e %d dias.', + result_1_year_days_format: 'Que é 1 ano e %d dias.', + result_years_format: 'Que são %d anos.', + result_years_months_format: 'Que são %d anos e %d meses.', + result_years_1_month_and_1_day_format: 'Que são %d anos, 1 mês e 1 dia.', + result_years_months_and_1_day_format: 'Que são %d anos, %d meses e 1 dia.', + result_years_and_1_month_format: 'Que são %d anos e 1 mês.', + result_years_and_1_day_format: 'Que são %d anos e 1 dia.', + result_years_and_days_format: 'Que são %d anos e %d dias.', + result_years_1_month_and_days_format: 'Que são %d anos, 1 mês e %d dias.', + result_years_months_and_days_format: 'Que são %d anos, %d meses e %d dias.', +}; diff --git a/nacc2/src/lang/zh-Hans.js b/nacc2/src/lang/zh-Hans.js new file mode 100644 index 0000000..7bb6f61 --- /dev/null +++ b/nacc2/src/lang/zh-Hans.js @@ -0,0 +1,33 @@ +export const zhHans = { + section_title: 'NA 清醒时间计算器', + prompt: '请输入您的清醒日期', + calculate_button_text: '计算', + change_layout_button_text: '更改钥匙扣设置', + change_use_special_tags_label: '显示特殊钥匙扣', + months: [ + '错误', '1月', '2月', '3月', '4月', '5月', '6月', + '7月', '8月', '9月', '10月', '11月', '12月', + ], + result_invalid: '请选择一个有效的清醒日期', + result_1_day: '您已经清醒一天了!', + result_days_format: '您已经清醒%d天了!', + result_months_format: '这是%d个月了', + result_months_and_1_day_format: '这是%d个月零一天了.', + result_months_and_days_format: '这是%d个月%d天了.', + result_1_year: '这是一年了.', + result_1_year_and_1_day: '这是一年零一天了.', + result_1_year_and_1_month: '这是一年一个月了.', + result_1_year_1_month_and_1_day: '这是一年一个月零一天了.', + result_1_year_months_and_1_day_format: '这是一年,%d个月零一天了.', + result_1_year_months_and_days_format: '这是一年,%d个月%d天了.', + result_1_year_days_format: '这是一年,%d天了.', + result_years_format: '这是%d年了.', + result_years_months_format: '这是%d年%d月了.', + result_years_1_month_and_1_day_format: '这是%d年,一个月零一天了.', + result_years_months_and_1_day_format: '这是%d年,%d月零一天了.', + result_years_and_1_month_format: '这是%d年一个月了.', + result_years_and_1_day_format: '这是%d年零一天了.', + result_years_and_days_format: '这是%d年%d天了.', + result_years_1_month_and_days_format: '这是%d年,一个月%d天了.', + result_years_months_and_days_format: '这是%d个年,%d个月,%d天了.', +}; diff --git a/nacc2/src/lang/zh-Hant.js b/nacc2/src/lang/zh-Hant.js new file mode 100644 index 0000000..bf1b3cd --- /dev/null +++ b/nacc2/src/lang/zh-Hant.js @@ -0,0 +1,33 @@ +export const zhHant = { + section_title: 'NA 清醒時間計算器', + prompt: '請輸入您的清醒日期', + calculate_button_text: '計算', + change_layout_button_text: '更改鑰匙扣設置', + change_use_special_tags_label: '顯示特殊鑰匙扣', + months: [ + '錯誤', '1月', '2月', '3月', '4月', '5月', '6月', + '7月', '8月', '9月', '10月', '11月', '12月', + ], + result_invalid: '請選擇一個有效的清醒日期', + result_1_day: '您已經清醒一天了!', + result_days_format: '您已经清醒%d天了!', + result_months_format: '這是%d個月了', + result_months_and_1_day_format: '這是%d個月零一天了.', + result_months_and_days_format: '這是%d個月%d天了.', + result_1_year: '這是一年了.', + result_1_year_and_1_day: '這是一年零一天了.', + result_1_year_and_1_month: '這是一年一個月了.', + result_1_year_1_month_and_1_day: '這是一年一個月零一天了.', + result_1_year_months_and_1_day_format: '這是一年,%d個月零一天了.', + result_1_year_months_and_days_format: '這是一年,%d個月%d天了.', + result_1_year_days_format: '這是一年,%d天了.', + result_years_format: '這是%d年了.', + result_years_months_format: '這是%d年%d月了.', + result_years_1_month_and_1_day_format: '這是%d年,一個月零一天了.', + result_years_months_and_1_day_format: '這是%d年,%d月零一天了.', + result_years_and_1_month_format: '這是%d年一個月了.', + result_years_and_1_day_format: '這是%d年零一天了.', + result_years_and_days_format: '這是%d年%d天了.', + result_years_1_month_and_days_format: '這是%d年,一個月%d天了.', + result_years_months_and_days_format: '這是%d個年,%d個月,%d天了.', +}; diff --git a/nacc2/src/nacc.js b/nacc2/src/nacc.js new file mode 100644 index 0000000..ea5d530 --- /dev/null +++ b/nacc2/src/nacc.js @@ -0,0 +1,433 @@ +import { languages } from './lang/index.js'; +import { dateSpan } from './calc.js'; +import { sprintf } from './sprintf.js'; + +class NACC { + /** @type {HTMLDivElement|null} */ + resultsDiv = null; + /** @type {HTMLDivElement|null} */ + resultsTextDiv = null; + /** @type {HTMLDivElement|null} */ + keytagsDiv = null; + /** @type {HTMLInputElement|null} */ + layoutToggleButton = null; + /** @type {HTMLInputElement|null} */ + specialTagsCheckbox = null; + /** @type {HTMLLabelElement|null} */ + specialTagsLabel = null; + + constructor( + containerId, + style = null, + lang = null, + tagLayout = null, + showSpecialTags = false, + directoryRoot = null, + year = null, + month = null, + day = null, + ) { + const container = document.getElementById(containerId); + if (!container) { + alert('NACC ERROR: INVALID CONTAINER ELEMENT ID'); + throw new Error(`NACC: no element with id "${containerId}"`); + } + this.container = container; + + // Parse GET parameters — they override constructor args + const params = NACC.getParameters(); + + // Directory root + let root = params['NACC-dir-root'] ?? directoryRoot ?? ''; + if (root && !root.endsWith('/')) root += '/'; + this.directoryRoot = root; + + // Style + this.styleSelector = params['NACC-style'] ?? style ?? null; + + // Language + this.langCode = params['NACC-lang'] ?? lang ?? 'en'; + this.lang = languages[this.langCode] ?? languages['en']; + + // Tag layout + const layoutParam = params['NACC-tag-layout'] ?? tagLayout; + if (layoutParam === 'linear' || layoutParam === 'tabular') { + this.tagLayout = layoutParam; + } else { + const saved = localStorage.getItem('nacc_keytag_layout'); + this.tagLayout = saved === 'tabular' ? 'tabular' : 'linear'; + } + + // Special tags + if (params['NACC-special-tags'] || showSpecialTags) { + this.showSpecialTags = true; + } else { + this.showSpecialTags = localStorage.getItem('nacc_keytag_special') === 'true'; + } + + // Set up container classes + if (this.container.className) { + this.container.className += ' NACC-Instance'; + } else { + this.container.className = 'NACC-Instance'; + } + if (this.styleSelector) { + this.container.className += ' ' + this.styleSelector; + } + this.container.innerHTML = ''; + + // Build UI + this.createHeader(); + this.createForm(); + this.evaluateMonthDays(); + + // Check for initial date (GET params > constructor args > localStorage) + let initYear = parseInt(params['NACC-year']) || (year ? Number(year) : 0); + let initMonth = parseInt(params['NACC-month']) || (month ? Number(month) : 0); + let initDay = parseInt(params['NACC-day']) || (day ? Number(day) : 0); + + if (!initYear && !initMonth && !initDay) { + const saved = localStorage.getItem('nacc_clean_date'); + if (saved) { + try { + const obj = JSON.parse(saved); + initYear = parseInt(obj.year) || 0; + initMonth = parseInt(obj.month) || 0; + initDay = parseInt(obj.day) || 0; + } catch { + // invalid saved data + } + } + } + + if (initYear && initMonth && initDay) { + this.monthPopup.selectedIndex = initMonth - 1; + for (let i = 0; i < this.yearPopup.options.length; i++) { + if (parseInt(this.yearPopup.options[i].value) === initYear) { + this.yearPopup.selectedIndex = i; + break; + } + } + this.dayPopup.selectedIndex = initDay - 1; + this.evaluateMonthDays(); + this.doCalculation(); + } + } + + // ── GET Parameters ────────────────────────────────────────────── + + static getParameters() { + const search = window.location.search.substring(1); + if (!search) return {}; + const params = {}; + for (const pair of search.split('&')) { + const [key, val] = pair.split('='); + params[decodeURIComponent(key)] = decodeURIComponent(val ?? ''); + } + return params; + } + + // ── Calculation ───────────────────────────────────────────────── + + doCalculation() { + const year = parseInt(this.yearPopup.value); + const month = parseInt(this.monthPopup.value) - 1; + const day = parseInt(this.dayPopup.value); + const result = dateSpan(new Date(year, month, day)); + this.displayCalculationResults(result); + } + + onCalculate(source) { + if (source === 'layout') { + this.tagLayout = this.tagLayout === 'linear' ? 'tabular' : 'linear'; + localStorage.setItem('nacc_keytag_layout', this.tagLayout); + } + + if (source === 'specialTags') { + this.showSpecialTags = !this.showSpecialTags; + localStorage.setItem('nacc_keytag_special', String(this.showSpecialTags)); + } + + if (source === 'button') { + const year = parseInt(this.yearPopup.value); + const month = parseInt(this.monthPopup.value); + const day = parseInt(this.dayPopup.value); + localStorage.setItem('nacc_clean_date', JSON.stringify({ year, month, day })); + } + + this.doCalculation(); + } + + // ── Result Display ────────────────────────────────────────────── + + displayCalculationResults(r) { + const L = this.lang; + let daysBlurb = ''; + let mainBlurb = ''; + + if (r.totalDays === 0) { + daysBlurb = L.result_invalid; + } else if (r.totalDays === 1) { + daysBlurb = L.result_1_day; + } else { + daysBlurb = sprintf(L.result_days_format, r.totalDays); + + if (r.totalDays > 90) { + if (r.years > 0) { + mainBlurb = this.formatYearsBlurb(r); + } else { + mainBlurb = this.formatMonthsBlurb(r); + } + } + } + + const totalMonths = r.years * 12 + r.months; + this.createResultsDiv(r.totalDays, totalMonths, daysBlurb, mainBlurb); + } + + formatYearsBlurb(r) { + const L = this.lang; + const { years: y, months: m, days: d } = r; + + if (y === 1) { + if (m === 0 && d === 0) return L.result_1_year; + if (m === 1 && d === 0) return L.result_1_year_and_1_month; + if (m === 1 && d === 1) return L.result_1_year_1_month_and_1_day; + if (m === 0 && d === 1) return L.result_1_year_and_1_day; + if (m === 0 && d > 1) return sprintf(L.result_1_year_days_format, d); + if (m > 1 && d > 1) return sprintf(L.result_1_year_months_and_days_format, m, d); + if (m > 1 && d === 1) return sprintf(L.result_1_year_months_and_1_day_format, m); + if (m > 1 && d === 0) return sprintf(L.result_years_months_format, 1, m); + } + + if (m === 0 && d === 0) return sprintf(L.result_years_format, y); + if (m === 1 && d === 0) return sprintf(L.result_years_and_1_month_format, y); + if (m === 0 && d === 1) return sprintf(L.result_years_and_1_day_format, y); + if (m === 0 && d > 1) return sprintf(L.result_years_and_days_format, y, d); + if (m > 1 && d === 0) return sprintf(L.result_years_months_format, y, m); + if (m === 1 && d === 1) return sprintf(L.result_years_1_month_and_1_day_format, y); + if (m === 1 && d > 1) return sprintf(L.result_years_1_month_and_days_format, y, d); + if (m > 1 && d === 1) return sprintf(L.result_years_months_and_1_day_format, y, m); + return sprintf(L.result_years_months_and_days_format, y, m, d); + } + + formatMonthsBlurb(r) { + const L = this.lang; + const { months: m, days: d } = r; + + if (m > 1 && d === 0) return sprintf(L.result_months_format, m); + if (m > 1 && d === 1) return sprintf(L.result_months_and_1_day_format, m); + if (m > 1 && d > 1) return sprintf(L.result_months_and_days_format, m, d); + return ''; + } + + // ── Month Days Validation ─────────────────────────────────────── + + evaluateMonthDays() { + const numDays = new Date( + parseInt(this.yearPopup.value), + parseInt(this.monthPopup.value), + 0, + ).getDate(); + + this.dayPopup.selectedIndex = Math.min(this.dayPopup.selectedIndex + 1, numDays) - 1; + + for (let i = 0; i < this.dayPopup.options.length; i++) { + this.dayPopup.options[i].disabled = i >= numDays; + } + } + + // ── DOM Construction Helpers ──────────────────────────────────── + + el(tag, className, parent) { + const elem = document.createElement(tag); + if (className) elem.className = className; + elem.id = className.replace(/\s+/g, '-') + '-' + Math.random().toString(36).substring(2, 12); + if (parent) parent.appendChild(elem); + return elem; + } + + // ── Header ────────────────────────────────────────────────────── + + createHeader() { + const header = this.el('div', 'NACC-Header', this.container); + header.innerHTML = this.lang.section_title; + } + + // ── Form Structure ────────────────────────────────────────────── + + createForm() { + const form = this.el('form', 'NACC-Form', this.container); + const fieldset = this.el('fieldset', 'NACC-Fieldset', form); + const legend = this.el('legend', 'NACC-Legend', fieldset); + const legendDiv = this.el('div', 'NACC-Legend-div', legend); + + this.promptLabel = this.el('label', 'NACC-Prompt-Label', legendDiv); + this.promptLabel.innerHTML = this.lang.prompt; + + this.popupContainer = this.el('div', 'NACC-Popups', legendDiv); + + this.createMonthPopup(); + this.createDayPopup(); + this.createYearPopup(); + this.createCalculateButton(); + this.el('div', 'breaker', this.popupContainer); + } + + createMonthPopup() { + this.monthPopup = this.el('select', 'NACC-Month', this.popupContainer); + this.promptLabel.setAttribute('for', this.monthPopup.id); + + const nowMonth = new Date().getMonth(); + for (let i = 1; i <= 12; i++) { + const opt = this.el('option', 'NACC-Option', this.monthPopup); + opt.value = String(i); + opt.innerHTML = this.lang.months[i]; + } + this.monthPopup.selectedIndex = nowMonth; + this.monthPopup.onchange = () => this.evaluateMonthDays(); + } + + createDayPopup() { + this.dayPopup = this.el('select', 'NACC-Day', this.popupContainer); + + const nowDay = new Date().getDate(); + for (let d = 1; d <= 31; d++) { + const opt = this.el('option', 'NACC-Option', this.dayPopup); + opt.value = String(d); + opt.innerHTML = String(d); + } + this.dayPopup.selectedIndex = nowDay - 1; + } + + createYearPopup() { + this.yearPopup = this.el('select', 'NACC-Year', this.popupContainer); + + const nowYear = new Date().getFullYear(); + for (let y = 1953; y <= nowYear; y++) { + const opt = this.el('option', 'NACC-Option', this.yearPopup); + opt.value = String(y); + opt.innerHTML = String(y); + } + this.yearPopup.selectedIndex = this.yearPopup.options.length - 1; + this.yearPopup.onchange = () => this.evaluateMonthDays(); + } + + createCalculateButton() { + this.calculateButton = this.el('input', 'NACC-Calculate-Button', this.popupContainer); + this.calculateButton.type = 'button'; + this.calculateButton.value = this.lang.calculate_button_text; + this.calculateButton.onclick = () => this.onCalculate('button'); + } + + // ── Results ───────────────────────────────────────────────────── + + createResultsDiv(numDays, totalMonths, daysBlurb, mainBlurb) { + if (this.resultsDiv) { + this.resultsDiv.innerHTML = ''; + this.resultsTextDiv = null; + this.keytagsDiv = null; + } else { + this.resultsDiv = this.el('div', 'NACC-Results', this.container.querySelector('.NACC-Fieldset')); + } + + // Text results + if (daysBlurb) { + this.resultsTextDiv = this.el('div', 'NACC-Results-Text', this.resultsDiv); + const daysDiv = this.el('div', 'NACC-Days', this.resultsTextDiv); + daysDiv.innerHTML = daysBlurb; + if (mainBlurb) { + const mainDiv = this.el('div', 'NACC-MainBlurb', this.resultsTextDiv); + mainDiv.innerHTML = mainBlurb; + } + } + + // Keytags + if (numDays > 0) { + const tabularClass = this.tagLayout !== 'linear' ? ' NACC-Keytag-Tabular' : ''; + this.keytagsDiv = this.el('div', 'NACC-Keytags' + tabularClass, this.resultsDiv); + this.createTagsArray(numDays, totalMonths); + } + } + + // ── Keytag Creation ───────────────────────────────────────────── + + createKeytag(tagNum, isFace, isClosed) { + const closed = isClosed ?? !isFace; + const className = 'NACC-Keytag' + (closed || this.tagLayout !== 'linear' ? ' NACC-Keytag-Ringtop' : ''); + const img = this.el('img', className, this.keytagsDiv); + const suffix = isFace ? '_Front' : ''; + img.src = `${this.directoryRoot}images/${this.langCode}/${tagNum}${suffix}.png`; + return img; + } + + createTagsArray(numDays, totalMonths) { + const isFace = this.tagLayout === 'linear'; + + if (numDays >= 1) { + const tag = this.createKeytag('01', isFace, true); + tag.className += ' NACC-White-Tag'; + } + if (numDays >= 30) this.createKeytag('02', isFace); + if (numDays >= 60) this.createKeytag('03', isFace); + if (numDays >= 90) this.createKeytag('04', isFace); + + if (numDays > 90) { + if (totalMonths > 5) this.createKeytag('05', isFace); + if (totalMonths > 8) this.createKeytag('06', isFace); + if (totalMonths > 11) this.createKeytag('07', isFace); + if (totalMonths > 17) this.createKeytag('08', isFace); + if (totalMonths > 23) this.createKeytag('09', isFace); + + for (let i = 24; i <= totalMonths - 12; i += 12) { + const comp = i + 12; + let specialTag = false; + + if (this.showSpecialTags) { + if (comp === 60) { specialTag = true; this.createKeytag('15', isFace); } + if (comp === 120) { specialTag = true; this.createKeytag('10', isFace); } + if (comp === 180) { specialTag = true; this.createKeytag('16', isFace); } + if (!specialTag && comp === 300) { specialTag = true; this.createKeytag('12', isFace); } + if (!specialTag && comp === 360) { specialTag = true; this.createKeytag('14', isFace); } + if (!specialTag && comp % 120 === 0) { specialTag = true; this.createKeytag('11', isFace); } + } + + if (!specialTag) this.createKeytag('09', isFace); + + if (this.showSpecialTags && comp === 324 && numDays > 9999) { + this.createKeytag('13', isFace); + } + } + } + + this.createLayoutToggle(); + } + + createLayoutToggle() { + this.layoutToggleButton?.parentNode?.removeChild(this.layoutToggleButton); + this.specialTagsCheckbox?.parentNode?.removeChild(this.specialTagsCheckbox); + this.specialTagsLabel?.parentNode?.removeChild(this.specialTagsLabel); + + this.layoutToggleButton = this.el('input', 'NACC-Change-Layout-Button', this.popupContainer); + this.layoutToggleButton.type = 'button'; + this.layoutToggleButton.value = this.lang.change_layout_button_text; + this.layoutToggleButton.onclick = () => this.onCalculate('layout'); + + this.specialTagsCheckbox = this.el('input', 'NACC-Show-Special-Tags-Checkbox', this.popupContainer); + this.specialTagsCheckbox.type = 'checkbox'; + this.specialTagsCheckbox.checked = this.showSpecialTags; + this.specialTagsCheckbox.value = '1'; + this.specialTagsCheckbox.onclick = () => this.onCalculate('specialTags'); + + this.specialTagsLabel = this.el('label', 'NACC-Show-Special-Tags-Checkbox-Label', this.popupContainer); + this.specialTagsLabel.setAttribute('for', this.specialTagsCheckbox.id); + this.specialTagsLabel.innerHTML = this.lang.change_use_special_tags_label; + + this.el('div', 'breaker', this.popupContainer); + } +} + +// Expose globally for - Test NACC - - - -

TEST THE NACC

-
-
-
-
-
- - -
-
- - -
-
-
-
-
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - - - - - -
-
- -
-
-
- - diff --git a/nacc2/vite.config.js b/nacc2/vite.config.js new file mode 100644 index 0000000..8230027 --- /dev/null +++ b/nacc2/vite.config.js @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite'; +import { resolve } from 'path'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export default defineConfig({ + build: { + lib: { + entry: resolve(__dirname, 'src/nacc.js'), + name: 'NACC', + formats: ['iife'], + fileName: () => 'nacc.js', + }, + outDir: '.', + emptyOutDir: false, + }, +}); diff --git a/readme.txt b/readme.txt index 0ff8f8c..f5ae128 100644 --- a/readme.txt +++ b/readme.txt @@ -4,8 +4,8 @@ Contributors: magblogapi, bmltenabled, pjaudiomv Plugin URI: https://wordpress.org/plugins/nacc-wordpress-plugin/ Tags: na, cleantime calculator, nacc, recovery, addiction Requires PHP: 8.0 -Tested up to: 6.9 -Stable tag: 5.1.1 +Tested up to: 7.0 +Stable tag: 5.2.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -30,6 +30,10 @@ That text will be replaced with this cleantime calculator. == Changelog == += 5.2.0 = + +* Migrated JS to ES6. + = 5.1.1 = * Added layout and special settings to localStorage.