From be884f2c9785c0294dcd31d5da31cd6f62a12b9c Mon Sep 17 00:00:00 2001 From: Soham Banerjee Date: Fri, 23 Oct 2026 15:23:31 +0530 Subject: [PATCH 1/6] Address WordPress.org review: prefixed post type, scoped notice, Select2 4.1.0 - Register FAQs as `fqj_faq` (FQJ_POST_TYPE) instead of the generic `faq_item`. fqj_migrate_legacy_post_types() renames rows stored under a type in FQJ_LEGACY_POST_TYPES once per site, on plugins_loaded priority 5 and on activation, so the schema upgrade, data migration 3 and the activation FAQ check all see the new type. Uninstall deletes both types. - Show the queue notice only on the plugin's own screens (Guideline 11), and make its text translatable. - Update the bundled Select2 from 4.0.13 to 4.1.0. - Bump to 3.1.1. Co-Authored-By: Claude --- .github/copilot-instructions.md | 2 +- AGENTS.md | 2 +- CLAUDE.md | 6 +- README.md | 4 +- assets/vendor/select2/select2.css | 771 ++++++++++++-------- assets/vendor/select2/select2.js | 926 +++++++++++++++---------- assets/vendor/select2/select2.min.css | 2 +- assets/vendor/select2/select2.min.js | 4 +- composer.json | 2 +- composer.lock | 4 +- faq-jsonld.php | 41 +- includes/admin.php | 20 +- includes/coverage.php | 4 +- includes/frontend.php | 2 +- includes/health.php | 4 +- includes/indexer.php | 8 +- includes/queue.php | 16 +- includes/settings.php | 2 +- readme.txt | 12 +- tests/php/class-fqj-testcase.php | 2 +- tests/php/test-admin.php | 4 +- tests/php/test-ajax.php | 4 +- tests/php/test-frontend.php | 2 +- tests/php/test-health.php | 4 +- tests/php/test-indexer.php | 10 +- tests/php/test-multisite.php | 20 + tests/php/test-plugin.php | 10 +- tests/php/test-post-type-migration.php | 104 +++ tests/php/test-queue.php | 38 + tests/php/test-settings.php | 2 +- tests/php/test-uninstall.php | 12 +- tests/php/test-url-migration.php | 2 +- uninstall.php | 6 +- 33 files changed, 1313 insertions(+), 739 deletions(-) create mode 100644 tests/php/test-post-type-migration.php diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 37c71da..bbe5c74 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -6,7 +6,7 @@ This plugin manages FAQ items as a custom post type (CPT) and injects FAQ JSON-L ## Architecture & Key Components -- **Custom Post Type:** `faq_item` is registered for FAQ entries. +- **Custom Post Type:** `fqj_faq` (formerly `faq_item`) is registered for FAQ entries. - **Mapping Table:** MySQL table (`fqj_mappings`) links FAQ items to posts, post types, terms, URLs, or global context. See `includes/indexer.php`. - **JSON-LD Output:** Injected on the frontend using `includes/frontend.php`, with per-post transient caching for performance. - **Cache Invalidation:** Uses a background queue (`includes/queue.php`) processed by WP-Cron or WP-CLI. Queue state and logs are visible in the Health admin page. diff --git a/AGENTS.md b/AGENTS.md index 2f922f6..1fcf18e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md -WordPress plugin `faq-jsonld`: FAQs as a `faq_item` CPT, schema.org `FAQPage` JSON-LD printed on singular posts/pages. Procedural PHP, every function prefixed `fqj_`, no build step, no runtime Composer deps. +WordPress plugin `faq-jsonld`: FAQs as a `fqj_faq` CPT (`FQJ_POST_TYPE`; renamed from `faq_item` on upgrade), schema.org `FAQPage` JSON-LD printed on singular posts/pages. Procedural PHP, every function prefixed `fqj_`, no build step, no runtime Composer deps. **PHP ceiling is 7.2 for shipped code** — `composer.json` pins platform 7.4, so syntax up to 7.4 passes locally while CI's `php -l` matrix (7.2–8.4) fails. Write 7.2-compatible syntax in `faq-jsonld.php`, `uninstall.php`, `includes/`. diff --git a/CLAUDE.md b/CLAUDE.md index 054ea11..59623b6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -WordPress plugin (slug `faq-jsonld`, published on WordPress.org) that manages FAQs as a `faq_item` custom post type and prints schema.org `FAQPage` JSON-LD in `wp_head` on the singular posts/pages each FAQ is mapped to. Procedural PHP, every function prefixed `fqj_`; no build step and no runtime Composer dependencies. Supports WordPress 6.5+ and PHP 7.2+, so don't use syntax newer than PHP 7.2 in shipped code. +WordPress plugin (slug `faq-jsonld`, published on WordPress.org) that manages FAQs as a `fqj_faq` custom post type (`FQJ_POST_TYPE`) and prints schema.org `FAQPage` JSON-LD in `wp_head` on the singular posts/pages each FAQ is mapped to. Procedural PHP, every function prefixed `fqj_`; no build step and no runtime Composer dependencies. Supports WordPress 6.5+ and PHP 7.2+, so don't use syntax newer than PHP 7.2 in shipped code. ## Commands @@ -25,7 +25,7 @@ PHPUnit runs against a real MySQL/MariaDB database that the suite **wipes on eve ## Architecture -`faq-jsonld.php` defines constants, requires every file in `includes/`, registers the CPT, and handles activation/deactivation (looping over all sites via `fqj_for_each_site()` on network activation and setting up new sites on `wp_initialize_site`). +`faq-jsonld.php` defines constants, requires every file in `includes/`, registers the CPT (renaming FAQs stored under a post type in `FQJ_LEGACY_POST_TYPES` once per site, on `plugins_loaded` priority 5 and on activation, before the schema upgrade and data migration), and handles activation/deactivation (looping over all sites via `fqj_for_each_site()` on network activation and setting up new sites on `wp_initialize_site`). Data flow: @@ -42,7 +42,7 @@ Schema and data versioning (constants in `faq-jsonld.php`): - `FQJ_DB_VERSION`: bump whenever the `CREATE TABLE` SQL in `fqj_create_table()` changes. `fqj_maybe_upgrade_db()` reruns `dbDelta` on `plugins_loaded`. dbDelta needs a strict format (no `IF NOT EXISTS`, two spaces after `PRIMARY KEY`, `KEY` not `INDEX`, no inline comments). - `FQJ_DATA_VERSION`: bump to force a one-time reindex of every FAQ plus a cache purge on existing sites. `fqj_run_data_migration()` processes FAQs in batches through a cron event, keeping its place in a cursor option. Version 3 (`fqj_migrate_url_associations()`) reads each FAQ's old `post` rows before reindexing and rewrites URL associations that only matched through them, recording each change in the non-autoloaded `fqj_url_migration_report` option shown on the FAQ list screen until dismissed. -`uninstall.php` removes options, transients, cron events, post meta and both tables on every site. `faq_item` posts are kept unless the `fqj_uninstall_delete_faqs` filter returns true. +`uninstall.php` removes options, transients, cron events, post meta and both tables on every site. FAQ posts (`fqj_faq`, plus any left under the legacy `faq_item`) are kept unless the `fqj_uninstall_delete_faqs` filter returns true. Admin JS (`assets/js/fqj-admin.js`, `fqj-health.js`) uses jQuery and a bundled Select2 (`assets/vendor/select2`, not a CDN). It talks to `wp_ajax_fqj_*` handlers protected by the `fqj_admin_nonce` nonce, and gets its strings through `wp_localize_script`. diff --git a/README.md b/README.md index 660abb9..720cc0d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A WordPress plugin to manage FAQ items as a custom post type (CPT) and inject FA ## Features -- **Custom Post Type:** Manage FAQ items via the `faq_item` CPT. +- **Custom Post Type:** Manage FAQ items via the `fqj_faq` CPT (named `faq_item` before 3.1.1; existing FAQs are renamed on upgrade). - **Mapping Table:** Fast MySQL table (`fqj_mappings`) links FAQ items to posts, post types, terms, URLs, or global context. - **JSON-LD Output:** Injects FAQ schema on the frontend, with per-post transient caching. - **Cache Invalidation:** Background queue (WP-Cron or WP-CLI) for efficient cache purging. @@ -61,7 +61,7 @@ A WordPress plugin to manage FAQ items as a custom post type (CPT) and inject FA - `assets/js/fqj-admin.js`, `assets/js/fqj-health.js`: Admin JS (Select2 is bundled in `assets/vendor/select2`) - **Mapping Types:** All associations are stored in a single table for fast lookups. - **Transient Keys:** `fqj_faq_json_{generation}_{post_id}` (build with `fqj_cache_key( $post_id )`). "Purge all" bumps the `fqj_cache_gen` option, which works with persistent object caches too. -- **Uninstall:** Deleting the plugin removes its options, transients, cron events, post meta and mapping table (on every site of a multisite network). `faq_item` posts are kept by default; to delete them as well, add `add_filter( 'fqj_uninstall_delete_faqs', '__return_true' );` (e.g. in a mu-plugin) before deleting the plugin. +- **Uninstall:** Deleting the plugin removes its options, transients, cron events, post meta and mapping table (on every site of a multisite network). FAQ posts are kept by default; to delete them as well, add `add_filter( 'fqj_uninstall_delete_faqs', '__return_true' );` (e.g. in a mu-plugin) before deleting the plugin. - **Admin JS:** Uses Select2 for post selectors, AJAX for search (`fqj_search_posts` action; all public post types except attachments and FAQs). Switching the association type keeps each type's in-progress selection until you save; only the selected type is stored. - **Background Processing:** The invalidation queue lives in a database table and is processed every 5 minutes by WP-Cron (up to the batch size per run), or on demand from the Health page or WP-CLI. - **Asset versions:** The plugin's own JS is versioned with `FQJ_VERSION`; keep it in sync with the `Version` header when releasing. diff --git a/assets/vendor/select2/select2.css b/assets/vendor/select2/select2.css index 750b320..65dbba6 100644 --- a/assets/vendor/select2/select2.css +++ b/assets/vendor/select2/select2.css @@ -3,49 +3,72 @@ display: inline-block; margin: 0; position: relative; - vertical-align: middle; } - .select2-container .select2-selection--single { - box-sizing: border-box; - cursor: pointer; - display: block; - height: 28px; - user-select: none; - -webkit-user-select: none; } - .select2-container .select2-selection--single .select2-selection__rendered { - display: block; - padding-left: 8px; - padding-right: 20px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } - .select2-container .select2-selection--single .select2-selection__clear { - position: relative; } - .select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered { - padding-right: 8px; - padding-left: 20px; } - .select2-container .select2-selection--multiple { - box-sizing: border-box; - cursor: pointer; - display: block; - min-height: 32px; - user-select: none; - -webkit-user-select: none; } - .select2-container .select2-selection--multiple .select2-selection__rendered { - display: inline-block; - overflow: hidden; - padding-left: 8px; - text-overflow: ellipsis; - white-space: nowrap; } - .select2-container .select2-search--inline { - float: left; } - .select2-container .select2-search--inline .select2-search__field { - box-sizing: border-box; - border: none; - font-size: 100%; - margin-top: 5px; - padding: 0; } - .select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button { - -webkit-appearance: none; } + vertical-align: middle; +} +.select2-container .select2-selection--single { + box-sizing: border-box; + cursor: pointer; + display: block; + height: 28px; + user-select: none; + -webkit-user-select: none; +} +.select2-container .select2-selection--single .select2-selection__rendered { + display: block; + padding-left: 8px; + padding-right: 20px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.select2-container .select2-selection--single .select2-selection__clear { + background-color: transparent; + border: none; + font-size: 1em; +} +.select2-container[dir=rtl] .select2-selection--single .select2-selection__rendered { + padding-right: 8px; + padding-left: 20px; +} +.select2-container .select2-selection--multiple { + box-sizing: border-box; + cursor: pointer; + display: block; + min-height: 32px; + user-select: none; + -webkit-user-select: none; +} +.select2-container .select2-selection--multiple .select2-selection__rendered { + display: inline; + list-style: none; + padding: 0; +} +.select2-container .select2-selection--multiple .select2-selection__clear { + background-color: transparent; + border: none; + font-size: 1em; +} +.select2-container .select2-search--inline .select2-search__field { + box-sizing: border-box; + border: none; + font-size: 100%; + margin-top: 5px; + margin-left: 5px; + padding: 0; + max-width: 100%; + resize: none; + height: 20px; + vertical-align: bottom; + font-family: sans-serif; + overflow: hidden; + word-break: keep-all; +} +.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button { + -webkit-appearance: none; +} +.select2-container .select2-search--inline .select2-search__field::placeholder { + line-height: normal; +} .select2-dropdown { background-color: white; @@ -56,47 +79,63 @@ position: absolute; left: -100000px; width: 100%; - z-index: 1051; } + z-index: 1051; +} .select2-results { - display: block; } + display: block; +} .select2-results__options { list-style: none; margin: 0; - padding: 0; } + padding: 0; +} .select2-results__option { padding: 6px; user-select: none; - -webkit-user-select: none; } - .select2-results__option[aria-selected] { - cursor: pointer; } + -webkit-user-select: none; +} + +.select2-results__option--selectable { + cursor: pointer; +} .select2-container--open .select2-dropdown { - left: 0; } + left: 0; +} .select2-container--open .select2-dropdown--above { border-bottom: none; border-bottom-left-radius: 0; - border-bottom-right-radius: 0; } + border-bottom-right-radius: 0; +} .select2-container--open .select2-dropdown--below { border-top: none; border-top-left-radius: 0; - border-top-right-radius: 0; } + border-top-right-radius: 0; +} .select2-search--dropdown { display: block; - padding: 4px; } - .select2-search--dropdown .select2-search__field { - padding: 4px; - width: 100%; - box-sizing: border-box; } - .select2-search--dropdown .select2-search__field::-webkit-search-cancel-button { - -webkit-appearance: none; } - .select2-search--dropdown.select2-search--hide { - display: none; } + padding: 4px; +} + +.select2-search--dropdown .select2-search__field { + padding: 4px; + width: 100%; + box-sizing: border-box; +} + +.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button { + -webkit-appearance: none; +} + +.select2-search--dropdown.select2-search--hide { + display: none; +} .select2-close-mask { border: 0; @@ -113,7 +152,8 @@ opacity: 0; z-index: 99; background-color: #fff; - filter: alpha(opacity=0); } + filter: alpha(opacity=0); +} .select2-hidden-accessible { border: 0 !important; @@ -125,178 +165,275 @@ padding: 0 !important; position: absolute !important; width: 1px !important; - white-space: nowrap !important; } + white-space: nowrap !important; +} .select2-container--default .select2-selection--single { background-color: #fff; border: 1px solid #aaa; - border-radius: 4px; } - .select2-container--default .select2-selection--single .select2-selection__rendered { - color: #444; - line-height: 28px; } - .select2-container--default .select2-selection--single .select2-selection__clear { - cursor: pointer; - float: right; - font-weight: bold; } - .select2-container--default .select2-selection--single .select2-selection__placeholder { - color: #999; } - .select2-container--default .select2-selection--single .select2-selection__arrow { - height: 26px; - position: absolute; - top: 1px; - right: 1px; - width: 20px; } - .select2-container--default .select2-selection--single .select2-selection__arrow b { - border-color: #888 transparent transparent transparent; - border-style: solid; - border-width: 5px 4px 0 4px; - height: 0; - left: 50%; - margin-left: -4px; - margin-top: -2px; - position: absolute; - top: 50%; - width: 0; } - -.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear { - float: left; } - -.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow { + border-radius: 4px; +} + +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: #444; + line-height: 28px; +} + +.select2-container--default .select2-selection--single .select2-selection__clear { + cursor: pointer; + float: right; + font-weight: bold; + height: 26px; + margin-right: 20px; + padding-right: 0px; +} + +.select2-container--default .select2-selection--single .select2-selection__placeholder { + color: #999; +} + +.select2-container--default .select2-selection--single .select2-selection__arrow { + height: 26px; + position: absolute; + top: 1px; + right: 1px; + width: 20px; +} + +.select2-container--default .select2-selection--single .select2-selection__arrow b { + border-color: #888 transparent transparent transparent; + border-style: solid; + border-width: 5px 4px 0 4px; + height: 0; + left: 50%; + margin-left: -4px; + margin-top: -2px; + position: absolute; + top: 50%; + width: 0; +} + +.select2-container--default[dir=rtl] .select2-selection--single .select2-selection__clear { + float: left; +} + +.select2-container--default[dir=rtl] .select2-selection--single .select2-selection__arrow { left: 1px; - right: auto; } + right: auto; +} .select2-container--default.select2-container--disabled .select2-selection--single { background-color: #eee; - cursor: default; } - .select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear { - display: none; } + cursor: default; +} + +.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear { + display: none; +} .select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b { border-color: transparent transparent #888 transparent; - border-width: 0 4px 5px 4px; } + border-width: 0 4px 5px 4px; +} .select2-container--default .select2-selection--multiple { background-color: white; border: 1px solid #aaa; border-radius: 4px; - cursor: text; } - .select2-container--default .select2-selection--multiple .select2-selection__rendered { - box-sizing: border-box; - list-style: none; - margin: 0; - padding: 0 5px; - width: 100%; } - .select2-container--default .select2-selection--multiple .select2-selection__rendered li { - list-style: none; } - .select2-container--default .select2-selection--multiple .select2-selection__clear { - cursor: pointer; - float: right; - font-weight: bold; - margin-top: 5px; - margin-right: 10px; - padding: 1px; } - .select2-container--default .select2-selection--multiple .select2-selection__choice { - background-color: #e4e4e4; - border: 1px solid #aaa; - border-radius: 4px; - cursor: default; - float: left; - margin-right: 5px; - margin-top: 5px; - padding: 0 5px; } - .select2-container--default .select2-selection--multiple .select2-selection__choice__remove { - color: #999; - cursor: pointer; - display: inline-block; - font-weight: bold; - margin-right: 2px; } - .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover { - color: #333; } - -.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline { - float: right; } - -.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice { + cursor: text; + padding-bottom: 5px; + padding-right: 5px; + position: relative; +} + +.select2-container--default .select2-selection--multiple.select2-selection--clearable { + padding-right: 25px; +} + +.select2-container--default .select2-selection--multiple .select2-selection__clear { + cursor: pointer; + font-weight: bold; + height: 20px; + margin-right: 10px; + margin-top: 5px; + position: absolute; + right: 0; + padding: 1px; +} + +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #e4e4e4; + border: 1px solid #aaa; + border-radius: 4px; + box-sizing: border-box; + display: inline-block; margin-left: 5px; - margin-right: auto; } + margin-top: 5px; + padding: 0; + padding-left: 20px; + position: relative; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: bottom; + white-space: nowrap; +} + +.select2-container--default .select2-selection--multiple .select2-selection__choice__display { + cursor: default; + padding-left: 2px; + padding-right: 5px; +} + +.select2-container--default .select2-selection--multiple .select2-selection__choice__remove { + background-color: transparent; + border: none; + border-right: 1px solid #aaa; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + color: #999; + cursor: pointer; + font-size: 1em; + font-weight: bold; + padding: 0 4px; + position: absolute; + left: 0; + top: 0; +} -.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove { - margin-left: 2px; - margin-right: auto; } +.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover, .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:focus { + background-color: #f1f1f1; + color: #333; + outline: none; +} + +.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__choice { + padding: 0 20px 0 0; + margin-left: 5px; + margin-right: auto; +} + +.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__choice__display { + padding-left: 5px; + padding-right: 2px; +} + +.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__choice__remove { + left: auto; + right: 0; + border-left: 1px solid #aaa; + border-right: none; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__clear { + float: left; + margin-left: 10px; + margin-right: auto; +} .select2-container--default.select2-container--focus .select2-selection--multiple { border: solid black 1px; - outline: 0; } + outline: 0; +} .select2-container--default.select2-container--disabled .select2-selection--multiple { background-color: #eee; - cursor: default; } + cursor: default; +} .select2-container--default.select2-container--disabled .select2-selection__choice__remove { - display: none; } + display: none; +} .select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple { border-top-left-radius: 0; - border-top-right-radius: 0; } + border-top-right-radius: 0; +} .select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple { border-bottom-left-radius: 0; - border-bottom-right-radius: 0; } + border-bottom-right-radius: 0; +} .select2-container--default .select2-search--dropdown .select2-search__field { - border: 1px solid #aaa; } + border: 1px solid #aaa; +} .select2-container--default .select2-search--inline .select2-search__field { background: transparent; border: none; outline: 0; box-shadow: none; - -webkit-appearance: textfield; } + -webkit-appearance: textfield; +} .select2-container--default .select2-results > .select2-results__options { max-height: 200px; - overflow-y: auto; } + overflow-y: auto; +} -.select2-container--default .select2-results__option[role=group] { - padding: 0; } +.select2-container--default .select2-results__option .select2-results__option { + padding-left: 1em; +} + +.select2-container--default .select2-results__option .select2-results__option .select2-results__group { + padding-left: 0; +} + +.select2-container--default .select2-results__option .select2-results__option .select2-results__option { + margin-left: -1em; + padding-left: 2em; +} + +.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -2em; + padding-left: 3em; +} + +.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -3em; + padding-left: 4em; +} + +.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -4em; + padding-left: 5em; +} + +.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -5em; + padding-left: 6em; +} + +.select2-container--default .select2-results__option--group { + padding: 0; +} -.select2-container--default .select2-results__option[aria-disabled=true] { - color: #999; } +.select2-container--default .select2-results__option--disabled { + color: #999; +} -.select2-container--default .select2-results__option[aria-selected=true] { - background-color: #ddd; } +.select2-container--default .select2-results__option--selected { + background-color: #ddd; +} -.select2-container--default .select2-results__option .select2-results__option { - padding-left: 1em; } - .select2-container--default .select2-results__option .select2-results__option .select2-results__group { - padding-left: 0; } - .select2-container--default .select2-results__option .select2-results__option .select2-results__option { - margin-left: -1em; - padding-left: 2em; } - .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option { - margin-left: -2em; - padding-left: 3em; } - .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { - margin-left: -3em; - padding-left: 4em; } - .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { - margin-left: -4em; - padding-left: 5em; } - .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { - margin-left: -5em; - padding-left: 6em; } - -.select2-container--default .select2-results__option--highlighted[aria-selected] { +.select2-container--default .select2-results__option--highlighted.select2-results__option--selectable { background-color: #5897fb; - color: white; } + color: white; +} .select2-container--default .select2-results__group { cursor: default; display: block; - padding: 6px; } + padding: 6px; +} .select2-container--classic .select2-selection--single { - background-color: #f7f7f7; + background-color: rgb(246.5, 246.5, 246.5); border: 1px solid #aaa; border-radius: 4px; outline: 0; @@ -304,67 +441,88 @@ background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%); background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%); background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); } - .select2-container--classic .select2-selection--single:focus { - border: 1px solid #5897fb; } - .select2-container--classic .select2-selection--single .select2-selection__rendered { - color: #444; - line-height: 28px; } - .select2-container--classic .select2-selection--single .select2-selection__clear { - cursor: pointer; - float: right; - font-weight: bold; - margin-right: 10px; } - .select2-container--classic .select2-selection--single .select2-selection__placeholder { - color: #999; } - .select2-container--classic .select2-selection--single .select2-selection__arrow { - background-color: #ddd; - border: none; - border-left: 1px solid #aaa; - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - height: 26px; - position: absolute; - top: 1px; - right: 1px; - width: 20px; - background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%); - background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%); - background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); } - .select2-container--classic .select2-selection--single .select2-selection__arrow b { - border-color: #888 transparent transparent transparent; - border-style: solid; - border-width: 5px 4px 0 4px; - height: 0; - left: 50%; - margin-left: -4px; - margin-top: -2px; - position: absolute; - top: 50%; - width: 0; } - -.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear { - float: left; } - -.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow { + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); +} + +.select2-container--classic .select2-selection--single:focus { + border: 1px solid #5897fb; +} + +.select2-container--classic .select2-selection--single .select2-selection__rendered { + color: #444; + line-height: 28px; +} + +.select2-container--classic .select2-selection--single .select2-selection__clear { + cursor: pointer; + float: right; + font-weight: bold; + height: 26px; + margin-right: 20px; +} + +.select2-container--classic .select2-selection--single .select2-selection__placeholder { + color: #999; +} + +.select2-container--classic .select2-selection--single .select2-selection__arrow { + background-color: #ddd; + border: none; + border-left: 1px solid #aaa; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + height: 26px; + position: absolute; + top: 1px; + right: 1px; + width: 20px; + background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%); + background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%); + background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); +} + +.select2-container--classic .select2-selection--single .select2-selection__arrow b { + border-color: #888 transparent transparent transparent; + border-style: solid; + border-width: 5px 4px 0 4px; + height: 0; + left: 50%; + margin-left: -4px; + margin-top: -2px; + position: absolute; + top: 50%; + width: 0; +} + +.select2-container--classic[dir=rtl] .select2-selection--single .select2-selection__clear { + float: left; +} + +.select2-container--classic[dir=rtl] .select2-selection--single .select2-selection__arrow { border: none; border-right: 1px solid #aaa; border-radius: 0; border-top-left-radius: 4px; border-bottom-left-radius: 4px; left: 1px; - right: auto; } + right: auto; +} .select2-container--classic.select2-container--open .select2-selection--single { - border: 1px solid #5897fb; } - .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow { - background: transparent; - border: none; } - .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b { - border-color: transparent transparent #888 transparent; - border-width: 0 4px 5px 4px; } + border: 1px solid #5897fb; +} + +.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow { + background: transparent; + border: none; +} + +.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b { + border-color: transparent transparent #888 transparent; + border-width: 0 4px 5px 4px; +} .select2-container--classic.select2-container--open.select2-container--above .select2-selection--single { border-top: none; @@ -374,7 +532,8 @@ background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%); background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%); background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); } + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); +} .select2-container--classic.select2-container--open.select2-container--below .select2-selection--single { border-bottom: none; @@ -384,98 +543,140 @@ background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%); background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%); background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); } + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); +} .select2-container--classic .select2-selection--multiple { background-color: white; border: 1px solid #aaa; border-radius: 4px; cursor: text; - outline: 0; } - .select2-container--classic .select2-selection--multiple:focus { - border: 1px solid #5897fb; } - .select2-container--classic .select2-selection--multiple .select2-selection__rendered { - list-style: none; - margin: 0; - padding: 0 5px; } - .select2-container--classic .select2-selection--multiple .select2-selection__clear { - display: none; } - .select2-container--classic .select2-selection--multiple .select2-selection__choice { - background-color: #e4e4e4; - border: 1px solid #aaa; - border-radius: 4px; - cursor: default; - float: left; - margin-right: 5px; - margin-top: 5px; - padding: 0 5px; } - .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove { - color: #888; - cursor: pointer; - display: inline-block; - font-weight: bold; - margin-right: 2px; } - .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover { - color: #555; } - -.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice { - float: right; + outline: 0; + padding-bottom: 5px; + padding-right: 5px; +} + +.select2-container--classic .select2-selection--multiple:focus { + border: 1px solid #5897fb; +} + +.select2-container--classic .select2-selection--multiple .select2-selection__clear { + display: none; +} + +.select2-container--classic .select2-selection--multiple .select2-selection__choice { + background-color: #e4e4e4; + border: 1px solid #aaa; + border-radius: 4px; + display: inline-block; + margin-left: 5px; + margin-top: 5px; + padding: 0; +} + +.select2-container--classic .select2-selection--multiple .select2-selection__choice__display { + cursor: default; + padding-left: 2px; + padding-right: 5px; +} + +.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove { + background-color: transparent; + border: none; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + color: #888; + cursor: pointer; + font-size: 1em; + font-weight: bold; + padding: 0 4px; +} + +.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover { + color: #555; + outline: none; +} + +.select2-container--classic[dir=rtl] .select2-selection--multiple .select2-selection__choice { margin-left: 5px; - margin-right: auto; } + margin-right: auto; +} -.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove { - margin-left: 2px; - margin-right: auto; } +.select2-container--classic[dir=rtl] .select2-selection--multiple .select2-selection__choice__display { + padding-left: 5px; + padding-right: 2px; +} + +.select2-container--classic[dir=rtl] .select2-selection--multiple .select2-selection__choice__remove { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} .select2-container--classic.select2-container--open .select2-selection--multiple { - border: 1px solid #5897fb; } + border: 1px solid #5897fb; +} .select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple { border-top: none; border-top-left-radius: 0; - border-top-right-radius: 0; } + border-top-right-radius: 0; +} .select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple { border-bottom: none; border-bottom-left-radius: 0; - border-bottom-right-radius: 0; } + border-bottom-right-radius: 0; +} .select2-container--classic .select2-search--dropdown .select2-search__field { border: 1px solid #aaa; - outline: 0; } + outline: 0; +} .select2-container--classic .select2-search--inline .select2-search__field { outline: 0; - box-shadow: none; } + box-shadow: none; +} .select2-container--classic .select2-dropdown { background-color: white; - border: 1px solid transparent; } + border: 1px solid transparent; +} .select2-container--classic .select2-dropdown--above { - border-bottom: none; } + border-bottom: none; +} .select2-container--classic .select2-dropdown--below { - border-top: none; } + border-top: none; +} .select2-container--classic .select2-results > .select2-results__options { max-height: 200px; - overflow-y: auto; } + overflow-y: auto; +} -.select2-container--classic .select2-results__option[role=group] { - padding: 0; } +.select2-container--classic .select2-results__option--group { + padding: 0; +} -.select2-container--classic .select2-results__option[aria-disabled=true] { - color: grey; } +.select2-container--classic .select2-results__option--disabled { + color: grey; +} -.select2-container--classic .select2-results__option--highlighted[aria-selected] { +.select2-container--classic .select2-results__option--highlighted.select2-results__option--selectable { background-color: #3875d7; - color: white; } + color: white; +} .select2-container--classic .select2-results__group { cursor: default; display: block; - padding: 6px; } + padding: 6px; +} .select2-container--classic.select2-container--open .select2-dropdown { - border-color: #5897fb; } + border-color: #5897fb; +} \ No newline at end of file diff --git a/assets/vendor/select2/select2.js b/assets/vendor/select2/select2.js index fcfb5ab..d331cf6 100644 --- a/assets/vendor/select2/select2.js +++ b/assets/vendor/select2/select2.js @@ -1,5 +1,5 @@ /*! - * Select2 4.0.13 + * Select2 4.1.0 * https://select2.github.io * * Released under the MIT license @@ -499,8 +499,7 @@ S2.define('jquery',[],function () { }); S2.define('select2/utils',[ - 'jquery' -], function ($) { +], function () { var Utils = {}; Utils.Extend = function (ChildClass, SuperClass) { @@ -716,7 +715,6 @@ S2.define('select2/utils',[ // http://codereview.stackexchange.com/q/13338 // and was designed to be used with the Sizzle selector engine. - var $el = $(el); var overflowX = el.style.overflowX; var overflowY = el.style.overflowY; @@ -730,8 +728,10 @@ S2.define('select2/utils',[ return true; } - return ($el.innerHeight() < el.scrollHeight || - $el.innerWidth() < el.scrollWidth); + var computedEl = window.getComputedStyle(el); + + return (parseFloat(computedEl.height) < el.scrollHeight || + parseFloat(computedEl.width) < el.scrollWidth); }; Utils.escapeMarkup = function (markup) { @@ -755,23 +755,6 @@ S2.define('select2/utils',[ }); }; - // Append an array of jQuery nodes to a given element. - Utils.appendMany = function ($element, $nodes) { - // jQuery 1.7.x does not support $.fn.append() with an array - // Fall back to a jQuery object collection using $.fn.add() - if ($.fn.jquery.substr(0, 3) === '1.7') { - var $jqNodes = $(); - - $.map($nodes, function (node) { - $jqNodes = $jqNodes.add(node); - }); - - $nodes = $jqNodes; - } - - $element.append($nodes); - }; - // Cache objects in Utils.__cache instead of $.data (see #4346) Utils.__cache = {}; @@ -779,20 +762,25 @@ S2.define('select2/utils',[ Utils.GetUniqueElementId = function (element) { // Get a unique element Id. If element has no id, // creates a new unique number, stores it in the id - // attribute and returns the new id. - // If an id already exists, it simply returns it. + // attribute and returns the new id with a prefix. + // If an id already exists, it simply returns it with a prefix. var select2Id = element.getAttribute('data-select2-id'); - if (select2Id == null) { - // If element has id, use it. - if (element.id) { - select2Id = element.id; - element.setAttribute('data-select2-id', select2Id); - } else { - element.setAttribute('data-select2-id', ++id); - select2Id = id.toString(); - } + + if (select2Id != null) { + return select2Id; + } + + // If element has id, use it. + if (element.id) { + select2Id = 'select2-data-' + element.id; + } else { + select2Id = 'select2-data-' + (++id).toString() + + '-' + Utils.generateChars(4); } + + element.setAttribute('data-select2-id', select2Id); + return select2Id; }; @@ -814,13 +802,18 @@ S2.define('select2/utils',[ // and for a specified element. var id = Utils.GetUniqueElementId(element); if (name) { + // Convert the attribute name format (e.g. 'foo-bar') to the dataset + // property key format (e.g. 'fooBar') as required by the HTML spec. + var datasetKey = name.replace(/-([a-z])/g, function (_, letter) { + return letter.toUpperCase(); + }); if (Utils.__cache[id]) { if (Utils.__cache[id][name] != null) { return Utils.__cache[id][name]; } - return $(element).data(name); // Fallback to HTML5 data attribs. + return element.dataset[datasetKey]; // Fallback to HTML5 data attribs. } - return $(element).data(name); // Fallback to HTML5 data attribs. + return element.dataset[datasetKey]; // Fallback to HTML5 data attribs. } else { return Utils.__cache[id]; } @@ -836,6 +829,28 @@ S2.define('select2/utils',[ element.removeAttribute('data-select2-id'); }; + Utils.copyNonInternalCssClasses = function (dest, src) { + var classes; + + var destinationClasses = dest.getAttribute('class').trim().split(/\s+/); + + destinationClasses = destinationClasses.filter(function (clazz) { + // Save all Select2 classes + return clazz.indexOf('select2-') === 0; + }); + + var sourceClasses = src.getAttribute('class').trim().split(/\s+/); + + sourceClasses = sourceClasses.filter(function (clazz) { + // Only copy non-Select2 classes + return clazz.indexOf('select2-') !== 0; + }); + + var replacements = destinationClasses.concat(sourceClasses); + + dest.setAttribute('class', replacements.join(' ')); + }; + return Utils; }); @@ -859,7 +874,7 @@ S2.define('select2/results',[ ); if (this.options.get('multiple')) { - $results.attr('aria-multiselectable', 'true'); + $results[0].setAttribute('aria-multiselectable', 'true'); } this.$results = $results; @@ -940,9 +955,9 @@ S2.define('select2/results',[ Results.prototype.highlightFirstItem = function () { var $options = this.$results - .find('.select2-results__option[aria-selected]'); + .find('.select2-results__option--selectable'); - var $selected = $options.filter('[aria-selected=true]'); + var $selected = $options.filter('.select2-results__option--selected'); // Check if there are any selected options if ($selected.length > 0) { @@ -961,12 +976,12 @@ S2.define('select2/results',[ var self = this; this.data.current(function (selected) { - var selectedIds = $.map(selected, function (s) { + var selectedIds = selected.map(function (s) { return s.id.toString(); }); var $options = self.$results - .find('.select2-results__option[aria-selected]'); + .find('.select2-results__option--selectable'); $options.each(function () { var $option = $(this); @@ -977,10 +992,12 @@ S2.define('select2/results',[ var id = '' + item.id; if ((item.element != null && item.element.selected) || - (item.element == null && $.inArray(id, selectedIds) > -1)) { - $option.attr('aria-selected', 'true'); + (item.element == null && selectedIds.indexOf(id) > -1)) { + this.classList.add('select2-results__option--selected'); + $option[0].setAttribute('aria-selected', 'true'); } else { - $option.attr('aria-selected', 'false'); + this.classList.remove('select2-results__option--selected'); + $option[0].setAttribute('aria-selected', 'false'); } }); @@ -1009,11 +1026,11 @@ S2.define('select2/results',[ Results.prototype.option = function (data) { var option = document.createElement('li'); - option.className = 'select2-results__option'; + option.classList.add('select2-results__option'); + option.classList.add('select2-results__option--selectable'); var attrs = { - 'role': 'option', - 'aria-selected': 'false' + 'role': 'option' }; var matches = window.Element.prototype.matches || @@ -1022,12 +1039,14 @@ S2.define('select2/results',[ if ((data.element != null && matches.call(data.element, ':disabled')) || (data.element == null && data.disabled)) { - delete attrs['aria-selected']; attrs['aria-disabled'] = 'true'; + + option.classList.remove('select2-results__option--selectable'); + option.classList.add('select2-results__option--disabled'); } if (data.id == null) { - delete attrs['aria-selected']; + option.classList.remove('select2-results__option--selectable'); } if (data._resultId != null) { @@ -1041,7 +1060,9 @@ S2.define('select2/results',[ if (data.children) { attrs.role = 'group'; attrs['aria-label'] = data.text; - delete attrs['aria-selected']; + + option.classList.remove('select2-results__option--selectable'); + option.classList.add('select2-results__option--group'); } for (var attr in attrs) { @@ -1056,7 +1077,6 @@ S2.define('select2/results',[ var label = document.createElement('strong'); label.className = 'select2-results__group'; - var $label = $(label); this.template(data, label); var $children = []; @@ -1070,7 +1090,8 @@ S2.define('select2/results',[ } var $childrenContainer = $('', { - 'class': 'select2-results__options select2-results__options--nested' + 'class': 'select2-results__options select2-results__options--nested', + 'role': 'none' }); $childrenContainer.append($children); @@ -1091,7 +1112,7 @@ S2.define('select2/results',[ var id = container.id + '-results'; - this.$results.attr('id', id); + this.$results[0].setAttribute('id', id); container.on('results:all', function (params) { self.clear(); @@ -1142,8 +1163,8 @@ S2.define('select2/results',[ container.on('open', function () { // When the dropdown is open, aria-expended="true" - self.$results.attr('aria-expanded', 'true'); - self.$results.attr('aria-hidden', 'false'); + self.$results[0].setAttribute('aria-expanded', 'true'); + self.$results[0].setAttribute('aria-hidden', 'false'); self.setClasses(); self.ensureHighlightVisible(); @@ -1151,9 +1172,9 @@ S2.define('select2/results',[ container.on('close', function () { // When the dropdown is closed, aria-expended="false" - self.$results.attr('aria-expanded', 'false'); - self.$results.attr('aria-hidden', 'true'); - self.$results.removeAttr('aria-activedescendant'); + self.$results[0].setAttribute('aria-expanded', 'false'); + self.$results[0].setAttribute('aria-hidden', 'true'); + self.$results[0].removeAttribute('aria-activedescendant'); }); container.on('results:toggle', function () { @@ -1175,7 +1196,7 @@ S2.define('select2/results',[ var data = Utils.GetData($highlighted[0], 'data'); - if ($highlighted.attr('aria-selected') == 'true') { + if ($highlighted.hasClass('select2-results__option--selected')) { self.trigger('close', {}); } else { self.trigger('select', { @@ -1187,7 +1208,7 @@ S2.define('select2/results',[ container.on('results:previous', function () { var $highlighted = self.getHighlightedResults(); - var $options = self.$results.find('[aria-selected]'); + var $options = self.$results.find('.select2-results__option--selectable'); var currentIndex = $options.index($highlighted); @@ -1222,7 +1243,7 @@ S2.define('select2/results',[ container.on('results:next', function () { var $highlighted = self.getHighlightedResults(); - var $options = self.$results.find('[aria-selected]'); + var $options = self.$results.find('.select2-results__option--selectable'); var currentIndex = $options.index($highlighted); @@ -1250,7 +1271,8 @@ S2.define('select2/results',[ }); container.on('results:focus', function (params) { - params.element.addClass('select2-results__option--highlighted'); + params.element[0].classList.add('select2-results__option--highlighted'); + params.element[0].setAttribute('aria-selected', 'true'); }); container.on('results:message', function (params) { @@ -1282,20 +1304,23 @@ S2.define('select2/results',[ }); } - this.$results.on('mouseup', '.select2-results__option[aria-selected]', + this.$results.on('mouseup', '.select2-results__option--selectable', function (evt) { var $this = $(this); var data = Utils.GetData(this, 'data'); - if ($this.attr('aria-selected') === 'true') { + if ($this.hasClass('select2-results__option--selected')) { if (self.options.get('multiple')) { self.trigger('unselect', { originalEvent: evt, data: data }); } else { - self.trigger('close', {}); + self.trigger('close', { + originalEvent: evt, + data: data + }); } return; @@ -1307,12 +1332,15 @@ S2.define('select2/results',[ }); }); - this.$results.on('mouseenter', '.select2-results__option[aria-selected]', + this.$results.on('mouseenter', '.select2-results__option--selectable', function (evt) { var data = Utils.GetData(this, 'data'); - self.getHighlightedResults() - .removeClass('select2-results__option--highlighted'); + var $highlighted = self.getHighlightedResults(); + $highlighted.removeClass('select2-results__option--highlighted'); + $highlighted.each(function () { + this.setAttribute('aria-selected', 'false'); + }); self.trigger('results:focus', { data: data, @@ -1339,7 +1367,7 @@ S2.define('select2/results',[ return; } - var $options = this.$results.find('[aria-selected]'); + var $options = this.$results.find('.select2-results__option--selectable'); var currentIndex = $options.index($highlighted); @@ -1426,13 +1454,16 @@ S2.define('select2/selection/base',[ if (Utils.GetData(this.$element[0], 'old-tabindex') != null) { this._tabindex = Utils.GetData(this.$element[0], 'old-tabindex'); - } else if (this.$element.attr('tabindex') != null) { - this._tabindex = this.$element.attr('tabindex'); + } else if (this.$element[0].getAttribute('tabindex') != null) { + this._tabindex = this.$element[0].getAttribute('tabindex'); } - $selection.attr('title', this.$element.attr('title')); - $selection.attr('tabindex', this._tabindex); - $selection.attr('aria-disabled', 'false'); + if(this.$element[0].getAttribute('title')) { + $selection[0] + .setAttribute('title', this.$element[0].getAttribute('title')); + } + $selection[0].setAttribute('tabindex', this._tabindex); + $selection[0].setAttribute('aria-disabled', 'false'); this.$selection = $selection; @@ -1463,7 +1494,8 @@ S2.define('select2/selection/base',[ }); container.on('results:focus', function (params) { - self.$selection.attr('aria-activedescendant', params.data._resultId); + self.$selection[0] + .setAttribute('aria-activedescendant', params.data._resultId); }); container.on('selection:update', function (params) { @@ -1472,17 +1504,17 @@ S2.define('select2/selection/base',[ container.on('open', function () { // When the dropdown is open, aria-expanded="true" - self.$selection.attr('aria-expanded', 'true'); - self.$selection.attr('aria-owns', resultsId); + self.$selection[0].setAttribute('aria-expanded', 'true'); + self.$selection[0].setAttribute('aria-owns', resultsId); self._attachCloseHandler(container); }); container.on('close', function () { // When the dropdown is closed, aria-expanded="false" - self.$selection.attr('aria-expanded', 'false'); - self.$selection.removeAttr('aria-activedescendant'); - self.$selection.removeAttr('aria-owns'); + self.$selection[0].setAttribute('aria-expanded', 'false'); + self.$selection[0].removeAttribute('aria-activedescendant'); + self.$selection[0].removeAttribute('aria-owns'); self.$selection.trigger('focus'); @@ -1490,13 +1522,13 @@ S2.define('select2/selection/base',[ }); container.on('enable', function () { - self.$selection.attr('tabindex', self._tabindex); - self.$selection.attr('aria-disabled', 'false'); + self.$selection[0].setAttribute('tabindex', self._tabindex); + self.$selection[0].setAttribute('aria-disabled', 'false'); }); container.on('disable', function () { - self.$selection.attr('tabindex', '-1'); - self.$selection.attr('aria-disabled', 'true'); + self.$selection[0].setAttribute('tabindex', '-1'); + self.$selection[0].setAttribute('aria-disabled', 'true'); }); }; @@ -1595,7 +1627,7 @@ S2.define('select2/selection/single',[ SingleSelection.prototype.render = function () { var $selection = SingleSelection.__super__.render.call(this); - $selection.addClass('select2-selection--single'); + $selection[0].classList.add('select2-selection--single'); $selection.html( '' + @@ -1614,11 +1646,14 @@ S2.define('select2/selection/single',[ var id = container.id + '-container'; - this.$selection.find('.select2-selection__rendered') - .attr('id', id) - .attr('role', 'textbox') - .attr('aria-readonly', 'true'); - this.$selection.attr('aria-labelledby', id); + var rendered = this.$selection.find('.select2-selection__rendered')[0]; + if(rendered != null) { + rendered.setAttribute('id', id); + rendered.setAttribute('role', 'textbox'); + rendered.setAttribute('aria-readonly', 'true'); + } + this.$selection[0].setAttribute('aria-labelledby', id); + this.$selection[0].setAttribute('aria-controls', id); this.$selection.on('mousedown', function (evt) { // Only respond to left clicks @@ -1649,7 +1684,7 @@ S2.define('select2/selection/single',[ SingleSelection.prototype.clear = function () { var $rendered = this.$selection.find('.select2-selection__rendered'); $rendered.empty(); - $rendered.removeAttr('title'); // clear tooltip on empty + $rendered[0].removeAttribute('title'); // clear tooltip on empty }; SingleSelection.prototype.display = function (data, container) { @@ -1679,9 +1714,9 @@ S2.define('select2/selection/single',[ var title = selection.title || selection.text; if (title) { - $rendered.attr('title', title); + $rendered[0].setAttribute('title', title); } else { - $rendered.removeAttr('title'); + $rendered[0].removeAttribute('title'); } }; @@ -1702,7 +1737,7 @@ S2.define('select2/selection/multiple',[ MultipleSelection.prototype.render = function () { var $selection = MultipleSelection.__super__.render.call(this); - $selection.addClass('select2-selection--multiple'); + $selection[0].classList.add('select2-selection--multiple'); $selection.html( '' @@ -1716,6 +1751,13 @@ S2.define('select2/selection/multiple',[ MultipleSelection.__super__.bind.apply(this, arguments); + var id = container.id + '-container'; + + var rendered = this.$selection.find('.select2-selection__rendered')[0]; + if(rendered != null) { + rendered.setAttribute('id', id); + } + this.$selection.on('click', function (evt) { self.trigger('toggle', { originalEvent: evt @@ -1742,12 +1784,25 @@ S2.define('select2/selection/multiple',[ }); } ); + + this.$selection.on( + 'keydown', + '.select2-selection__choice__remove', + function (evt) { + // Ignore the event if it is disabled + if (self.isDisabled()) { + return; + } + + evt.stopPropagation(); + } + ); }; MultipleSelection.prototype.clear = function () { var $rendered = this.$selection.find('.select2-selection__rendered'); $rendered.empty(); - $rendered.removeAttr('title'); + $rendered[0].removeAttribute('title'); }; MultipleSelection.prototype.display = function (data, container) { @@ -1760,9 +1815,11 @@ S2.define('select2/selection/multiple',[ MultipleSelection.prototype.selectionContainer = function () { var $container = $( '
  • ' + - '' + - '×' + - '' + + '' + + '' + '
  • ' ); @@ -1778,20 +1835,42 @@ S2.define('select2/selection/multiple',[ var $selections = []; + var selectionIdPrefix = this.$selection + .find('.select2-selection__rendered')[0] + .getAttribute('id') + '-choice-'; + for (var d = 0; d < data.length; d++) { var selection = data[d]; var $selection = this.selectionContainer(); var formatted = this.display(selection, $selection); - $selection.append(formatted); + var selectionId = selectionIdPrefix + Utils.generateChars(4) + '-'; + + if (selection.id) { + selectionId += selection.id; + } else { + selectionId += Utils.generateChars(4); + } + + $selection.find('.select2-selection__choice__display') + .append(formatted)[0] + .setAttribute('id', selectionId); var title = selection.title || selection.text; if (title) { - $selection.attr('title', title); + $selection[0].setAttribute('title', title); } + var removeItem = this.options.get('translations').get('removeItem'); + + var remove = $selection.find('.select2-selection__choice__remove')[0]; + + remove.setAttribute('title', removeItem()); + remove.setAttribute('aria-label', removeItem()); + remove.setAttribute('aria-describedby', selectionId); + Utils.StoreData($selection[0], 'data', selection); $selections.push($selection); @@ -1799,15 +1878,15 @@ S2.define('select2/selection/multiple',[ var $rendered = this.$selection.find('.select2-selection__rendered'); - Utils.appendMany($rendered, $selections); + $rendered.append($selections); }; return MultipleSelection; }); S2.define('select2/selection/placeholder',[ - '../utils' -], function (Utils) { + +], function () { function Placeholder (decorated, $element, options) { this.placeholder = this.normalizePlaceholder(options.get('placeholder')); @@ -1815,7 +1894,7 @@ S2.define('select2/selection/placeholder',[ } Placeholder.prototype.normalizePlaceholder = function (_, placeholder) { - if (typeof placeholder === 'string') { + if (typeof placeholder !== 'object') { placeholder = { id: '', text: placeholder @@ -1829,8 +1908,17 @@ S2.define('select2/selection/placeholder',[ var $placeholder = this.selectionContainer(); $placeholder.html(this.display(placeholder)); - $placeholder.addClass('select2-selection__placeholder') - .removeClass('select2-selection__choice'); + $placeholder[0].classList.add('select2-selection__placeholder'); + $placeholder[0].classList.remove('select2-selection__choice'); + + var placeholderTitle = placeholder.title || + placeholder.text || + $placeholder.text(); + + this.$selection.find('.select2-selection__rendered')[0].setAttribute( + 'title', + placeholderTitle + ); return $placeholder; }; @@ -1949,21 +2037,34 @@ S2.define('select2/selection/allowClear',[ AllowClear.prototype.update = function (decorated, data) { decorated.call(this, data); + this.$selection.find('.select2-selection__clear').remove(); + this.$selection[0].classList.remove('select2-selection--clearable'); + if (this.$selection.find('.select2-selection__placeholder').length > 0 || data.length === 0) { return; } + var rendered = this.$selection.find('.select2-selection__rendered')[0]; + var selectionId = null; + if(rendered != null) { + selectionId = rendered.getAttribute('id'); + } + var removeAll = this.options.get('translations').get('removeAllItems'); var $remove = $( - '' + - '×' + - '' + '' ); + $remove[0].setAttribute('title', removeAll()); + $remove[0].setAttribute('aria-label', removeAll()); + $remove[0].setAttribute('aria-describedby', selectionId); Utils.StoreData($remove[0], 'data', data); - this.$selection.find('.select2-selection__rendered').prepend($remove); + this.$selection.prepend($remove); + this.$selection[0].classList.add('select2-selection--clearable'); }; return AllowClear; @@ -1979,20 +2080,27 @@ S2.define('select2/selection/search',[ } Search.prototype.render = function (decorated) { + var searchLabel = this.options.get('translations').get('search'); var $search = $( - '' + '' + + '' + + '' ); this.$searchContainer = $search; - this.$search = $search.find('input'); + this.$search = $search.find('textarea'); + + this.$search[0].autocomplete = this.options.get('autocomplete'); + this.$search[0].setAttribute('aria-label', searchLabel()); var $rendered = decorated.call(this); this._transferTabIndex(); + $rendered.append(this.$searchContainer); return $rendered; }; @@ -2001,29 +2109,33 @@ S2.define('select2/selection/search',[ var self = this; var resultsId = container.id + '-results'; + var selectionId = container.id + '-container'; decorated.call(this, container, $container); + self.$search[0].setAttribute('aria-describedby', selectionId); + container.on('open', function () { - self.$search.attr('aria-controls', resultsId); + self.$search[0].setAttribute('aria-controls', resultsId); self.$search.trigger('focus'); }); container.on('close', function () { self.$search.val(''); - self.$search.removeAttr('aria-controls'); - self.$search.removeAttr('aria-activedescendant'); + self.resizeSearch(); + self.$search[0].removeAttribute('aria-controls'); + self.$search[0].removeAttribute('aria-activedescendant'); self.$search.trigger('focus'); }); container.on('enable', function () { - self.$search.prop('disabled', false); + self.$search[0].disabled = false; self._transferTabIndex(); }); container.on('disable', function () { - self.$search.prop('disabled', true); + self.$search[0].disabled = true; }); container.on('focus', function (evt) { @@ -2032,9 +2144,10 @@ S2.define('select2/selection/search',[ container.on('results:focus', function (params) { if (params.data._resultId) { - self.$search.attr('aria-activedescendant', params.data._resultId); + self.$search[0] + .setAttribute('aria-activedescendant', params.data._resultId); } else { - self.$search.removeAttr('aria-activedescendant'); + self.$search[0].removeAttribute('aria-activedescendant'); } }); @@ -2056,8 +2169,8 @@ S2.define('select2/selection/search',[ var key = evt.which; if (key === KEYS.BACKSPACE && self.$search.val() === '') { - var $previousChoice = self.$searchContainer - .prev('.select2-selection__choice'); + var $previousChoice = self.$selection + .find('.select2-selection__choice').last(); if ($previousChoice.length > 0) { var item = Utils.GetData($previousChoice[0], 'data'); @@ -2140,24 +2253,22 @@ S2.define('select2/selection/search',[ * @private */ Search.prototype._transferTabIndex = function (decorated) { - this.$search.attr('tabindex', this.$selection.attr('tabindex')); - this.$selection.attr('tabindex', '-1'); + this.$search[0] + .setAttribute('tabindex', this.$selection[0].getAttribute('tabindex')); + this.$selection[0].setAttribute('tabindex', '-1'); }; Search.prototype.createPlaceholder = function (decorated, placeholder) { - this.$search.attr('placeholder', placeholder.text); + this.$search[0].setAttribute('placeholder', placeholder.text); }; Search.prototype.update = function (decorated, data) { var searchHadFocus = this.$search[0] == document.activeElement; - this.$search.attr('placeholder', ''); + this.$search[0].setAttribute('placeholder', ''); decorated.call(this, data); - this.$selection.find('.select2-selection__rendered') - .append(this.$searchContainer); - this.resizeSearch(); if (searchHadFocus) { this.$search.trigger('focus'); @@ -2190,11 +2301,9 @@ S2.define('select2/selection/search',[ Search.prototype.resizeSearch = function () { this.$search.css('width', '25px'); - var width = ''; + var width = '100%'; - if (this.$search.attr('placeholder') !== '') { - width = this.$selection.find('.select2-selection__rendered').width(); - } else { + if (this.$search[0].getAttribute('placeholder') === '') { var minimumWidth = this.$search.val().length + 1; width = (minimumWidth * 0.75) + 'em'; @@ -2206,6 +2315,34 @@ S2.define('select2/selection/search',[ return Search; }); +S2.define('select2/selection/selectionCss',[ + '../utils' +], function (Utils) { + function SelectionCSS () { } + + SelectionCSS.prototype.render = function (decorated) { + var $selection = decorated.call(this); + + var selectionCssClass = this.options.get('selectionCssClass') || ''; + + if (selectionCssClass.indexOf(':all:') !== -1) { + selectionCssClass = selectionCssClass.replace(':all:', ''); + + Utils.copyNonInternalCssClasses($selection[0], this.$element[0]); + } + + selectionCssClass.trim().split(' ').forEach(function(cssClass) { + if(cssClass.length > 0) { + $selection[0].classList.add(cssClass); + } + }); + + return $selection; + }; + + return SelectionCSS; +}); + S2.define('select2/selection/eventRelay',[ 'jquery' ], function ($) { @@ -2229,7 +2366,7 @@ S2.define('select2/selection/eventRelay',[ container.on('*', function (name, params) { // Ignore events that should not be relayed - if ($.inArray(name, relayEvents) === -1) { + if (relayEvents.indexOf(name) === -1) { return; } @@ -2244,7 +2381,7 @@ S2.define('select2/selection/eventRelay',[ self.$element.trigger(evt); // Only handle preventable events if it was one - if ($.inArray(name, preventableEvents) === -1) { + if (preventableEvents.indexOf(name) === -1) { return; } @@ -2256,9 +2393,8 @@ S2.define('select2/selection/eventRelay',[ }); S2.define('select2/translation',[ - 'jquery', 'require' -], function ($, require) { +], function (require) { function Translation (dict) { this.dict = dict || {}; } @@ -2272,7 +2408,7 @@ S2.define('select2/translation',[ }; Translation.prototype.extend = function (translation) { - this.dict = $.extend({}, translation.all(), this.dict); + this.dict = Object.assign({}, translation.all(), this.dict); }; // Static functions @@ -3199,16 +3335,14 @@ S2.define('select2/data/select',[ Utils.Extend(SelectAdapter, BaseAdapter); SelectAdapter.prototype.current = function (callback) { - var data = []; var self = this; - this.$element.find(':selected').each(function () { - var $option = $(this); - - var option = self.item($option); - - data.push(option); - }); + var data = Array.prototype.map.call( + this.$element[0].querySelectorAll(':checked'), + function (selectedElement) { + return self.item($(selectedElement)); + } + ); callback(data); }; @@ -3219,7 +3353,9 @@ S2.define('select2/data/select',[ data.selected = true; // If data.element is a DOM node, use it instead - if ($(data.element).is('option')) { + if ( + data.element != null && data.element.tagName.toLowerCase() === 'option' + ) { data.element.selected = true; this.$element.trigger('input').trigger('change'); @@ -3227,7 +3363,7 @@ S2.define('select2/data/select',[ return; } - if (this.$element.prop('multiple')) { + if (this.$element[0].multiple) { this.current(function (currentData) { var val = []; @@ -3237,7 +3373,7 @@ S2.define('select2/data/select',[ for (var d = 0; d < data.length; d++) { var id = data[d].id; - if ($.inArray(id, val) === -1) { + if (val.indexOf(id) === -1) { val.push(id); } } @@ -3256,13 +3392,16 @@ S2.define('select2/data/select',[ SelectAdapter.prototype.unselect = function (data) { var self = this; - if (!this.$element.prop('multiple')) { + if (!this.$element[0].multiple) { return; } data.selected = false; - if ($(data.element).is('option')) { + if ( + data.element != null && + data.element.tagName.toLowerCase() === 'option' + ) { data.element.selected = false; this.$element.trigger('input').trigger('change'); @@ -3276,7 +3415,7 @@ S2.define('select2/data/select',[ for (var d = 0; d < currentData.length; d++) { var id = currentData[d].id; - if (id !== data.id && $.inArray(id, val) === -1) { + if (id !== data.id && val.indexOf(id) === -1) { val.push(id); } } @@ -3316,12 +3455,15 @@ S2.define('select2/data/select',[ var $options = this.$element.children(); $options.each(function () { - var $option = $(this); - - if (!$option.is('option') && !$option.is('optgroup')) { + if ( + this.tagName.toLowerCase() !== 'option' && + this.tagName.toLowerCase() !== 'optgroup' + ) { return; } + var $option = $(this); + var option = self.item($option); var matches = self.matches(params, option); @@ -3337,7 +3479,7 @@ S2.define('select2/data/select',[ }; SelectAdapter.prototype.addOptions = function ($options) { - Utils.appendMany(this.$element, $options); + this.$element.append($options); }; SelectAdapter.prototype.option = function (data) { @@ -3372,15 +3514,13 @@ S2.define('select2/data/select',[ option.title = data.title; } - var $option = $(option); - var normalizedData = this._normalizeItem(data); normalizedData.element = option; // Override the option's data with the combined data Utils.StoreData(option, 'data', normalizedData); - return $option; + return $(option); }; SelectAdapter.prototype.item = function ($option) { @@ -3392,19 +3532,21 @@ S2.define('select2/data/select',[ return data; } - if ($option.is('option')) { + var option = $option[0]; + + if (option.tagName.toLowerCase() === 'option') { data = { id: $option.val(), text: $option.text(), - disabled: $option.prop('disabled'), - selected: $option.prop('selected'), - title: $option.prop('title') + disabled: $option[0].disabled, + selected: $option[0].selected, + title: $option[0].title }; - } else if ($option.is('optgroup')) { + } else if (option.tagName.toLowerCase() === 'optgroup') { data = { - text: $option.prop('label'), + text: $option[0].label, children: [], - title: $option.prop('title') + title: $option[0].title }; var $children = $option.children('option'); @@ -3458,6 +3600,12 @@ S2.define('select2/data/select',[ item._resultId = this.generateResultId(this.container, item); } + if (item.children) { + item.children = item.children.map( + SelectAdapter.prototype._normalizeItem + ); + } + return $.extend({}, defaults, item); }; @@ -3524,7 +3672,7 @@ S2.define('select2/data/array',[ var item = this._normalizeItem(data[d]); // Skip items which were pre-loaded, only merge the data - if ($.inArray(item.id, existingIds) >= 0) { + if (existingIds.indexOf(item.id) >= 0) { var $existingOption = $existing.filter(onlyItem(item)); var existingData = this.item($existingOption); @@ -3542,7 +3690,7 @@ S2.define('select2/data/array',[ if (item.children) { var $children = this.convertToOptions(item.children); - Utils.appendMany($option, $children); + $option.append($children); } $options.push($option); @@ -3601,7 +3749,7 @@ S2.define('select2/data/ajax',[ if (this._request != null) { // JSONP requests cannot always be aborted - if ($.isFunction(this._request.abort)) { + if (typeof this._request.abort === 'function') { this._request.abort(); } @@ -3624,9 +3772,13 @@ S2.define('select2/data/ajax',[ var $request = options.transport(options, function (data) { var results = self.processResults(data, params); - if (self.options.get('debug') && window.console && console.error) { - // Check to make sure that the response included a `results` key. - if (!results || !results.results || !$.isArray(results.results)) { + if (results && results.results && Array.isArray(results.results)) { + results.results = results.results.map( + AjaxAdapter.prototype._normalizeItem + ); + } else { + if (self.options.get('debug') && window.console && console.error) { + // Check to make sure that the response included a `results` key. console.error( 'Select2: The AJAX results did not return an array in the ' + '`results` key of the response.' @@ -3638,7 +3790,7 @@ S2.define('select2/data/ajax',[ }, function () { // Attempt to detect if a request was aborted // Only works if the transport exposes a status property - if ('status' in $request && + if ($request && 'status' in $request && ($request.status === 0 || $request.status === '0')) { return; } @@ -3685,7 +3837,7 @@ S2.define('select2/data/tags',[ decorated.call(this, $element, options); - if ($.isArray(tags)) { + if (Array.isArray(tags)) { for (var t = 0; t < tags.length; t++) { var tag = tags[t]; var item = this._normalizeItem(tag); @@ -3745,7 +3897,7 @@ S2.define('select2/data/tags',[ if (tag != null) { var $option = self.option(tag); - $option.attr('data-select2-tag', true); + $option[0].setAttribute('data-select2-tag', 'true'); self.addOptions([$option]); @@ -3761,7 +3913,11 @@ S2.define('select2/data/tags',[ }; Tags.prototype.createTag = function (decorated, params) { - var term = $.trim(params.term); + if (params.term == null) { + return null; + } + + var term = params.term.trim(); if (term === '') { return null; @@ -3828,7 +3984,7 @@ S2.define('select2/data/tokenizer',[ // If an existing option wasn't found for it, create the option if (!$existingOptions.length) { var $option = self.option(item); - $option.attr('data-select2-tag', true); + $option[0].setAttribute('data-select2-tag', true); self._removeOldTags(); self.addOptions([$option]); @@ -3876,7 +4032,7 @@ S2.define('select2/data/tokenizer',[ while (i < term.length) { var termChar = term[i]; - if ($.inArray(termChar, separators) === -1) { + if (separators.indexOf(termChar) === -1) { i++; continue; @@ -4047,7 +4203,7 @@ S2.define('select2/dropdown',[ '' ); - $dropdown.attr('dir', this.options.get('dir')); + $dropdown[0].setAttribute('dir', this.options.get('dir')); this.$dropdown = $dropdown; @@ -4071,18 +4227,18 @@ S2.define('select2/dropdown',[ }); S2.define('select2/dropdown/search',[ - 'jquery', - '../utils' -], function ($, Utils) { + 'jquery' +], function ($) { function Search () { } Search.prototype.render = function (decorated) { var $rendered = decorated.call(this); + var searchLabel = this.options.get('translations').get('search'); var $search = $( '' + '' + '' ); @@ -4090,6 +4246,9 @@ S2.define('select2/dropdown/search',[ this.$searchContainer = $search; this.$search = $search.find('input'); + this.$search[0].autocomplete = this.options.get('autocomplete'); + this.$search[0].setAttribute('aria-label', searchLabel()); + $rendered.prepend($search); return $rendered; @@ -4121,8 +4280,8 @@ S2.define('select2/dropdown/search',[ }); container.on('open', function () { - self.$search.attr('tabindex', 0); - self.$search.attr('aria-controls', resultsId); + self.$search[0].setAttribute('tabindex', 0); + self.$search[0].setAttribute('aria-controls', resultsId); self.$search.trigger('focus'); @@ -4132,9 +4291,9 @@ S2.define('select2/dropdown/search',[ }); container.on('close', function () { - self.$search.attr('tabindex', -1); - self.$search.removeAttr('aria-controls'); - self.$search.removeAttr('aria-activedescendant'); + self.$search[0].setAttribute('tabindex', -1); + self.$search[0].removeAttribute('aria-controls'); + self.$search[0].removeAttribute('aria-activedescendant'); self.$search.val(''); self.$search.trigger('blur'); @@ -4151,18 +4310,19 @@ S2.define('select2/dropdown/search',[ var showSearch = self.showSearch(params); if (showSearch) { - self.$searchContainer.removeClass('select2-search--hide'); + self.$searchContainer[0].classList.remove('select2-search--hide'); } else { - self.$searchContainer.addClass('select2-search--hide'); + self.$searchContainer[0].classList.add('select2-search--hide'); } } }); container.on('results:focus', function (params) { if (params.data._resultId) { - self.$search.attr('aria-activedescendant', params.data._resultId); + self.$search[0] + .setAttribute('aria-activedescendant', params.data._resultId); } else { - self.$search.removeAttr('aria-activedescendant'); + self.$search[0].removeAttribute('aria-activedescendant'); } }); }; @@ -4202,7 +4362,7 @@ S2.define('select2/dropdown/hidePlaceholder',[ }; HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) { - if (typeof placeholder === 'string') { + if (typeof placeholder !== 'object') { placeholder = { id: '', text: placeholder @@ -4363,10 +4523,10 @@ S2.define('select2/dropdown/attachBody',[ AttachBody.prototype.position = function (decorated, $dropdown, $container) { // Clone all of the container classes - $dropdown.attr('class', $container.attr('class')); + $dropdown[0].setAttribute('class', $container[0].getAttribute('class')); - $dropdown.removeClass('select2'); - $dropdown.addClass('select2-container--open'); + $dropdown[0].classList.remove('select2'); + $dropdown[0].classList.add('select2-container--open'); $dropdown.css({ position: 'absolute', @@ -4472,8 +4632,10 @@ S2.define('select2/dropdown/attachBody',[ AttachBody.prototype._positionDropdown = function () { var $window = $(window); - var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above'); - var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below'); + var isCurrentlyAbove = this.$dropdown[0].classList + .contains('select2-dropdown--above'); + var isCurrentlyBelow = this.$dropdown[0].classList + .contains('select2-dropdown--below'); var newDirection = null; @@ -4545,12 +4707,13 @@ S2.define('select2/dropdown/attachBody',[ } if (newDirection != null) { - this.$dropdown - .removeClass('select2-dropdown--below select2-dropdown--above') - .addClass('select2-dropdown--' + newDirection); - this.$container - .removeClass('select2-container--below select2-container--above') - .addClass('select2-container--' + newDirection); + this.$dropdown[0].classList.remove('select2-dropdown--below'); + this.$dropdown[0].classList.remove('select2-dropdown--above'); + this.$dropdown[0].classList.add('select2-dropdown--' + newDirection); + + this.$container[0].classList.remove('select2-container--below'); + this.$container[0].classList.remove('select2-container--above'); + this.$container[0].classList.add('select2-container--' + newDirection); } this.$dropdownContainer.css(css); @@ -4707,6 +4870,66 @@ S2.define('select2/dropdown/closeOnSelect',[ return CloseOnSelect; }); +S2.define('select2/dropdown/dropdownCss',[ + '../utils' +], function (Utils) { + function DropdownCSS () { } + + DropdownCSS.prototype.render = function (decorated) { + var $dropdown = decorated.call(this); + + var dropdownCssClass = this.options.get('dropdownCssClass') || ''; + + if (dropdownCssClass.indexOf(':all:') !== -1) { + dropdownCssClass = dropdownCssClass.replace(':all:', ''); + + Utils.copyNonInternalCssClasses($dropdown[0], this.$element[0]); + } + + dropdownCssClass.trim().split(' ').forEach(function(cssClass) { + if(cssClass.length > 0) { + $dropdown[0].classList.add(cssClass); + } + }); + + return $dropdown; + }; + + return DropdownCSS; +}); + +S2.define('select2/dropdown/tagsSearchHighlight',[ + '../utils' +], function (Utils) { + function TagsSearchHighlight () { } + + TagsSearchHighlight.prototype.highlightFirstItem = function (decorated) { + var $options = this.$results + .find( + '.select2-results__option--selectable' + + ':not(.select2-results__option--selected)' + ); + + if ($options.length > 0) { + var $firstOption = $options.first(); + var data = Utils.GetData($firstOption[0], 'data'); + var firstElement = data.element; + + if (firstElement && firstElement.getAttribute) { + if (firstElement.getAttribute('data-select2-tag') === 'true') { + $firstOption.trigger('mouseenter'); + + return; + } + } + } + + decorated.call(this); + }; + + return TagsSearchHighlight; +}); + S2.define('select2/i18n/en',[],function () { // English return { @@ -4751,13 +4974,18 @@ S2.define('select2/i18n/en',[],function () { }, removeAllItems: function () { return 'Remove all items'; + }, + removeItem: function () { + return 'Remove item'; + }, + search: function() { + return 'Search'; } }; }); S2.define('select2/defaults',[ 'jquery', - 'require', './results', @@ -4766,6 +4994,7 @@ S2.define('select2/defaults',[ './selection/placeholder', './selection/allowClear', './selection/search', + './selection/selectionCss', './selection/eventRelay', './utils', @@ -4789,14 +5018,16 @@ S2.define('select2/defaults',[ './dropdown/minimumResultsForSearch', './dropdown/selectOnClose', './dropdown/closeOnSelect', + './dropdown/dropdownCss', + './dropdown/tagsSearchHighlight', './i18n/en' -], function ($, require, +], function ($, ResultsList, SingleSelection, MultipleSelection, Placeholder, AllowClear, - SelectionSearch, EventRelay, + SelectionSearch, SelectionCSS, EventRelay, Utils, Translation, DIACRITICS, @@ -4805,6 +5036,7 @@ S2.define('select2/defaults',[ Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll, AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect, + DropdownCSS, TagsSearchHighlight, EnglishTranslation) { function Defaults () { @@ -4854,24 +5086,6 @@ S2.define('select2/defaults',[ Tokenizer ); } - - if (options.query != null) { - var Query = require(options.amdBase + 'compat/query'); - - options.dataAdapter = Utils.Decorate( - options.dataAdapter, - Query - ); - } - - if (options.initSelection != null) { - var InitSelection = require(options.amdBase + 'compat/initSelection'); - - options.dataAdapter = Utils.Decorate( - options.dataAdapter, - InitSelection - ); - } } if (options.resultsAdapter == null) { @@ -4897,6 +5111,13 @@ S2.define('select2/defaults',[ SelectOnClose ); } + + if (options.tags) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + TagsSearchHighlight + ); + } } if (options.dropdownAdapter == null) { @@ -4922,13 +5143,7 @@ S2.define('select2/defaults',[ ); } - if ( - options.dropdownCssClass != null || - options.dropdownCss != null || - options.adaptDropdownCssClass != null - ) { - var DropdownCSS = require(options.amdBase + 'compat/dropdownCss'); - + if (options.dropdownCssClass != null) { options.dropdownAdapter = Utils.Decorate( options.dropdownAdapter, DropdownCSS @@ -4970,16 +5185,10 @@ S2.define('select2/defaults',[ ); } - if ( - options.containerCssClass != null || - options.containerCss != null || - options.adaptContainerCssClass != null - ) { - var ContainerCSS = require(options.amdBase + 'compat/containerCss'); - + if (options.selectionCssClass != null) { options.selectionAdapter = Utils.Decorate( options.selectionAdapter, - ContainerCSS + SelectionCSS ); } @@ -5028,7 +5237,7 @@ S2.define('select2/defaults',[ function matcher (params, data) { // Always return the object if there is nothing to compare - if ($.trim(params.term) === '') { + if (params.term == null || params.term.trim() === '') { return data; } @@ -5072,8 +5281,8 @@ S2.define('select2/defaults',[ } this.defaults = { - amdBase: './', amdLanguageBase: './i18n/', + autocomplete: 'off', closeOnSelect: true, debug: false, dropdownAutoWidth: false, @@ -5103,8 +5312,9 @@ S2.define('select2/defaults',[ Defaults.prototype.applyFromElement = function (options, $element) { var optionLanguage = options.language; var defaultLanguage = this.defaults.language; - var elementLanguage = $element.prop('lang'); - var parentLanguage = $element.closest('[lang]').prop('lang'); + var elementLanguage = $element[0].lang; + var elementClosest = $element.closest('[lang]'); + var parentLanguage = elementClosest[0] ? elementClosest[0].lang : null; var languages = Array.prototype.concat.call( this._resolveLanguage(elementLanguage), @@ -5133,7 +5343,7 @@ S2.define('select2/defaults',[ var languages; - if (!$.isArray(language)) { + if (!Array.isArray(language)) { languages = [language]; } else { languages = language; @@ -5198,7 +5408,10 @@ S2.define('select2/defaults',[ }; Defaults.prototype.set = function (key, value) { - var camelKey = $.camelCase(key); + function upperCaseLetter(_, letter) { + return letter.toUpperCase(); + } + var camelKey = key.replace(/-([a-z])/g, upperCaseLetter); var data = {}; data[camelKey] = value; @@ -5214,11 +5427,10 @@ S2.define('select2/defaults',[ }); S2.define('select2/options',[ - 'require', 'jquery', './defaults', './utils' -], function (require, $, Defaults, Utils) { +], function ($, Defaults, Utils) { function Options (options, $element) { this.options = options; @@ -5231,40 +5443,41 @@ S2.define('select2/options',[ } this.options = Defaults.apply(this.options); - - if ($element && $element.is('input')) { - var InputCompat = require(this.get('amdBase') + 'compat/inputData'); - - this.options.dataAdapter = Utils.Decorate( - this.options.dataAdapter, - InputCompat - ); - } } Options.prototype.fromElement = function ($e) { var excludedData = ['select2']; if (this.options.multiple == null) { - this.options.multiple = $e.prop('multiple'); + this.options.multiple = $e[0].multiple; } if (this.options.disabled == null) { - this.options.disabled = $e.prop('disabled'); + this.options.disabled = $e[0].disabled; } - if (this.options.dir == null) { - if ($e.prop('dir')) { - this.options.dir = $e.prop('dir'); - } else if ($e.closest('[dir]').prop('dir')) { - this.options.dir = $e.closest('[dir]').prop('dir'); + if (this.options.autocomplete == null && $e[0].autocomplete) { + this.options.autocomplete = $e[0].autocomplete; + } + + if (this.options.dir === null) { + var dirValue = $e[0].getAttribute('dir'); + + if (dirValue) { + this.options.dir = dirValue; } else { - this.options.dir = 'ltr'; + var closestDirValue = $e.closest('[dir]').getAttribute('dir'); + + if (closestDirValue) { + this.options.dir = closestDirValue; + } else { + this.options.dir = 'ltr'; + } } } - $e.prop('disabled', this.options.disabled); - $e.prop('multiple', this.options.multiple); + $e[0].disabled = this.options.disabled; + $e[0].multiple = this.options.multiple; if (Utils.GetData($e[0], 'select2Tags')) { if (this.options.debug && window.console && console.warn) { @@ -5288,7 +5501,7 @@ S2.define('select2/options',[ ); } - $e.attr('ajax--url', Utils.GetData($e[0], 'ajaxUrl')); + $e[0].setAttribute('ajax--url', Utils.GetData($e[0], 'ajaxUrl')); Utils.StoreData($e[0], 'ajax-Url', Utils.GetData($e[0], 'ajaxUrl')); } @@ -5331,7 +5544,7 @@ S2.define('select2/options',[ data = Utils._convertData(data); for (var key in data) { - if ($.inArray(key, excludedData) > -1) { + if (excludedData.indexOf(key) > -1) { continue; } @@ -5379,9 +5592,9 @@ S2.define('select2/core',[ // Set up the tabindex - var tabindex = $element.attr('tabindex') || 0; + var tabindex = $element[0].getAttribute('tabindex') || 0; Utils.StoreData($element[0], 'old-tabindex', tabindex); - $element.attr('tabindex', '-1'); + $element[0].setAttribute('tabindex', '-1'); // Set up containers and adapters @@ -5435,8 +5648,8 @@ S2.define('select2/core',[ }); // Hide the original select - $element.addClass('select2-hidden-accessible'); - $element.attr('aria-hidden', 'true'); + $element[0].classList.add('select2-hidden-accessible'); + $element[0].setAttribute('aria-hidden', 'true'); // Synchronize any monitored attributes this._syncAttributes(); @@ -5451,11 +5664,12 @@ S2.define('select2/core',[ Select2.prototype._generateId = function ($element) { var id = ''; + var element = $element[0]; - if ($element.attr('id') != null) { - id = $element.attr('id'); - } else if ($element.attr('name') != null) { - id = $element.attr('name') + '-' + Utils.generateChars(2); + if (element != null && element.getAttribute('id') != null) { + id = element.getAttribute('id'); + } else if (element != null && element.getAttribute('name') != null) { + id = element.getAttribute('name') + '-' + Utils.generateChars(2); } else { id = Utils.generateChars(4); } @@ -5500,7 +5714,7 @@ S2.define('select2/core',[ } if (method == 'style') { - var style = $element.attr('style'); + var style = $element[0].getAttribute('style'); if (typeof(style) !== 'string') { return null; @@ -5555,42 +5769,15 @@ S2.define('select2/core',[ this._syncA = Utils.bind(this._syncAttributes, this); this._syncS = Utils.bind(this._syncSubtree, this); - if (this.$element[0].attachEvent) { - this.$element[0].attachEvent('onpropertychange', this._syncA); - } - - var observer = window.MutationObserver || - window.WebKitMutationObserver || - window.MozMutationObserver - ; - - if (observer != null) { - this._observer = new observer(function (mutations) { - self._syncA(); - self._syncS(null, mutations); - }); - this._observer.observe(this.$element[0], { - attributes: true, - childList: true, - subtree: false - }); - } else if (this.$element[0].addEventListener) { - this.$element[0].addEventListener( - 'DOMAttrModified', - self._syncA, - false - ); - this.$element[0].addEventListener( - 'DOMNodeInserted', - self._syncS, - false - ); - this.$element[0].addEventListener( - 'DOMNodeRemoved', - self._syncS, - false - ); - } + this._observer = new window.MutationObserver(function (mutations) { + self._syncA(); + self._syncS(mutations); + }); + this._observer.observe(this.$element[0], { + attributes: true, + childList: true, + subtree: false + }); }; Select2.prototype._registerDataEvents = function () { @@ -5614,7 +5801,7 @@ S2.define('select2/core',[ }); this.selection.on('*', function (name, params) { - if ($.inArray(name, nonRelayEvents) !== -1) { + if (nonRelayEvents.indexOf(name) !== -1) { return; } @@ -5642,23 +5829,23 @@ S2.define('select2/core',[ var self = this; this.on('open', function () { - self.$container.addClass('select2-container--open'); + self.$container[0].classList.add('select2-container--open'); }); this.on('close', function () { - self.$container.removeClass('select2-container--open'); + self.$container[0].classList.remove('select2-container--open'); }); this.on('enable', function () { - self.$container.removeClass('select2-container--disabled'); + self.$container[0].classList.remove('select2-container--disabled'); }); this.on('disable', function () { - self.$container.addClass('select2-container--disabled'); + self.$container[0].classList.add('select2-container--disabled'); }); this.on('blur', function () { - self.$container.removeClass('select2-container--focus'); + self.$container[0].classList.remove('select2-container--focus'); }); this.on('query', function (params) { @@ -5687,12 +5874,11 @@ S2.define('select2/core',[ var key = evt.which; if (self.isOpen()) { - if (key === KEYS.ESC || key === KEYS.TAB || - (key === KEYS.UP && evt.altKey)) { + if (key === KEYS.ESC || (key === KEYS.UP && evt.altKey)) { self.close(evt); evt.preventDefault(); - } else if (key === KEYS.ENTER) { + } else if (key === KEYS.ENTER || key === KEYS.TAB) { self.trigger('results:select', {}); evt.preventDefault(); @@ -5721,7 +5907,7 @@ S2.define('select2/core',[ }; Select2.prototype._syncAttributes = function () { - this.options.set('disabled', this.$element.prop('disabled')); + this.options.set('disabled', this.$element[0].disabled); if (this.isDisabled()) { if (this.isOpen()) { @@ -5734,49 +5920,30 @@ S2.define('select2/core',[ } }; - Select2.prototype._isChangeMutation = function (evt, mutations) { - var changed = false; + Select2.prototype._isChangeMutation = function (mutations) { var self = this; - // Ignore any mutation events raised for elements that aren't options or - // optgroups. This handles the case when the select element is destroyed - if ( - evt && evt.target && ( - evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP' - ) - ) { - return; - } - - if (!mutations) { - // If mutation events aren't supported, then we can only assume that the - // change affected the selections - changed = true; - } else if (mutations.addedNodes && mutations.addedNodes.length > 0) { + if (mutations.addedNodes && mutations.addedNodes.length > 0) { for (var n = 0; n < mutations.addedNodes.length; n++) { var node = mutations.addedNodes[n]; if (node.selected) { - changed = true; + return true; } } } else if (mutations.removedNodes && mutations.removedNodes.length > 0) { - changed = true; - } else if ($.isArray(mutations)) { - $.each(mutations, function(evt, mutation) { - if (self._isChangeMutation(evt, mutation)) { - // We've found a change mutation. - // Let's escape from the loop and continue - changed = true; - return false; - } + return true; + } else if (Array.isArray(mutations)) { + return mutations.some(function (mutation) { + return self._isChangeMutation(mutation); }); } - return changed; + + return false; }; - Select2.prototype._syncSubtree = function (evt, mutations) { - var changed = this._isChangeMutation(evt, mutations); + Select2.prototype._syncSubtree = function (mutations) { + var changed = this._isChangeMutation(mutations); var self = this; // Only re-pull the data if we think there is a change @@ -5881,11 +6048,11 @@ S2.define('select2/core',[ }; Select2.prototype.isOpen = function () { - return this.$container.hasClass('select2-container--open'); + return this.$container[0].classList.contains('select2-container--open'); }; Select2.prototype.hasFocus = function () { - return this.$container.hasClass('select2-container--focus'); + return this.$container[0].classList.contains('select2-container--focus'); }; Select2.prototype.focus = function (data) { @@ -5894,7 +6061,7 @@ S2.define('select2/core',[ return; } - this.$container.addClass('select2-container--focus'); + this.$container[0].classList.add('select2-container--focus'); this.trigger('focus', {}); }; @@ -5902,7 +6069,7 @@ S2.define('select2/core',[ if (this.options.get('debug') && window.console && console.warn) { console.warn( 'Select2: The `select2("enable")` method has been deprecated and will' + - ' be removed in later Select2 versions. Use $element.prop("disabled")' + + ' be removed in later Select2 versions. Use $element[0].disabled' + ' instead.' ); } @@ -5913,7 +6080,7 @@ S2.define('select2/core',[ var disabled = !args[0]; - this.$element.prop('disabled', disabled); + this.$element[0].disabled = disabled; }; Select2.prototype.data = function () { @@ -5948,8 +6115,8 @@ S2.define('select2/core',[ var newVal = args[0]; - if ($.isArray(newVal)) { - newVal = $.map(newVal, function (obj) { + if (Array.isArray(newVal)) { + newVal = newVal.map(function (obj) { return obj.toString(); }); } @@ -5958,33 +6125,21 @@ S2.define('select2/core',[ }; Select2.prototype.destroy = function () { + Utils.RemoveData(this.$container[0]); this.$container.remove(); - if (this.$element[0].detachEvent) { - this.$element[0].detachEvent('onpropertychange', this._syncA); - } - - if (this._observer != null) { - this._observer.disconnect(); - this._observer = null; - } else if (this.$element[0].removeEventListener) { - this.$element[0] - .removeEventListener('DOMAttrModified', this._syncA, false); - this.$element[0] - .removeEventListener('DOMNodeInserted', this._syncS, false); - this.$element[0] - .removeEventListener('DOMNodeRemoved', this._syncS, false); - } + this._observer.disconnect(); + this._observer = null; this._syncA = null; this._syncS = null; this.$element.off('.select2'); - this.$element.attr('tabindex', + this.$element[0].setAttribute('tabindex', Utils.GetData(this.$element[0], 'old-tabindex')); - this.$element.removeClass('select2-hidden-accessible'); - this.$element.attr('aria-hidden', 'false'); + this.$element[0].classList.remove('select2-hidden-accessible'); + this.$element[0].setAttribute('aria-hidden', 'false'); Utils.RemoveData(this.$element[0]); this.$element.removeData('select2'); @@ -6007,11 +6162,12 @@ S2.define('select2/core',[ '' ); - $container.attr('dir', this.options.get('dir')); + $container[0].setAttribute('dir', this.options.get('dir')); this.$container = $container; - this.$container.addClass('select2-container--' + this.options.get('theme')); + this.$container[0].classList + .add('select2-container--' + this.options.get('theme')); Utils.StoreData($container[0], 'element', this.$element); @@ -6069,7 +6225,7 @@ S2.define('jquery.select2',[ }); // Check if we should be returning `this` - if ($.inArray(options, thisMethods) > -1) { + if (thisMethods.indexOf(options) > -1) { return this; } diff --git a/assets/vendor/select2/select2.min.css b/assets/vendor/select2/select2.min.css index 7c18ad5..e079918 100644 --- a/assets/vendor/select2/select2.min.css +++ b/assets/vendor/select2/select2.min.css @@ -1 +1 @@ -.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px;padding:1px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right;margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} +.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{background-color:rgba(0,0,0,0);border:none;font-size:1em}.select2-container[dir=rtl] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline;list-style:none;padding:0}.select2-container .select2-selection--multiple .select2-selection__clear{background-color:rgba(0,0,0,0);border:none;font-size:1em}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;margin-left:5px;padding:0;max-width:100%;resize:none;height:20px;vertical-align:bottom;font-family:sans-serif;overflow:hidden;word-break:keep-all}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-container .select2-search--inline .select2-search__field::placeholder{line-height:normal}.select2-dropdown{background-color:#fff;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option--selectable{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;height:26px;margin-right:20px;padding-right:0px}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0);border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir=rtl] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir=rtl] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:rgba(0,0,0,0) rgba(0,0,0,0) #888 rgba(0,0,0,0);border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:#fff;border:1px solid #aaa;border-radius:4px;cursor:text;padding-bottom:5px;padding-right:5px;position:relative}.select2-container--default .select2-selection--multiple.select2-selection--clearable{padding-right:25px}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;font-weight:bold;height:20px;margin-right:10px;margin-top:5px;position:absolute;right:0;padding:1px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:inline-block;margin-left:5px;margin-top:5px;padding:0;padding-left:20px;position:relative;max-width:100%;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap}.select2-container--default .select2-selection--multiple .select2-selection__choice__display{cursor:default;padding-left:2px;padding-right:5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{background-color:rgba(0,0,0,0);border:none;border-right:1px solid #aaa;border-top-left-radius:4px;border-bottom-left-radius:4px;color:#999;cursor:pointer;font-size:1em;font-weight:bold;padding:0 4px;position:absolute;left:0;top:0}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover,.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:focus{background-color:#f1f1f1;color:#333;outline:none}.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__choice{padding:0 20px 0 0;margin-left:5px;margin-right:auto}.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__choice__display{padding-left:5px;padding-right:2px}.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__choice__remove{left:auto;right:0;border-left:1px solid #aaa;border-right:none;border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:4px;border-bottom-right-radius:4px}.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__clear{float:left;margin-left:10px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid #000 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:rgba(0,0,0,0);border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--group{padding:0}.select2-container--default .select2-results__option--disabled{color:#999}.select2-container--default .select2-results__option--selected{background-color:#ddd}.select2-container--default .select2-results__option--highlighted.select2-results__option--selectable{background-color:#5897fb;color:#fff}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:rgb(246.5,246.5,246.5);border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, white 50%, #eeeeee 100%);background-image:-o-linear-gradient(top, white 50%, #eeeeee 100%);background-image:linear-gradient(to bottom, white 50%, #eeeeee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;height:26px;margin-right:20px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);background-image:-o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);background-image:linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0);border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir=rtl] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir=rtl] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:rgba(0,0,0,0);border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:rgba(0,0,0,0) rgba(0,0,0,0) #888 rgba(0,0,0,0);border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, white 0%, #eeeeee 50%);background-image:-o-linear-gradient(top, white 0%, #eeeeee 50%);background-image:linear-gradient(to bottom, white 0%, #eeeeee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eeeeee 50%, white 100%);background-image:-o-linear-gradient(top, #eeeeee 50%, white 100%);background-image:linear-gradient(to bottom, #eeeeee 50%, white 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:#fff;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0;padding-bottom:5px;padding-right:5px}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;display:inline-block;margin-left:5px;margin-top:5px;padding:0}.select2-container--classic .select2-selection--multiple .select2-selection__choice__display{cursor:default;padding-left:2px;padding-right:5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{background-color:rgba(0,0,0,0);border:none;border-top-left-radius:4px;border-bottom-left-radius:4px;color:#888;cursor:pointer;font-size:1em;font-weight:bold;padding:0 4px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555;outline:none}.select2-container--classic[dir=rtl] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--classic[dir=rtl] .select2-selection--multiple .select2-selection__choice__display{padding-left:5px;padding-right:2px}.select2-container--classic[dir=rtl] .select2-selection--multiple .select2-selection__choice__remove{border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:4px;border-bottom-right-radius:4px}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid rgba(0,0,0,0)}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option--group{padding:0}.select2-container--classic .select2-results__option--disabled{color:gray}.select2-container--classic .select2-results__option--highlighted.select2-results__option--selectable{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} \ No newline at end of file diff --git a/assets/vendor/select2/select2.min.js b/assets/vendor/select2/select2.min.js index 091d4b5..f13b190 100644 --- a/assets/vendor/select2/select2.min.js +++ b/assets/vendor/select2/select2.min.js @@ -1,2 +1,2 @@ -/*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ -!function(n){"function"==typeof define&&define.amd?define(["jquery"],n):"object"==typeof module&&module.exports?module.exports=function(e,t){return void 0===t&&(t="undefined"!=typeof window?require("jquery"):require("jquery")(e)),n(t),t}:n(jQuery)}(function(u){var e=function(){if(u&&u.fn&&u.fn.select2&&u.fn.select2.amd)var e=u.fn.select2.amd;var t,n,r,h,o,s,f,g,m,v,y,_,i,a,b;function w(e,t){return i.call(e,t)}function l(e,t){var n,r,i,o,s,a,l,c,u,d,p,h=t&&t.split("/"),f=y.map,g=f&&f["*"]||{};if(e){for(s=(e=e.split("/")).length-1,y.nodeIdCompat&&b.test(e[s])&&(e[s]=e[s].replace(b,"")),"."===e[0].charAt(0)&&h&&(e=h.slice(0,h.length-1).concat(e)),u=0;u":">",'"':""","'":"'","/":"/"};return"string"!=typeof e?e:String(e).replace(/[&<>"'\/\\]/g,function(e){return t[e]})},i.appendMany=function(e,t){if("1.7"===o.fn.jquery.substr(0,3)){var n=o();o.map(t,function(e){n=n.add(e)}),t=n}e.append(t)},i.__cache={};var n=0;return i.GetUniqueElementId=function(e){var t=e.getAttribute("data-select2-id");return null==t&&(e.id?(t=e.id,e.setAttribute("data-select2-id",t)):(e.setAttribute("data-select2-id",++n),t=n.toString())),t},i.StoreData=function(e,t,n){var r=i.GetUniqueElementId(e);i.__cache[r]||(i.__cache[r]={}),i.__cache[r][t]=n},i.GetData=function(e,t){var n=i.GetUniqueElementId(e);return t?i.__cache[n]&&null!=i.__cache[n][t]?i.__cache[n][t]:o(e).data(t):i.__cache[n]},i.RemoveData=function(e){var t=i.GetUniqueElementId(e);null!=i.__cache[t]&&delete i.__cache[t],e.removeAttribute("data-select2-id")},i}),e.define("select2/results",["jquery","./utils"],function(h,f){function r(e,t,n){this.$element=e,this.data=n,this.options=t,r.__super__.constructor.call(this)}return f.Extend(r,f.Observable),r.prototype.render=function(){var e=h('
      ');return this.options.get("multiple")&&e.attr("aria-multiselectable","true"),this.$results=e},r.prototype.clear=function(){this.$results.empty()},r.prototype.displayMessage=function(e){var t=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var n=h(''),r=this.options.get("translations").get(e.message);n.append(t(r(e.args))),n[0].className+=" select2-results__message",this.$results.append(n)},r.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},r.prototype.append=function(e){this.hideLoading();var t=[];if(null!=e.results&&0!==e.results.length){e.results=this.sort(e.results);for(var n=0;n",{class:"select2-results__options select2-results__options--nested"});p.append(l),s.append(a),s.append(p)}else this.template(e,t);return f.StoreData(t,"data",e),t},r.prototype.bind=function(t,e){var l=this,n=t.id+"-results";this.$results.attr("id",n),t.on("results:all",function(e){l.clear(),l.append(e.data),t.isOpen()&&(l.setClasses(),l.highlightFirstItem())}),t.on("results:append",function(e){l.append(e.data),t.isOpen()&&l.setClasses()}),t.on("query",function(e){l.hideMessages(),l.showLoading(e)}),t.on("select",function(){t.isOpen()&&(l.setClasses(),l.options.get("scrollAfterSelect")&&l.highlightFirstItem())}),t.on("unselect",function(){t.isOpen()&&(l.setClasses(),l.options.get("scrollAfterSelect")&&l.highlightFirstItem())}),t.on("open",function(){l.$results.attr("aria-expanded","true"),l.$results.attr("aria-hidden","false"),l.setClasses(),l.ensureHighlightVisible()}),t.on("close",function(){l.$results.attr("aria-expanded","false"),l.$results.attr("aria-hidden","true"),l.$results.removeAttr("aria-activedescendant")}),t.on("results:toggle",function(){var e=l.getHighlightedResults();0!==e.length&&e.trigger("mouseup")}),t.on("results:select",function(){var e=l.getHighlightedResults();if(0!==e.length){var t=f.GetData(e[0],"data");"true"==e.attr("aria-selected")?l.trigger("close",{}):l.trigger("select",{data:t})}}),t.on("results:previous",function(){var e=l.getHighlightedResults(),t=l.$results.find("[aria-selected]"),n=t.index(e);if(!(n<=0)){var r=n-1;0===e.length&&(r=0);var i=t.eq(r);i.trigger("mouseenter");var o=l.$results.offset().top,s=i.offset().top,a=l.$results.scrollTop()+(s-o);0===r?l.$results.scrollTop(0):s-o<0&&l.$results.scrollTop(a)}}),t.on("results:next",function(){var e=l.getHighlightedResults(),t=l.$results.find("[aria-selected]"),n=t.index(e)+1;if(!(n>=t.length)){var r=t.eq(n);r.trigger("mouseenter");var i=l.$results.offset().top+l.$results.outerHeight(!1),o=r.offset().top+r.outerHeight(!1),s=l.$results.scrollTop()+o-i;0===n?l.$results.scrollTop(0):ithis.$results.outerHeight()||o<0)&&this.$results.scrollTop(i)}},r.prototype.template=function(e,t){var n=this.options.get("templateResult"),r=this.options.get("escapeMarkup"),i=n(e,t);null==i?t.style.display="none":"string"==typeof i?t.innerHTML=r(i):h(t).append(i)},r}),e.define("select2/keys",[],function(){return{BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46}}),e.define("select2/selection/base",["jquery","../utils","../keys"],function(n,r,i){function o(e,t){this.$element=e,this.options=t,o.__super__.constructor.call(this)}return r.Extend(o,r.Observable),o.prototype.render=function(){var e=n('');return this._tabindex=0,null!=r.GetData(this.$element[0],"old-tabindex")?this._tabindex=r.GetData(this.$element[0],"old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),e.attr("title",this.$element.attr("title")),e.attr("tabindex",this._tabindex),e.attr("aria-disabled","false"),this.$selection=e},o.prototype.bind=function(e,t){var n=this,r=e.id+"-results";this.container=e,this.$selection.on("focus",function(e){n.trigger("focus",e)}),this.$selection.on("blur",function(e){n._handleBlur(e)}),this.$selection.on("keydown",function(e){n.trigger("keypress",e),e.which===i.SPACE&&e.preventDefault()}),e.on("results:focus",function(e){n.$selection.attr("aria-activedescendant",e.data._resultId)}),e.on("selection:update",function(e){n.update(e.data)}),e.on("open",function(){n.$selection.attr("aria-expanded","true"),n.$selection.attr("aria-owns",r),n._attachCloseHandler(e)}),e.on("close",function(){n.$selection.attr("aria-expanded","false"),n.$selection.removeAttr("aria-activedescendant"),n.$selection.removeAttr("aria-owns"),n.$selection.trigger("focus"),n._detachCloseHandler(e)}),e.on("enable",function(){n.$selection.attr("tabindex",n._tabindex),n.$selection.attr("aria-disabled","false")}),e.on("disable",function(){n.$selection.attr("tabindex","-1"),n.$selection.attr("aria-disabled","true")})},o.prototype._handleBlur=function(e){var t=this;window.setTimeout(function(){document.activeElement==t.$selection[0]||n.contains(t.$selection[0],document.activeElement)||t.trigger("blur",e)},1)},o.prototype._attachCloseHandler=function(e){n(document.body).on("mousedown.select2."+e.id,function(e){var t=n(e.target).closest(".select2");n(".select2.select2-container--open").each(function(){this!=t[0]&&r.GetData(this,"element").select2("close")})})},o.prototype._detachCloseHandler=function(e){n(document.body).off("mousedown.select2."+e.id)},o.prototype.position=function(e,t){t.find(".selection").append(e)},o.prototype.destroy=function(){this._detachCloseHandler(this.container)},o.prototype.update=function(e){throw new Error("The `update` method must be defined in child classes.")},o.prototype.isEnabled=function(){return!this.isDisabled()},o.prototype.isDisabled=function(){return this.options.get("disabled")},o}),e.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(e,t,n,r){function i(){i.__super__.constructor.apply(this,arguments)}return n.Extend(i,t),i.prototype.render=function(){var e=i.__super__.render.call(this);return e.addClass("select2-selection--single"),e.html(''),e},i.prototype.bind=function(t,e){var n=this;i.__super__.bind.apply(this,arguments);var r=t.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",r).attr("role","textbox").attr("aria-readonly","true"),this.$selection.attr("aria-labelledby",r),this.$selection.on("mousedown",function(e){1===e.which&&n.trigger("toggle",{originalEvent:e})}),this.$selection.on("focus",function(e){}),this.$selection.on("blur",function(e){}),t.on("focus",function(e){t.isOpen()||n.$selection.trigger("focus")})},i.prototype.clear=function(){var e=this.$selection.find(".select2-selection__rendered");e.empty(),e.removeAttr("title")},i.prototype.display=function(e,t){var n=this.options.get("templateSelection");return this.options.get("escapeMarkup")(n(e,t))},i.prototype.selectionContainer=function(){return e("")},i.prototype.update=function(e){if(0!==e.length){var t=e[0],n=this.$selection.find(".select2-selection__rendered"),r=this.display(t,n);n.empty().append(r);var i=t.title||t.text;i?n.attr("title",i):n.removeAttr("title")}else this.clear()},i}),e.define("select2/selection/multiple",["jquery","./base","../utils"],function(i,e,l){function n(e,t){n.__super__.constructor.apply(this,arguments)}return l.Extend(n,e),n.prototype.render=function(){var e=n.__super__.render.call(this);return e.addClass("select2-selection--multiple"),e.html('
        '),e},n.prototype.bind=function(e,t){var r=this;n.__super__.bind.apply(this,arguments),this.$selection.on("click",function(e){r.trigger("toggle",{originalEvent:e})}),this.$selection.on("click",".select2-selection__choice__remove",function(e){if(!r.isDisabled()){var t=i(this).parent(),n=l.GetData(t[0],"data");r.trigger("unselect",{originalEvent:e,data:n})}})},n.prototype.clear=function(){var e=this.$selection.find(".select2-selection__rendered");e.empty(),e.removeAttr("title")},n.prototype.display=function(e,t){var n=this.options.get("templateSelection");return this.options.get("escapeMarkup")(n(e,t))},n.prototype.selectionContainer=function(){return i('
      • ×
      • ')},n.prototype.update=function(e){if(this.clear(),0!==e.length){for(var t=[],n=0;n×');a.StoreData(r[0],"data",t),this.$selection.find(".select2-selection__rendered").prepend(r)}},e}),e.define("select2/selection/search",["jquery","../utils","../keys"],function(r,a,l){function e(e,t,n){e.call(this,t,n)}return e.prototype.render=function(e){var t=r('');this.$searchContainer=t,this.$search=t.find("input");var n=e.call(this);return this._transferTabIndex(),n},e.prototype.bind=function(e,t,n){var r=this,i=t.id+"-results";e.call(this,t,n),t.on("open",function(){r.$search.attr("aria-controls",i),r.$search.trigger("focus")}),t.on("close",function(){r.$search.val(""),r.$search.removeAttr("aria-controls"),r.$search.removeAttr("aria-activedescendant"),r.$search.trigger("focus")}),t.on("enable",function(){r.$search.prop("disabled",!1),r._transferTabIndex()}),t.on("disable",function(){r.$search.prop("disabled",!0)}),t.on("focus",function(e){r.$search.trigger("focus")}),t.on("results:focus",function(e){e.data._resultId?r.$search.attr("aria-activedescendant",e.data._resultId):r.$search.removeAttr("aria-activedescendant")}),this.$selection.on("focusin",".select2-search--inline",function(e){r.trigger("focus",e)}),this.$selection.on("focusout",".select2-search--inline",function(e){r._handleBlur(e)}),this.$selection.on("keydown",".select2-search--inline",function(e){if(e.stopPropagation(),r.trigger("keypress",e),r._keyUpPrevented=e.isDefaultPrevented(),e.which===l.BACKSPACE&&""===r.$search.val()){var t=r.$searchContainer.prev(".select2-selection__choice");if(0this.maximumInputLength?this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:t.term,params:t}}):e.call(this,t,n)},e}),e.define("select2/data/maximumSelectionLength",[],function(){function e(e,t,n){this.maximumSelectionLength=n.get("maximumSelectionLength"),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("select",function(){r._checkIfMaximumSelected()})},e.prototype.query=function(e,t,n){var r=this;this._checkIfMaximumSelected(function(){e.call(r,t,n)})},e.prototype._checkIfMaximumSelected=function(e,n){var r=this;this.current(function(e){var t=null!=e?e.length:0;0=r.maximumSelectionLength?r.trigger("results:message",{message:"maximumSelected",args:{maximum:r.maximumSelectionLength}}):n&&n()})},e}),e.define("select2/dropdown",["jquery","./utils"],function(t,e){function n(e,t){this.$element=e,this.options=t,n.__super__.constructor.call(this)}return e.Extend(n,e.Observable),n.prototype.render=function(){var e=t('');return e.attr("dir",this.options.get("dir")),this.$dropdown=e},n.prototype.bind=function(){},n.prototype.position=function(e,t){},n.prototype.destroy=function(){this.$dropdown.remove()},n}),e.define("select2/dropdown/search",["jquery","../utils"],function(o,e){function t(){}return t.prototype.render=function(e){var t=e.call(this),n=o('');return this.$searchContainer=n,this.$search=n.find("input"),t.prepend(n),t},t.prototype.bind=function(e,t,n){var r=this,i=t.id+"-results";e.call(this,t,n),this.$search.on("keydown",function(e){r.trigger("keypress",e),r._keyUpPrevented=e.isDefaultPrevented()}),this.$search.on("input",function(e){o(this).off("keyup")}),this.$search.on("keyup input",function(e){r.handleSearch(e)}),t.on("open",function(){r.$search.attr("tabindex",0),r.$search.attr("aria-controls",i),r.$search.trigger("focus"),window.setTimeout(function(){r.$search.trigger("focus")},0)}),t.on("close",function(){r.$search.attr("tabindex",-1),r.$search.removeAttr("aria-controls"),r.$search.removeAttr("aria-activedescendant"),r.$search.val(""),r.$search.trigger("blur")}),t.on("focus",function(){t.isOpen()||r.$search.trigger("focus")}),t.on("results:all",function(e){null!=e.query.term&&""!==e.query.term||(r.showSearch(e)?r.$searchContainer.removeClass("select2-search--hide"):r.$searchContainer.addClass("select2-search--hide"))}),t.on("results:focus",function(e){e.data._resultId?r.$search.attr("aria-activedescendant",e.data._resultId):r.$search.removeAttr("aria-activedescendant")})},t.prototype.handleSearch=function(e){if(!this._keyUpPrevented){var t=this.$search.val();this.trigger("query",{term:t})}this._keyUpPrevented=!1},t.prototype.showSearch=function(e,t){return!0},t}),e.define("select2/dropdown/hidePlaceholder",[],function(){function e(e,t,n,r){this.placeholder=this.normalizePlaceholder(n.get("placeholder")),e.call(this,t,n,r)}return e.prototype.append=function(e,t){t.results=this.removePlaceholder(t.results),e.call(this,t)},e.prototype.normalizePlaceholder=function(e,t){return"string"==typeof t&&(t={id:"",text:t}),t},e.prototype.removePlaceholder=function(e,t){for(var n=t.slice(0),r=t.length-1;0<=r;r--){var i=t[r];this.placeholder.id===i.id&&n.splice(r,1)}return n},e}),e.define("select2/dropdown/infiniteScroll",["jquery"],function(n){function e(e,t,n,r){this.lastParams={},e.call(this,t,n,r),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return e.prototype.append=function(e,t){this.$loadingMore.remove(),this.loading=!1,e.call(this,t),this.showLoadingMore(t)&&(this.$results.append(this.$loadingMore),this.loadMoreIfNeeded())},e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("query",function(e){r.lastParams=e,r.loading=!0}),t.on("query:append",function(e){r.lastParams=e,r.loading=!0}),this.$results.on("scroll",this.loadMoreIfNeeded.bind(this))},e.prototype.loadMoreIfNeeded=function(){var e=n.contains(document.documentElement,this.$loadingMore[0]);if(!this.loading&&e){var t=this.$results.offset().top+this.$results.outerHeight(!1);this.$loadingMore.offset().top+this.$loadingMore.outerHeight(!1)<=t+50&&this.loadMore()}},e.prototype.loadMore=function(){this.loading=!0;var e=n.extend({},{page:1},this.lastParams);e.page++,this.trigger("query:append",e)},e.prototype.showLoadingMore=function(e,t){return t.pagination&&t.pagination.more},e.prototype.createLoadingMore=function(){var e=n('
      • '),t=this.options.get("translations").get("loadingMore");return e.html(t(this.lastParams)),e},e}),e.define("select2/dropdown/attachBody",["jquery","../utils"],function(f,a){function e(e,t,n){this.$dropdownParent=f(n.get("dropdownParent")||document.body),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("open",function(){r._showDropdown(),r._attachPositioningHandler(t),r._bindContainerResultHandlers(t)}),t.on("close",function(){r._hideDropdown(),r._detachPositioningHandler(t)}),this.$dropdownContainer.on("mousedown",function(e){e.stopPropagation()})},e.prototype.destroy=function(e){e.call(this),this.$dropdownContainer.remove()},e.prototype.position=function(e,t,n){t.attr("class",n.attr("class")),t.removeClass("select2"),t.addClass("select2-container--open"),t.css({position:"absolute",top:-999999}),this.$container=n},e.prototype.render=function(e){var t=f(""),n=e.call(this);return t.append(n),this.$dropdownContainer=t},e.prototype._hideDropdown=function(e){this.$dropdownContainer.detach()},e.prototype._bindContainerResultHandlers=function(e,t){if(!this._containerResultsHandlersBound){var n=this;t.on("results:all",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:append",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:message",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("select",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("unselect",function(){n._positionDropdown(),n._resizeDropdown()}),this._containerResultsHandlersBound=!0}},e.prototype._attachPositioningHandler=function(e,t){var n=this,r="scroll.select2."+t.id,i="resize.select2."+t.id,o="orientationchange.select2."+t.id,s=this.$container.parents().filter(a.hasScroll);s.each(function(){a.StoreData(this,"select2-scroll-position",{x:f(this).scrollLeft(),y:f(this).scrollTop()})}),s.on(r,function(e){var t=a.GetData(this,"select2-scroll-position");f(this).scrollTop(t.y)}),f(window).on(r+" "+i+" "+o,function(e){n._positionDropdown(),n._resizeDropdown()})},e.prototype._detachPositioningHandler=function(e,t){var n="scroll.select2."+t.id,r="resize.select2."+t.id,i="orientationchange.select2."+t.id;this.$container.parents().filter(a.hasScroll).off(n),f(window).off(n+" "+r+" "+i)},e.prototype._positionDropdown=function(){var e=f(window),t=this.$dropdown.hasClass("select2-dropdown--above"),n=this.$dropdown.hasClass("select2-dropdown--below"),r=null,i=this.$container.offset();i.bottom=i.top+this.$container.outerHeight(!1);var o={height:this.$container.outerHeight(!1)};o.top=i.top,o.bottom=i.top+o.height;var s=this.$dropdown.outerHeight(!1),a=e.scrollTop(),l=e.scrollTop()+e.height(),c=ai.bottom+s,d={left:i.left,top:o.bottom},p=this.$dropdownParent;"static"===p.css("position")&&(p=p.offsetParent());var h={top:0,left:0};(f.contains(document.body,p[0])||p[0].isConnected)&&(h=p.offset()),d.top-=h.top,d.left-=h.left,t||n||(r="below"),u||!c||t?!c&&u&&t&&(r="below"):r="above",("above"==r||t&&"below"!==r)&&(d.top=o.top-h.top-s),null!=r&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+r),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+r)),this.$dropdownContainer.css(d)},e.prototype._resizeDropdown=function(){var e={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(e.minWidth=e.width,e.position="relative",e.width="auto"),this.$dropdown.css(e)},e.prototype._showDropdown=function(e){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},e}),e.define("select2/dropdown/minimumResultsForSearch",[],function(){function e(e,t,n,r){this.minimumResultsForSearch=n.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),e.call(this,t,n,r)}return e.prototype.showSearch=function(e,t){return!(function e(t){for(var n=0,r=0;r');return e.attr("dir",this.options.get("dir")),this.$container=e,this.$container.addClass("select2-container--"+this.options.get("theme")),u.StoreData(e[0],"element",this.$element),e},d}),e.define("jquery-mousewheel",["jquery"],function(e){return e}),e.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults","./select2/utils"],function(i,e,o,t,s){if(null==i.fn.select2){var a=["open","close","destroy"];i.fn.select2=function(t){if("object"==typeof(t=t||{}))return this.each(function(){var e=i.extend(!0,{},t);new o(i(this),e)}),this;if("string"!=typeof t)throw new Error("Invalid arguments for Select2: "+t);var n,r=Array.prototype.slice.call(arguments,1);return this.each(function(){var e=s.GetData(this,"select2");null==e&&window.console&&console.error&&console.error("The select2('"+t+"') method was called on an element that is not using Select2."),n=e[t].apply(e,r)}),-1{"function"==typeof define&&define.amd?define(["jquery"],n):"object"==typeof module&&module.exports?module.exports=function(e,t){return void 0===t&&(t="undefined"!=typeof window?require("jquery"):require("jquery")(e)),n(t),t}:n(jQuery)})(function(t){(e=t&&t.fn&&t.fn.select2&&t.fn.select2.amd?t.fn.select2.amd:e)&&e.requirejs||(e?n=e:e={},g={},m={},y={},v={},i=Object.prototype.hasOwnProperty,s=[].slice,b=/\.js$/,h=function(e,t){var n,i,s=c(e),o=s[0],t=t[1];return e=s[1],o&&(n=A(o=a(o,t))),o?e=n&&n.normalize?n.normalize(e,(i=t,function(e){return a(e,i)})):a(e,t):(o=(s=c(e=a(e,t)))[0],e=s[1],o&&(n=A(o))),{f:o?o+"!"+e:e,n:e,pr:o,p:n}},f={require:function(e){return w(e)},exports:function(e){var t=g[e];return void 0!==t?t:g[e]={}},module:function(e){return{id:e,uri:"",exports:g[e],config:(t=e,function(){return y&&y.config&&y.config[t]||{}})};var t}},o=function(e,t,n,i){var s,o,r,l,a,c=[],u=typeof n,d=x(i=i||e);if("undefined"==u||"function"==u){for(t=!t.length&&n.length?["require","exports","module"]:t,l=0;lfunction(e){g[t]=e})(o),{}),c[l]=g[o]}u=n?n.apply(g[e],c):void 0,e&&(s&&s.exports!==p&&s.exports!==g[e]?g[e]=s.exports:u===p&&a||(g[e]=u))}else e&&(g[e]=n)},l=n=r=function(e,t,n,i,s){if("string"==typeof e)return f[e]?f[e](t):A(h(e,x(t)).f);if(!e.splice){if((y=e).deps&&r(y.deps,y.callback),!t)return;t.splice?(e=t,t=n,n=null):e=p}return t=t||function(){},"function"==typeof n&&(n=i,i=s),i?o(p,e,t,n):setTimeout(function(){o(p,e,t,n)},4),r},r.config=function(e){return r(e)},l._defined=g,(u=function(e,t,n){if("string"!=typeof e)throw new Error("See almond README: incorrect module build, no module name");t.splice||(n=t,t=[]),_(g,e)||_(m,e)||(m[e]=[e,t,n])}).amd={jQuery:!0},e.requirejs=l,e.require=n,e.define=u),e.define("almond",function(){}),e.define("jquery",[],function(){var e=t||$;return null==e&&console&&console.error&&console.error("Select2: An instance of jQuery or a jQuery-compatible library was not found. Make sure that you are including jQuery before Select2 on your web page."),e}),e.define("select2/utils",[],function(){var s={};function c(e){var t,n=e.prototype,i=[];for(t in n)"function"==typeof n[t]&&"constructor"!==t&&i.push(t);return i}s.Extend=function(e,t){var n,i={}.hasOwnProperty;function s(){this.constructor=e}for(n in t)i.call(t,n)&&(e[n]=t[n]);return s.prototype=t.prototype,e.prototype=new s,e.__super__=t.prototype,e},s.Decorate=function(i,s){var e=c(s),t=c(i);function o(){var e=Array.prototype.unshift,t=s.prototype.constructor.length,n=i.prototype.constructor;0{var t=function(){},n=(e in o.prototype&&(t=o.prototype[e]),s.prototype[e]);return function(){return Array.prototype.unshift.call(arguments,t),n.apply(this,arguments)}})(a)}return o};function e(){this.listeners={}}e.prototype.on=function(e,t){this.listeners=this.listeners||{},e in this.listeners?this.listeners[e].push(t):this.listeners[e]=[t]},e.prototype.trigger=function(e){var t=Array.prototype.slice,n=t.call(arguments,1);this.listeners=this.listeners||{},0===(n=null==n?[]:n).length&&n.push({}),(n[0]._type=e)in this.listeners&&this.invoke(this.listeners[e],t.call(arguments,1)),"*"in this.listeners&&this.invoke(this.listeners["*"],arguments)},e.prototype.invoke=function(e,t){for(var n=0,i=e.length;n":">",'"':""","'":"'","/":"/"};return"string"!=typeof e?e:String(e).replace(/[&<>"'\/\\]/g,function(e){return t[e]})},s.__cache={};var n=0;return s.GetUniqueElementId=function(e){var t=e.getAttribute("data-select2-id");return null==t&&(t=e.id?"select2-data-"+e.id:"select2-data-"+(++n).toString()+"-"+s.generateChars(4),e.setAttribute("data-select2-id",t)),t},s.StoreData=function(e,t,n){e=s.GetUniqueElementId(e);s.__cache[e]||(s.__cache[e]={}),s.__cache[e][t]=n},s.GetData=function(e,t){var n,i=s.GetUniqueElementId(e);return t?(n=t.replace(/-([a-z])/g,function(e,t){return t.toUpperCase()}),s.__cache[i]&&null!=s.__cache[i][t]?s.__cache[i][t]:e.dataset[n]):s.__cache[i]},s.RemoveData=function(e){var t=s.GetUniqueElementId(e);null!=s.__cache[t]&&delete s.__cache[t],e.removeAttribute("data-select2-id")},s.copyNonInternalCssClasses=function(e,t){var n=(n=e.getAttribute("class").trim().split(/\s+/)).filter(function(e){return 0===e.indexOf("select2-")}),t=(t=t.getAttribute("class").trim().split(/\s+/)).filter(function(e){return 0!==e.indexOf("select2-")}),n=n.concat(t);e.setAttribute("class",n.join(" "))},s}),e.define("select2/results",["jquery","./utils"],function(u,d){function i(e,t,n){this.$element=e,this.data=n,this.options=t,i.__super__.constructor.call(this)}return d.Extend(i,d.Observable),i.prototype.render=function(){var e=u('
          ');return this.options.get("multiple")&&e[0].setAttribute("aria-multiselectable","true"),this.$results=e},i.prototype.clear=function(){this.$results.empty()},i.prototype.displayMessage=function(e){var t=this.options.get("escapeMarkup"),n=(this.clear(),this.hideLoading(),u('')),i=this.options.get("translations").get(e.message);n.append(t(i(e.args))),n[0].className+=" select2-results__message",this.$results.append(n)},i.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},i.prototype.append=function(e){this.hideLoading();var t=[];if(null==e.results||0===e.results.length)0===this.$results.children().length&&this.trigger("results:message",{message:"noResults"});else{e.results=this.sort(e.results);for(var n=0;n",{class:"select2-results__options select2-results__options--nested",role:"none"});c.append(r),s.append(o),s.append(c)}else this.template(e,n);return d.StoreData(n,"data",e),n},i.prototype.bind=function(t,e){var s=this,n=t.id+"-results";this.$results[0].setAttribute("id",n),t.on("results:all",function(e){s.clear(),s.append(e.data),t.isOpen()&&(s.setClasses(),s.highlightFirstItem())}),t.on("results:append",function(e){s.append(e.data),t.isOpen()&&s.setClasses()}),t.on("query",function(e){s.hideMessages(),s.showLoading(e)}),t.on("select",function(){t.isOpen()&&(s.setClasses(),s.options.get("scrollAfterSelect"))&&s.highlightFirstItem()}),t.on("unselect",function(){t.isOpen()&&(s.setClasses(),s.options.get("scrollAfterSelect"))&&s.highlightFirstItem()}),t.on("open",function(){s.$results[0].setAttribute("aria-expanded","true"),s.$results[0].setAttribute("aria-hidden","false"),s.setClasses(),s.ensureHighlightVisible()}),t.on("close",function(){s.$results[0].setAttribute("aria-expanded","false"),s.$results[0].setAttribute("aria-hidden","true"),s.$results[0].removeAttribute("aria-activedescendant")}),t.on("results:toggle",function(){var e=s.getHighlightedResults();0!==e.length&&e.trigger("mouseup")}),t.on("results:select",function(){var e,t=s.getHighlightedResults();0!==t.length&&(e=d.GetData(t[0],"data"),t.hasClass("select2-results__option--selected")?s.trigger("close",{}):s.trigger("select",{data:e}))}),t.on("results:previous",function(){var e,t=s.getHighlightedResults(),n=s.$results.find(".select2-results__option--selectable"),i=n.index(t);i<=0||(i=i-1,0===t.length&&(i=0),(t=n.eq(i)).trigger("mouseenter"),n=s.$results.offset().top,t=t.offset().top,e=s.$results.scrollTop()+(t-n),0===i?s.$results.scrollTop(0):t-n<0&&s.$results.scrollTop(e))}),t.on("results:next",function(){var e,t,n=s.getHighlightedResults(),i=s.$results.find(".select2-results__option--selectable"),n=i.index(n)+1;n>=i.length||((i=i.eq(n)).trigger("mouseenter"),e=s.$results.offset().top+s.$results.outerHeight(!1),i=i.offset().top+i.outerHeight(!1),t=s.$results.scrollTop()+i-e,0===n?s.$results.scrollTop(0):ethis.$results.outerHeight()||i<0)&&this.$results.scrollTop(n))},i.prototype.template=function(e,t){var n=this.options.get("templateResult"),i=this.options.get("escapeMarkup"),n=n(e,t);null==n?t.style.display="none":"string"==typeof n?t.innerHTML=i(n):u(t).append(n)},i}),e.define("select2/keys",[],function(){return{BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46}}),e.define("select2/selection/base",["jquery","../utils","../keys"],function(n,i,s){function o(e,t){this.$element=e,this.options=t,o.__super__.constructor.call(this)}return i.Extend(o,i.Observable),o.prototype.render=function(){var e=n('');return this._tabindex=0,null!=i.GetData(this.$element[0],"old-tabindex")?this._tabindex=i.GetData(this.$element[0],"old-tabindex"):null!=this.$element[0].getAttribute("tabindex")&&(this._tabindex=this.$element[0].getAttribute("tabindex")),this.$element[0].getAttribute("title")&&e[0].setAttribute("title",this.$element[0].getAttribute("title")),e[0].setAttribute("tabindex",this._tabindex),e[0].setAttribute("aria-disabled","false"),this.$selection=e},o.prototype.bind=function(e,t){var n=this,i=e.id+"-results";this.container=e,this.$selection.on("focus",function(e){n.trigger("focus",e)}),this.$selection.on("blur",function(e){n._handleBlur(e)}),this.$selection.on("keydown",function(e){n.trigger("keypress",e),e.which===s.SPACE&&e.preventDefault()}),e.on("results:focus",function(e){n.$selection[0].setAttribute("aria-activedescendant",e.data._resultId)}),e.on("selection:update",function(e){n.update(e.data)}),e.on("open",function(){n.$selection[0].setAttribute("aria-expanded","true"),n.$selection[0].setAttribute("aria-owns",i),n._attachCloseHandler(e)}),e.on("close",function(){n.$selection[0].setAttribute("aria-expanded","false"),n.$selection[0].removeAttribute("aria-activedescendant"),n.$selection[0].removeAttribute("aria-owns"),n.$selection.trigger("focus"),n._detachCloseHandler(e)}),e.on("enable",function(){n.$selection[0].setAttribute("tabindex",n._tabindex),n.$selection[0].setAttribute("aria-disabled","false")}),e.on("disable",function(){n.$selection[0].setAttribute("tabindex","-1"),n.$selection[0].setAttribute("aria-disabled","true")})},o.prototype._handleBlur=function(e){var t=this;window.setTimeout(function(){document.activeElement==t.$selection[0]||n.contains(t.$selection[0],document.activeElement)||t.trigger("blur",e)},1)},o.prototype._attachCloseHandler=function(e){n(document.body).on("mousedown.select2."+e.id,function(e){var t=n(e.target).closest(".select2");n(".select2.select2-container--open").each(function(){this!=t[0]&&i.GetData(this,"element").select2("close")})})},o.prototype._detachCloseHandler=function(e){n(document.body).off("mousedown.select2."+e.id)},o.prototype.position=function(e,t){t.find(".selection").append(e)},o.prototype.destroy=function(){this._detachCloseHandler(this.container)},o.prototype.update=function(e){throw new Error("The `update` method must be defined in child classes.")},o.prototype.isEnabled=function(){return!this.isDisabled()},o.prototype.isDisabled=function(){return this.options.get("disabled")},o}),e.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(e,t,n,i){function o(){o.__super__.constructor.apply(this,arguments)}return n.Extend(o,t),o.prototype.render=function(){var e=o.__super__.render.call(this);return e[0].classList.add("select2-selection--single"),e.html(''),e},o.prototype.bind=function(t,e){var n=this,i=(o.__super__.bind.apply(this,arguments),t.id+"-container"),s=this.$selection.find(".select2-selection__rendered")[0];null!=s&&(s.setAttribute("id",i),s.setAttribute("role","textbox"),s.setAttribute("aria-readonly","true")),this.$selection[0].setAttribute("aria-labelledby",i),this.$selection[0].setAttribute("aria-controls",i),this.$selection.on("mousedown",function(e){1===e.which&&n.trigger("toggle",{originalEvent:e})}),this.$selection.on("focus",function(e){}),this.$selection.on("blur",function(e){}),t.on("focus",function(e){t.isOpen()||n.$selection.trigger("focus")})},o.prototype.clear=function(){var e=this.$selection.find(".select2-selection__rendered");e.empty(),e[0].removeAttribute("title")},o.prototype.display=function(e,t){var n=this.options.get("templateSelection");return this.options.get("escapeMarkup")(n(e,t))},o.prototype.selectionContainer=function(){return e("")},o.prototype.update=function(e){var t,n;0===e.length?this.clear():(e=e[0],t=this.$selection.find(".select2-selection__rendered"),n=this.display(e,t),t.empty().append(n),(n=e.title||e.text)?t[0].setAttribute("title",n):t[0].removeAttribute("title"))},o}),e.define("select2/selection/multiple",["jquery","./base","../utils"],function(s,e,c){function o(e,t){o.__super__.constructor.apply(this,arguments)}return c.Extend(o,e),o.prototype.render=function(){var e=o.__super__.render.call(this);return e[0].classList.add("select2-selection--multiple"),e.html('
            '),e},o.prototype.bind=function(e,t){var n=this,e=(o.__super__.bind.apply(this,arguments),e.id+"-container"),i=this.$selection.find(".select2-selection__rendered")[0];null!=i&&i.setAttribute("id",e),this.$selection.on("click",function(e){n.trigger("toggle",{originalEvent:e})}),this.$selection.on("click",".select2-selection__choice__remove",function(e){var t;n.isDisabled()||(t=s(this).parent(),t=c.GetData(t[0],"data"),n.trigger("unselect",{originalEvent:e,data:t}))}),this.$selection.on("keydown",".select2-selection__choice__remove",function(e){n.isDisabled()||e.stopPropagation()})},o.prototype.clear=function(){var e=this.$selection.find(".select2-selection__rendered");e.empty(),e[0].removeAttribute("title")},o.prototype.display=function(e,t){var n=this.options.get("templateSelection");return this.options.get("escapeMarkup")(n(e,t))},o.prototype.selectionContainer=function(){return s('
          • ')},o.prototype.update=function(e){if(this.clear(),0!==e.length){for(var t=[],n=this.$selection.find(".select2-selection__rendered")[0].getAttribute("id")+"-choice-",i=0;i'))[0].setAttribute("title",n()),i[0].setAttribute("aria-label",n()),i[0].setAttribute("aria-describedby",e),l.StoreData(i[0],"data",t),this.$selection.prepend(i),this.$selection[0].classList.add("select2-selection--clearable"))},e}),e.define("select2/selection/search",["jquery","../utils","../keys"],function(i,l,a){function e(e,t,n){e.call(this,t,n)}return e.prototype.render=function(e){var t=this.options.get("translations").get("search"),n=i(''),n=(this.$searchContainer=n,this.$search=n.find("textarea"),this.$search[0].autocomplete=this.options.get("autocomplete"),this.$search[0].setAttribute("aria-label",t()),e.call(this));return this._transferTabIndex(),n.append(this.$searchContainer),n},e.prototype.bind=function(e,t,n){var i=this,s=t.id+"-results",o=t.id+"-container",e=(e.call(this,t,n),i.$search[0].setAttribute("aria-describedby",o),t.on("open",function(){i.$search[0].setAttribute("aria-controls",s),i.$search.trigger("focus")}),t.on("close",function(){i.$search.val(""),i.resizeSearch(),i.$search[0].removeAttribute("aria-controls"),i.$search[0].removeAttribute("aria-activedescendant"),i.$search.trigger("focus")}),t.on("enable",function(){i.$search[0].disabled=!1,i._transferTabIndex()}),t.on("disable",function(){i.$search[0].disabled=!0}),t.on("focus",function(e){i.$search.trigger("focus")}),t.on("results:focus",function(e){e.data._resultId?i.$search[0].setAttribute("aria-activedescendant",e.data._resultId):i.$search[0].removeAttribute("aria-activedescendant")}),this.$selection.on("focusin",".select2-search--inline",function(e){i.trigger("focus",e)}),this.$selection.on("focusout",".select2-search--inline",function(e){i._handleBlur(e)}),this.$selection.on("keydown",".select2-search--inline",function(e){var t;e.stopPropagation(),i.trigger("keypress",e),i._keyUpPrevented=e.isDefaultPrevented(),e.which===a.BACKSPACE&&""===i.$search.val()&&0<(t=i.$selection.find(".select2-selection__choice").last()).length&&(t=l.GetData(t[0],"data"),i.searchRemoveChoice(t),e.preventDefault())}),this.$selection.on("click",".select2-search--inline",function(e){i.$search.val()&&e.stopPropagation()}),document.documentMode),r=e&&e<=11;this.$selection.on("input.searchcheck",".select2-search--inline",function(e){r?i.$selection.off("input.search input.searchcheck"):i.$selection.off("keyup.search")}),this.$selection.on("keyup.search input.search",".select2-search--inline",function(e){var t;r&&"input"===e.type?i.$selection.off("input.search input.searchcheck"):(t=e.which)!=a.SHIFT&&t!=a.CTRL&&t!=a.ALT&&t!=a.TAB&&i.handleSearch(e)})},e.prototype._transferTabIndex=function(e){this.$search[0].setAttribute("tabindex",this.$selection[0].getAttribute("tabindex")),this.$selection[0].setAttribute("tabindex","-1")},e.prototype.createPlaceholder=function(e,t){this.$search[0].setAttribute("placeholder",t.text)},e.prototype.update=function(e,t){var n=this.$search[0]==document.activeElement;this.$search[0].setAttribute("placeholder",""),e.call(this,t),this.resizeSearch(),n&&this.$search.trigger("focus")},e.prototype.handleSearch=function(){var e;this.resizeSearch(),this._keyUpPrevented||(e=this.$search.val(),this.trigger("query",{term:e})),this._keyUpPrevented=!1},e.prototype.searchRemoveChoice=function(e,t){this.trigger("unselect",{data:t}),this.$search.val(t.text),this.handleSearch()},e.prototype.resizeSearch=function(){this.$search.css("width","25px");var e="100%";""===this.$search[0].getAttribute("placeholder")&&(e=.75*(this.$search.val().length+1)+"em"),this.$search.css("width",e)},e}),e.define("select2/selection/selectionCss",["../utils"],function(n){function e(){}return e.prototype.render=function(e){var t=e.call(this),e=this.options.get("selectionCssClass")||"";return-1!==e.indexOf(":all:")&&(e=e.replace(":all:",""),n.copyNonInternalCssClasses(t[0],this.$element[0])),e.trim().split(" ").forEach(function(e){0function(){return c(this).val()==e.id})(a)),l=this.item(r),l=c.extend(!0,{},a,l),l=this.option(l),r.replaceWith(l)):(r=this.option(a),a.children&&(l=this.convertToOptions(a.children),r.append(l)),s.push(r))}return s},i}),e.define("select2/data/ajax",["./array","../utils","jquery"],function(e,t,o){function r(e,t){this.ajaxOptions=this._applyDefaults(t.get("ajax")),null!=this.ajaxOptions.processResults&&(this.processResults=this.ajaxOptions.processResults),r.__super__.constructor.call(this,e,t)}return t.Extend(r,e),r.prototype._applyDefaults=function(e){return o.extend({},{data:function(e){return o.extend({},e,{q:e.term})},transport:function(e,t,n){e=o.ajax(e);return e.then(t),e.fail(n),e}},e,!0)},r.prototype.processResults=function(e){return e},r.prototype.query=function(t,n){var i=this,s=(null!=this._request&&("function"==typeof this._request.abort&&this._request.abort(),this._request=null),o.extend({type:"GET"},this.ajaxOptions));function e(){var e=s.transport(s,function(e){e=i.processResults(e,t);e&&e.results&&Array.isArray(e.results)?e.results=e.results.map(r.prototype._normalizeItem):i.options.get("debug")&&window.console&&console.error&&console.error("Select2: The AJAX results did not return an array in the `results` key of the response."),n(e)},function(){e&&"status"in e&&(0===e.status||"0"===e.status)||i.trigger("results:message",{message:"errorLoading"})});i._request=e}"function"==typeof s.url&&(s.url=s.url.call(this.$element,t)),"function"==typeof s.data&&(s.data=s.data.call(this.$element,t)),this.ajaxOptions.delay&&null!=t.term?(this._queryTimeout&&window.clearTimeout(this._queryTimeout),this._queryTimeout=window.setTimeout(e,this.ajaxOptions.delay)):e()},r}),e.define("select2/data/tags",["jquery"],function(t){function e(e,t,n){var i=n.get("tags"),s=n.get("createTag"),s=(void 0!==s&&(this.createTag=s),n.get("insertTag"));if(void 0!==s&&(this.insertTag=s),e.call(this,t,n),Array.isArray(i))for(var o=0;othis.maximumInputLength?this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:t.term,params:t}}):e.call(this,t,n)},e}),e.define("select2/data/maximumSelectionLength",[],function(){function e(e,t,n){this.maximumSelectionLength=n.get("maximumSelectionLength"),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var i=this;e.call(this,t,n),t.on("select",function(){i._checkIfMaximumSelected()})},e.prototype.query=function(e,t,n){var i=this;this._checkIfMaximumSelected(function(){e.call(i,t,n)})},e.prototype._checkIfMaximumSelected=function(e,t){var n=this;this.current(function(e){e=null!=e?e.length:0;0=n.maximumSelectionLength?n.trigger("results:message",{message:"maximumSelected",args:{maximum:n.maximumSelectionLength}}):t&&t()})},e}),e.define("select2/dropdown",["jquery","./utils"],function(t,e){function n(e,t){this.$element=e,this.options=t,n.__super__.constructor.call(this)}return e.Extend(n,e.Observable),n.prototype.render=function(){var e=t('');return e[0].setAttribute("dir",this.options.get("dir")),this.$dropdown=e},n.prototype.bind=function(){},n.prototype.position=function(e,t){},n.prototype.destroy=function(){this.$dropdown.remove()},n}),e.define("select2/dropdown/search",["jquery"],function(o){function e(){}return e.prototype.render=function(e){var e=e.call(this),t=this.options.get("translations").get("search"),n=o('');return this.$searchContainer=n,this.$search=n.find("input"),this.$search[0].autocomplete=this.options.get("autocomplete"),this.$search[0].setAttribute("aria-label",t()),e.prepend(n),e},e.prototype.bind=function(e,t,n){var i=this,s=t.id+"-results";e.call(this,t,n),this.$search.on("keydown",function(e){i.trigger("keypress",e),i._keyUpPrevented=e.isDefaultPrevented()}),this.$search.on("input",function(e){o(this).off("keyup")}),this.$search.on("keyup input",function(e){i.handleSearch(e)}),t.on("open",function(){i.$search[0].setAttribute("tabindex",0),i.$search[0].setAttribute("aria-controls",s),i.$search.trigger("focus"),window.setTimeout(function(){i.$search.trigger("focus")},0)}),t.on("close",function(){i.$search[0].setAttribute("tabindex",-1),i.$search[0].removeAttribute("aria-controls"),i.$search[0].removeAttribute("aria-activedescendant"),i.$search.val(""),i.$search.trigger("blur")}),t.on("focus",function(){t.isOpen()||i.$search.trigger("focus")}),t.on("results:all",function(e){null!=e.query.term&&""!==e.query.term||(i.showSearch(e)?i.$searchContainer[0].classList.remove("select2-search--hide"):i.$searchContainer[0].classList.add("select2-search--hide"))}),t.on("results:focus",function(e){e.data._resultId?i.$search[0].setAttribute("aria-activedescendant",e.data._resultId):i.$search[0].removeAttribute("aria-activedescendant")})},e.prototype.handleSearch=function(e){var t;this._keyUpPrevented||(t=this.$search.val(),this.trigger("query",{term:t})),this._keyUpPrevented=!1},e.prototype.showSearch=function(e,t){return!0},e}),e.define("select2/dropdown/hidePlaceholder",[],function(){function e(e,t,n,i){this.placeholder=this.normalizePlaceholder(n.get("placeholder")),e.call(this,t,n,i)}return e.prototype.append=function(e,t){t.results=this.removePlaceholder(t.results),e.call(this,t)},e.prototype.normalizePlaceholder=function(e,t){return t="object"!=typeof t?{id:"",text:t}:t},e.prototype.removePlaceholder=function(e,t){for(var n=t.slice(0),i=t.length-1;0<=i;i--){var s=t[i];this.placeholder.id===s.id&&n.splice(i,1)}return n},e}),e.define("select2/dropdown/infiniteScroll",["jquery"],function(n){function e(e,t,n,i){this.lastParams={},e.call(this,t,n,i),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return e.prototype.append=function(e,t){this.$loadingMore.remove(),this.loading=!1,e.call(this,t),this.showLoadingMore(t)&&(this.$results.append(this.$loadingMore),this.loadMoreIfNeeded())},e.prototype.bind=function(e,t,n){var i=this;e.call(this,t,n),t.on("query",function(e){i.lastParams=e,i.loading=!0}),t.on("query:append",function(e){i.lastParams=e,i.loading=!0}),this.$results.on("scroll",this.loadMoreIfNeeded.bind(this))},e.prototype.loadMoreIfNeeded=function(){var e=n.contains(document.documentElement,this.$loadingMore[0]);!this.loading&&e&&(e=this.$results.offset().top+this.$results.outerHeight(!1),this.$loadingMore.offset().top+this.$loadingMore.outerHeight(!1)<=e+50)&&this.loadMore()},e.prototype.loadMore=function(){this.loading=!0;var e=n.extend({},{page:1},this.lastParams);e.page++,this.trigger("query:append",e)},e.prototype.showLoadingMore=function(e,t){return t.pagination&&t.pagination.more},e.prototype.createLoadingMore=function(){var e=n('
          • '),t=this.options.get("translations").get("loadingMore");return e.html(t(this.lastParams)),e},e}),e.define("select2/dropdown/attachBody",["jquery","../utils"],function(u,r){function e(e,t,n){this.$dropdownParent=u(n.get("dropdownParent")||document.body),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var i=this;e.call(this,t,n),t.on("open",function(){i._showDropdown(),i._attachPositioningHandler(t),i._bindContainerResultHandlers(t)}),t.on("close",function(){i._hideDropdown(),i._detachPositioningHandler(t)}),this.$dropdownContainer.on("mousedown",function(e){e.stopPropagation()})},e.prototype.destroy=function(e){e.call(this),this.$dropdownContainer.remove()},e.prototype.position=function(e,t,n){t[0].setAttribute("class",n[0].getAttribute("class")),t[0].classList.remove("select2"),t[0].classList.add("select2-container--open"),t.css({position:"absolute",top:-999999}),this.$container=n},e.prototype.render=function(e){var t=u(""),e=e.call(this);return t.append(e),this.$dropdownContainer=t},e.prototype._hideDropdown=function(e){this.$dropdownContainer.detach()},e.prototype._bindContainerResultHandlers=function(e,t){var n;this._containerResultsHandlersBound||(n=this,t.on("results:all",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:append",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:message",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("select",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("unselect",function(){n._positionDropdown(),n._resizeDropdown()}),this._containerResultsHandlersBound=!0)},e.prototype._attachPositioningHandler=function(e,t){var n=this,i="scroll.select2."+t.id,s="resize.select2."+t.id,t="orientationchange.select2."+t.id,o=this.$container.parents().filter(r.hasScroll);o.each(function(){r.StoreData(this,"select2-scroll-position",{x:u(this).scrollLeft(),y:u(this).scrollTop()})}),o.on(i,function(e){var t=r.GetData(this,"select2-scroll-position");u(this).scrollTop(t.y)}),u(window).on(i+" "+s+" "+t,function(e){n._positionDropdown(),n._resizeDropdown()})},e.prototype._detachPositioningHandler=function(e,t){var n="scroll.select2."+t.id,i="resize.select2."+t.id,t="orientationchange.select2."+t.id;this.$container.parents().filter(r.hasScroll).off(n),u(window).off(n+" "+i+" "+t)},e.prototype._positionDropdown=function(){var e=u(window),t=this.$dropdown[0].classList.contains("select2-dropdown--above"),n=this.$dropdown[0].classList.contains("select2-dropdown--below"),i=null,s=this.$container.offset(),o=(s.bottom=s.top+this.$container.outerHeight(!1),{height:this.$container.outerHeight(!1)});o.top=s.top,o.bottom=s.top+o.height;var r=this.$dropdown.outerHeight(!1),l=e.scrollTop(),e=e.scrollTop()+e.height(),l=ls.bottom+r,s={left:s.left,top:o.bottom},a=this.$dropdownParent,c=("static"===a.css("position")&&(a=a.offsetParent()),{top:0,left:0});(u.contains(document.body,a[0])||a[0].isConnected)&&(c=a.offset()),s.top-=c.top,s.left-=c.left,t||n||(i="below"),e||!l||t?!l&&e&&t&&(i="below"):i="above",("above"==i||t&&"below"!==i)&&(s.top=o.top-c.top-r),null!=i&&(this.$dropdown[0].classList.remove("select2-dropdown--below"),this.$dropdown[0].classList.remove("select2-dropdown--above"),this.$dropdown[0].classList.add("select2-dropdown--"+i),this.$container[0].classList.remove("select2-container--below"),this.$container[0].classList.remove("select2-container--above"),this.$container[0].classList.add("select2-container--"+i)),this.$dropdownContainer.css(s)},e.prototype._resizeDropdown=function(){var e={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(e.minWidth=e.width,e.position="relative",e.width="auto"),this.$dropdown.css(e)},e.prototype._showDropdown=function(e){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},e}),e.define("select2/dropdown/minimumResultsForSearch",[],function(){function e(e,t,n,i){this.minimumResultsForSearch=n.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),e.call(this,t,n,i)}return e.prototype.showSearch=function(e,t){return!(function e(t){for(var n=0,i=0;i');return e[0].setAttribute("dir",this.options.get("dir")),this.$container=e,this.$container[0].classList.add("select2-container--"+this.options.get("theme")),o.StoreData(e[0],"element",this.$element),e},r}),e.define("jquery-mousewheel",["jquery"],function(e){return e}),e.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults","./select2/utils"],function(s,e,o,t,r){var l;return null==s.fn.select2&&(l=["open","close","destroy"],s.fn.select2=function(t){if("object"==typeof(t=t||{}))return this.each(function(){var e=s.extend(!0,{},t);new o(s(this),e)}),this;var n,i;if("string"==typeof t)return i=Array.prototype.slice.call(arguments,1),this.each(function(){var e=r.GetData(this,"select2");null==e&&window.console&&console.error&&console.error("The select2('"+t+"') method was called on an element that is not using Select2."),n=e[t].apply(e,i)}),-1 'dashicons-editor-help', 'has_archive' => false, ); - register_post_type( 'faq_item', $args ); + register_post_type( FQJ_POST_TYPE, $args ); } add_action( 'init', 'fqj_register_cpt_faq_item' ); +/** + * Rename FAQ items stored under a legacy post type to FQJ_POST_TYPE, once per site. + * + * Runs before the schema upgrade and the data migration, which only look at FQJ_POST_TYPE. + */ +function fqj_migrate_legacy_post_types() { + global $wpdb; + + if ( FQJ_POST_TYPE === get_option( FQJ_POST_TYPE_OPTION ) ) { + return; + } + + foreach ( FQJ_LEGACY_POST_TYPES as $legacy ) { + // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s", $legacy ) ); + if ( $ids ) { + $wpdb->update( $wpdb->posts, array( 'post_type' => FQJ_POST_TYPE ), array( 'post_type' => $legacy ) ); + } + // phpcs:enable + foreach ( $ids as $id ) { + clean_post_cache( (int) $id ); + } + } + + update_option( FQJ_POST_TYPE_OPTION, FQJ_POST_TYPE ); +} +add_action( 'plugins_loaded', 'fqj_migrate_legacy_post_types', 5 ); + /** * Run a callback for every site of the network, or once when the install is single-site. * @@ -108,6 +141,8 @@ function fqj_for_each_site( $callback ) { * Activate the plugin for the current site: create DB table, defaults, schedule cron. */ function fqj_activate_site() { + fqj_migrate_legacy_post_types(); + // A site with no FAQs yet has nothing to migrate. if ( false === get_option( FQJ_DATA_VERSION_OPTION ) && ! fqj_has_faq_items() ) { // Cached output can outlive deleted FAQs on a reactivated site. diff --git a/includes/admin.php b/includes/admin.php index b5813ce..0ae4bbd 100644 --- a/includes/admin.php +++ b/includes/admin.php @@ -12,7 +12,7 @@ // phpcs:disable PSR1.Files.SideEffects /** - * Enqueue admin assets for faq_item edit screen + * Enqueue admin assets for the FAQ edit screen * * @param string $hook The current admin page. */ @@ -21,7 +21,7 @@ function fqj_admin_assets( $hook ) { if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) { return; } - if ( ! $post || 'faq_item' !== $post->post_type ) { + if ( ! $post || FQJ_POST_TYPE !== $post->post_type ) { return; } @@ -45,13 +45,13 @@ function fqj_admin_assets( $hook ) { 'fqj-select2-css', FQJ_PLUGIN_URL . 'assets/vendor/select2/select2' . $suffix . '.css', array(), - '4.0.13' + '4.1.0' ); wp_enqueue_script( 'fqj-select2-js', FQJ_PLUGIN_URL . 'assets/vendor/select2/select2' . $suffix . '.js', array( 'jquery' ), - '4.0.13', + '4.1.0', true ); } else { @@ -71,7 +71,7 @@ function fqj_admin_assets( $hook ) { 'fqj-select2-css', FQJ_PLUGIN_URL . 'assets/vendor/select2/select2' . $suffix . '.css', array(), - '4.0.13' + '4.1.0' ); } } @@ -112,7 +112,7 @@ function fqj_add_meta_boxes() { 'fqj_assoc_rules', __( 'Associations & Output', 'faq-jsonld' ), 'fqj_assoc_rules_meta_box_cb', - 'faq_item', + FQJ_POST_TYPE, 'normal', 'default' ); @@ -221,7 +221,7 @@ function fqj_save_meta_handler( $post_id, $post ) { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } - if ( 'faq_item' !== $post->post_type ) { + if ( FQJ_POST_TYPE !== $post->post_type ) { return; } if ( ! current_user_can( 'edit_post', $post_id ) ) { @@ -376,7 +376,7 @@ function fqj_unmatched_urls_notice() { */ function fqj_url_migration_report_notice() { $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; - if ( ! $screen || 'edit-faq_item' !== $screen->id || ! current_user_can( 'edit_others_posts' ) ) { + if ( ! $screen || 'edit-' . FQJ_POST_TYPE !== $screen->id || ! current_user_can( 'edit_others_posts' ) ) { return; } $report = get_option( FQJ_URL_MIGRATION_REPORT_OPTION ); @@ -423,7 +423,7 @@ function fqj_dismiss_url_migration_report() { */ function fqj_admin_post_dismiss_url_migration_report() { fqj_dismiss_url_migration_report(); - wp_safe_redirect( admin_url( 'edit.php?post_type=faq_item' ) ); + wp_safe_redirect( admin_url( 'edit.php?post_type=' . FQJ_POST_TYPE ) ); exit; } add_action( 'admin_post_fqj_dismiss_url_migration_report', 'fqj_admin_post_dismiss_url_migration_report' ); @@ -444,7 +444,7 @@ function fqj_ajax_search_posts() { $args = array( 's' => $q, - 'post_type' => array_values( array_diff( get_post_types( array( 'public' => true ) ), array( 'attachment', 'faq_item' ) ) ), + 'post_type' => array_values( array_diff( get_post_types( array( 'public' => true ) ), array( 'attachment', FQJ_POST_TYPE ) ) ), 'posts_per_page' => 20, 'post_status' => 'publish', ); diff --git a/includes/coverage.php b/includes/coverage.php index 4ad31de..cecfd66 100644 --- a/includes/coverage.php +++ b/includes/coverage.php @@ -393,7 +393,7 @@ function fqj_posts_covered_by_mapping( $mapping, $after_id = 0, $limit = 100 ) { */ function fqj_on_object_terms_set( $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids ) { $type = get_post_type( $object_id ); - if ( ! $type || 'faq_item' === $type || wp_is_post_revision( $object_id ) ) { + if ( ! $type || FQJ_POST_TYPE === $type || wp_is_post_revision( $object_id ) ) { return; } if ( ! $append ) { @@ -417,7 +417,7 @@ function fqj_on_object_terms_set( $object_id, $terms, $tt_ids, $taxonomy, $appen * @param WP_Post $post_before Post before the update. */ function fqj_on_post_permalink_change( $post_id, $post_after, $post_before ) { - if ( 'faq_item' === $post_after->post_type || wp_is_post_revision( $post_id ) ) { + if ( FQJ_POST_TYPE === $post_after->post_type || wp_is_post_revision( $post_id ) ) { return; } if ( $post_after->post_name === $post_before->post_name diff --git a/includes/frontend.php b/includes/frontend.php index a17e5b7..beff415 100644 --- a/includes/frontend.php +++ b/includes/frontend.php @@ -153,7 +153,7 @@ function fqj_render_faq_jsonld( $post_id ) { $faqs = get_posts( array( - 'post_type' => 'faq_item', + 'post_type' => FQJ_POST_TYPE, 'post_status' => 'publish', 'posts_per_page' => -1, 'post__in' => $faq_ids, diff --git a/includes/health.php b/includes/health.php index 894fbb9..8ff9e8c 100644 --- a/includes/health.php +++ b/includes/health.php @@ -25,7 +25,7 @@ */ function fqj_register_health_page() { add_submenu_page( - 'edit.php?post_type=faq_item', + 'edit.php?post_type=' . FQJ_POST_TYPE, __( 'FAQ JSON-LD Health', 'faq-jsonld' ), __( 'Health', 'faq-jsonld' ), 'manage_options', @@ -42,7 +42,7 @@ function fqj_register_health_page() { */ function fqj_health_admin_assets( $hook ) { // check page hook: load only on fqj-health page. - if ( 'faq_item_page_fqj-health' !== $hook ) { + if ( FQJ_POST_TYPE . '_page_fqj-health' !== $hook ) { return; } diff --git a/includes/indexer.php b/includes/indexer.php index b464823..19cab1a 100644 --- a/includes/indexer.php +++ b/includes/indexer.php @@ -109,7 +109,7 @@ function fqj_get_faq_ids_after( $after_id, $limit ) { $ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status <> 'auto-draft' AND ID > %d ORDER BY ID ASC LIMIT %d", - 'faq_item', + FQJ_POST_TYPE, (int) $after_id, (int) $limit ) @@ -473,7 +473,7 @@ function fqj_invalidate_faq_mappings( $faq_id ) { * @param WP_Post $post Post object. */ function fqj_on_faq_transition_status( $new_status, $old_status, $post ) { - if ( 'faq_item' !== $post->post_type || $new_status === $old_status ) { + if ( FQJ_POST_TYPE !== $post->post_type || $new_status === $old_status ) { return; } fqj_invalidate_faq_mappings( $post->ID ); @@ -488,7 +488,7 @@ function fqj_on_faq_transition_status( $new_status, $old_status, $post ) { * @param WP_Post $post_before Post before the update. */ function fqj_on_faq_updated( $post_id, $post_after, $post_before ) { - if ( 'faq_item' !== $post_after->post_type ) { + if ( FQJ_POST_TYPE !== $post_after->post_type ) { return; } if ( $post_after->post_title === $post_before->post_title && $post_after->post_content === $post_before->post_content ) { @@ -510,7 +510,7 @@ function fqj_on_faq_updated( $post_id, $post_after, $post_before ) { */ function fqj_on_faq_before_delete( $post_id, $post = null ) { $post = $post ? $post : get_post( $post_id ); - if ( ! $post || 'faq_item' !== $post->post_type ) { + if ( ! $post || FQJ_POST_TYPE !== $post->post_type ) { return; } fqj_invalidate_faq_mappings( $post_id ); diff --git a/includes/queue.php b/includes/queue.php index fd360b0..277dbac 100644 --- a/includes/queue.php +++ b/includes/queue.php @@ -393,18 +393,26 @@ function fqj_process_invalidation_queue_now( $limit = null ) { } /** - * Admin notice helper: show queue length on admin screens for admins. + * Admin notice on the plugin's own screens (FAQ list and editor, Health, Settings): show the queue length to admins. */ function fqj_admin_queue_notice() { if ( ! current_user_can( 'manage_options' ) ) { return; } + $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; + $own_pages = array( FQJ_POST_TYPE . '_page_fqj-health', FQJ_POST_TYPE . '_page_fqj-settings' ); + if ( ! $screen || ( FQJ_POST_TYPE !== $screen->post_type && ! in_array( $screen->id, $own_pages, true ) ) ) { + return; + } $len = fqj_queue_length(); if ( $len > 0 ) { printf( - '

            FAQ JSON-LD queue: %d ' . - 'items pending invalidation. The background worker (WP-Cron) will process them in batches.

            ', - intval( $len ) + '

            %s

            ', + sprintf( + /* translators: %s: number of queued items. */ + esc_html( _n( 'FAQ JSON-LD queue: %s item pending invalidation. The background worker (WP-Cron) will process it in batches.', 'FAQ JSON-LD queue: %s items pending invalidation. The background worker (WP-Cron) will process them in batches.', $len, 'faq-jsonld' ) ), + '' . intval( $len ) . '' + ) ); } } diff --git a/includes/settings.php b/includes/settings.php index 6485d58..26250d2 100644 --- a/includes/settings.php +++ b/includes/settings.php @@ -16,7 +16,7 @@ */ function fqj_register_settings_page() { add_submenu_page( - 'edit.php?post_type=faq_item', + 'edit.php?post_type=' . FQJ_POST_TYPE, __( 'FAQ JSON-LD Settings', 'faq-jsonld' ), __( 'Settings', 'faq-jsonld' ), 'manage_options', diff --git a/readme.txt b/readme.txt index 2715a1b..bb7bdec 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: faq, schema, json-ld, structured data, seo Requires at least: 6.5 Tested up to: 7.1 Requires PHP: 7.2 -Stable tag: 3.1.0 +Stable tag: 3.1.1 License: GPLv2 or later License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html @@ -38,7 +38,7 @@ Built for large sites: * `fqj_question_text( $text, $faq_post )` filters the plain-text question. * `fqj_answer_text( $text, $faq_post )` filters the plain-text answer. -* `fqj_uninstall_delete_faqs` (default `false`): return `true` to delete `faq_item` posts when the plugin is deleted. +* `fqj_uninstall_delete_faqs` (default `false`): return `true` to delete FAQ posts when the plugin is deleted. Development happens on [GitHub](https://github.com/renderbit-technologies/faq-jsonld-wordpress-plugin). @@ -69,6 +69,11 @@ Its options, transients, scheduled events, post meta and mapping table are remov == Changelog == += 3.1.1 = +* FAQ items now use the prefixed post type `fqj_faq` instead of `faq_item`, so they cannot collide with another plugin's FAQ post type. Existing FAQs are moved over automatically on upgrade; links or code that use `post_type=faq_item` need updating. +* The invalidation queue notice only appears on the plugin's own screens (FAQ list and editor, Health, Settings) instead of on every admin page, and can be translated. +* Updated the bundled Select2 to 4.1.0. + = 3.1.0 = * URL associations now mean the URL itself: a FAQ shows on whichever post has that permalink now. Before, saving a URL also tied the FAQ to the post found there, so it followed that post when it was renamed and appeared on shortlinks and the front page's slug URL. * URLs on your own site are stored relative to the site root, so moving between `http` and `https`, adding or dropping `www.`, or changing domain keeps them working. @@ -92,6 +97,9 @@ Its options, transients, scheduled events, post meta and mapping table are remov == Upgrade Notice == += 3.1.1 = +FAQ items move to the prefixed post type `fqj_faq` automatically. Update any bookmarks or custom code that use `post_type=faq_item`. + = 3.1.0 = URL associations now match the URL itself. The upgrade rewrites URLs that only matched the old way and lists every change on the FAQ list screen. diff --git a/tests/php/class-fqj-testcase.php b/tests/php/class-fqj-testcase.php index 3b82f72..8d6aee9 100644 --- a/tests/php/class-fqj-testcase.php +++ b/tests/php/class-fqj-testcase.php @@ -41,7 +41,7 @@ public function tear_down() { protected function create_faq( $question = 'What is it?', $answer = 'It is a thing.', $payload = null ) { $faq_id = self::factory()->post->create( array( - 'post_type' => 'faq_item', + 'post_type' => 'fqj_faq', 'post_status' => 'publish', 'post_title' => $question, 'post_content' => $answer, diff --git a/tests/php/test-admin.php b/tests/php/test-admin.php index 3908fb9..60a15ed 100644 --- a/tests/php/test-admin.php +++ b/tests/php/test-admin.php @@ -54,10 +54,10 @@ protected function payload( $faq_id ) { return json_decode( get_post_meta( $faq_id, 'fqj_assoc_data_json', true ), true ); } - public function test_meta_box_is_registered_for_faq_items() { + public function test_meta_box_is_registered_for_faqs() { global $wp_meta_boxes; fqj_add_meta_boxes(); - $this->assertArrayHasKey( 'fqj_assoc_rules', $wp_meta_boxes['faq_item']['normal']['default'] ); + $this->assertArrayHasKey( 'fqj_assoc_rules', $wp_meta_boxes['fqj_faq']['normal']['default'] ); } public function test_save_urls() { diff --git a/tests/php/test-ajax.php b/tests/php/test-ajax.php index 659372f..6c17698 100644 --- a/tests/php/test-ajax.php +++ b/tests/php/test-ajax.php @@ -94,7 +94,7 @@ public function test_search_posts_includes_custom_public_post_types_only() { register_post_type( 'fqj_secret', array( 'public' => false ) ); $book = self::factory()->post->create( array( 'post_title' => 'Zebra book', 'post_type' => 'fqj_book' ) ); self::factory()->post->create( array( 'post_title' => 'Zebra secret', 'post_type' => 'fqj_secret' ) ); - self::factory()->post->create( array( 'post_title' => 'Zebra faq', 'post_type' => 'faq_item' ) ); + self::factory()->post->create( array( 'post_title' => 'Zebra faq', 'post_type' => 'fqj_faq' ) ); $attach = self::factory()->post->create( array( 'post_title' => 'Zebra file', 'post_type' => 'attachment', 'post_status' => 'inherit' ) ); $this->nonce( 'fqj_admin_nonce' ); @@ -147,7 +147,7 @@ public function test_get_post_types_lists_public_types() { $this->assertContains( 'post', $names ); $this->assertContains( 'page', $names ); - $this->assertNotContains( 'faq_item', $names, 'FAQ items are not public.' ); + $this->assertNotContains( 'fqj_faq', $names, 'FAQ items are not public.' ); $by_name = array_column( $results, 'label', 'name' ); $this->assertSame( 'Page', $by_name['page'] ); diff --git a/tests/php/test-frontend.php b/tests/php/test-frontend.php index 29a7f1a..3076a35 100644 --- a/tests/php/test-frontend.php +++ b/tests/php/test-frontend.php @@ -433,7 +433,7 @@ public function test_text_filters_can_modify_question_and_answer() { $json = $this->extract_json_ld( $this->render_head_for( $post ) ); remove_filter( 'fqj_question_text', $q, 10 ); remove_filter( 'fqj_answer_text', $a ); - $this->assertSame( 'Q [faq_item]', $json['mainEntity'][0]['name'] ); + $this->assertSame( 'Q [fqj_faq]', $json['mainEntity'][0]['name'] ); $this->assertSame( 'ANSWER', $json['mainEntity'][0]['acceptedAnswer']['text'] ); } } diff --git a/tests/php/test-health.php b/tests/php/test-health.php index 8d59bbf..bca4bc5 100644 --- a/tests/php/test-health.php +++ b/tests/php/test-health.php @@ -37,7 +37,7 @@ protected function render() { public function test_submenu_is_registered() { global $submenu; fqj_register_health_page(); - $slugs = wp_list_pluck( $submenu['edit.php?post_type=faq_item'], 2 ); + $slugs = wp_list_pluck( $submenu['edit.php?post_type=fqj_faq'], 2 ); $this->assertContains( 'fqj-health', $slugs ); } @@ -99,7 +99,7 @@ public function test_assets_only_on_health_page() { fqj_health_admin_assets( 'edit.php' ); $this->assertFalse( wp_script_is( 'fqj-health-js', 'enqueued' ) ); - fqj_health_admin_assets( 'faq_item_page_fqj-health' ); + fqj_health_admin_assets( 'fqj_faq_page_fqj-health' ); $this->assertTrue( wp_script_is( 'fqj-health-js', 'enqueued' ) ); $this->assertStringContainsString( 'var fqjHealth', wp_scripts()->get_data( 'fqj-health-js', 'data' ) ); } diff --git a/tests/php/test-indexer.php b/tests/php/test-indexer.php index 390da94..34436a1 100644 --- a/tests/php/test-indexer.php +++ b/tests/php/test-indexer.php @@ -69,7 +69,7 @@ public function test_maybe_upgrade_db_skips_newer_stored_version() { } public function test_data_migration_reindexes_and_purges_once() { - $faq = self::factory()->post->create( array( 'post_type' => 'faq_item' ) ); + $faq = self::factory()->post->create( array( 'post_type' => 'fqj_faq' ) ); update_post_meta( $faq, 'fqj_assoc_data_json', wp_json_encode( array( 'posts' => array( 5 ) ) ) ); fqj_insert_mappings( $faq, @@ -115,7 +115,7 @@ public function test_data_migration_normalizes_legacy_faqsection_option_and_purg public function test_data_migration_reindexes_trashed_faqs() { $faq = self::factory()->post->create( array( - 'post_type' => 'faq_item', + 'post_type' => 'fqj_faq', 'post_status' => 'trash', ) ); @@ -139,7 +139,7 @@ public function test_data_migration_reindexes_trashed_faqs() { public function test_data_migration_runs_in_batches() { add_filter( 'fqj_data_migration_batch_size', array( $this, 'tiny_batch' ) ); - self::factory()->post->create_many( 3, array( 'post_type' => 'faq_item' ) ); + self::factory()->post->create_many( 3, array( 'post_type' => 'fqj_faq' ) ); delete_option( FQJ_DATA_VERSION_OPTION ); delete_option( FQJ_DATA_MIGRATION_CURSOR_OPTION ); wp_clear_scheduled_hook( 'fqj_data_migration' ); @@ -157,7 +157,7 @@ public function test_data_migration_runs_in_batches() { } public function test_data_migration_restarts_cursor_left_by_an_older_migration() { - $ids = self::factory()->post->create_many( 3, array( 'post_type' => 'faq_item' ) ); + $ids = self::factory()->post->create_many( 3, array( 'post_type' => 'fqj_faq' ) ); foreach ( $ids as $faq ) { update_post_meta( $faq, 'fqj_assoc_data_json', wp_json_encode( array( 'posts' => array( 5 ) ) ) ); } @@ -175,7 +175,7 @@ public function test_data_migration_restarts_cursor_left_by_an_older_migration() public function test_data_migration_cursor_records_its_version() { add_filter( 'fqj_data_migration_batch_size', array( $this, 'tiny_batch' ) ); - $ids = self::factory()->post->create_many( 3, array( 'post_type' => 'faq_item' ) ); + $ids = self::factory()->post->create_many( 3, array( 'post_type' => 'fqj_faq' ) ); sort( $ids ); delete_option( FQJ_DATA_VERSION_OPTION ); delete_option( FQJ_DATA_MIGRATION_CURSOR_OPTION ); diff --git a/tests/php/test-multisite.php b/tests/php/test-multisite.php index 6154c18..52bcf0c 100644 --- a/tests/php/test-multisite.php +++ b/tests/php/test-multisite.php @@ -99,6 +99,26 @@ public function test_network_activation_keeps_each_sites_settings() { $this->assertSame( 12 * HOUR_IN_SECONDS, get_option( FQJ_OPTION_KEY )['cache_ttl'] ); } + public function test_network_activation_renames_legacy_faqs_on_every_site() { + $site_id = self::factory()->blog->create(); + $faqs = array(); + foreach ( array( get_main_site_id(), $site_id ) as $id ) { + switch_to_blog( $id ); + $faqs[ $id ] = self::factory()->post->create( array( 'post_type' => 'faq_item' ) ); + delete_option( FQJ_POST_TYPE_OPTION ); + restore_current_blog(); + } + + fqj_activate( true ); + + foreach ( $faqs as $id => $faq ) { + switch_to_blog( $id ); + $type = get_post( $faq )->post_type; + restore_current_blog(); + $this->assertSame( FQJ_POST_TYPE, $type, "site {$id}" ); + } + } + public function test_new_site_is_set_up_when_network_active() { $this->network_activate_plugin(); diff --git a/tests/php/test-plugin.php b/tests/php/test-plugin.php index bb1549c..3b7cd0e 100644 --- a/tests/php/test-plugin.php +++ b/tests/php/test-plugin.php @@ -18,17 +18,17 @@ public function test_constants_are_defined() { $this->assertFileExists( FQJ_PLUGIN_DIR . 'faq-jsonld.php' ); } - public function test_faq_item_post_type_is_registered() { - $this->assertTrue( post_type_exists( 'faq_item' ) ); + public function test_faq_post_type_is_registered() { + $this->assertTrue( post_type_exists( 'fqj_faq' ) ); - $pt = get_post_type_object( 'faq_item' ); + $pt = get_post_type_object( 'fqj_faq' ); $this->assertFalse( $pt->public ); $this->assertTrue( $pt->show_ui ); $this->assertFalse( $pt->hierarchical ); $this->assertFalse( $pt->has_archive ); $this->assertSame( 'FAQ Items', $pt->labels->name ); - $this->assertTrue( post_type_supports( 'faq_item', 'title' ) ); - $this->assertTrue( post_type_supports( 'faq_item', 'editor' ) ); + $this->assertTrue( post_type_supports( 'fqj_faq', 'title' ) ); + $this->assertTrue( post_type_supports( 'fqj_faq', 'editor' ) ); } public function test_activation_creates_table() { diff --git a/tests/php/test-post-type-migration.php b/tests/php/test-post-type-migration.php new file mode 100644 index 0000000..7eaba2d --- /dev/null +++ b/tests/php/test-post-type-migration.php @@ -0,0 +1,104 @@ +post->create( + array_merge( + array( + 'post_type' => 'faq_item', + 'post_status' => 'publish', + 'post_title' => 'Legacy?', + 'post_content' => 'Yes.', + ), + $args + ) + ); + if ( null !== $payload ) { + update_post_meta( $faq, 'fqj_assoc_data_json', wp_json_encode( $payload ) ); + } + delete_option( FQJ_POST_TYPE_OPTION ); + + return $faq; + } + + public function test_legacy_post_type_is_not_the_current_one() { + $this->assertSame( 'fqj_faq', FQJ_POST_TYPE ); + $this->assertContains( 'faq_item', FQJ_LEGACY_POST_TYPES ); + $this->assertNotContains( FQJ_POST_TYPE, FQJ_LEGACY_POST_TYPES ); + $this->assertFalse( post_type_exists( 'faq_item' ) ); + } + + public function test_migration_renames_legacy_faqs_in_every_status() { + $published = $this->create_legacy_faq(); + $draft = $this->create_legacy_faq( null, array( 'post_status' => 'draft' ) ); + $trashed = $this->create_legacy_faq( null, array( 'post_status' => 'trash' ) ); + $post = self::factory()->post->create(); + // Prime the object cache with the old post type. + $this->assertSame( 'faq_item', get_post( $published )->post_type ); + + fqj_migrate_legacy_post_types(); + + foreach ( array( $published, $draft, $trashed ) as $id ) { + $this->assertSame( FQJ_POST_TYPE, get_post( $id )->post_type, "post {$id}" ); + } + $this->assertSame( 'post', get_post( $post )->post_type ); + $this->assertSame( FQJ_POST_TYPE, get_option( FQJ_POST_TYPE_OPTION ) ); + } + + public function test_migration_runs_once_per_site() { + fqj_migrate_legacy_post_types(); + $this->assertSame( FQJ_POST_TYPE, get_option( FQJ_POST_TYPE_OPTION ) ); + + // A legacy row added after the migration (e.g. by an import) is left alone; the option short-circuits. + $late = self::factory()->post->create( array( 'post_type' => 'faq_item' ) ); + fqj_migrate_legacy_post_types(); + $this->assertSame( 'faq_item', get_post( $late )->post_type ); + } + + public function test_migration_runs_before_the_schema_upgrade_and_data_migration() { + $migrate = has_action( 'plugins_loaded', 'fqj_migrate_legacy_post_types' ); + $this->assertNotFalse( $migrate ); + $this->assertLessThan( has_action( 'plugins_loaded', 'fqj_maybe_upgrade_db' ), $migrate ); + $this->assertLessThan( has_action( 'plugins_loaded', 'fqj_maybe_schedule_data_migration' ), $migrate ); + } + + public function test_upgrade_from_data_version_2_reindexes_renamed_faqs() { + $post = self::factory()->post->create(); + $faq = $this->create_legacy_faq( array( 'posts' => array( $post ) ) ); + update_option( FQJ_DATA_VERSION_OPTION, '2' ); + + fqj_migrate_legacy_post_types(); + fqj_run_data_migration(); + + $this->assertSame( FQJ_DATA_VERSION, get_option( FQJ_DATA_VERSION_OPTION ) ); + $this->assertSame( array( $faq ), fqj_faq_ids_for_post( $post ) ); + $this->assertStringContainsString( 'Legacy?', fqj_render_faq_jsonld( $post ) ); + } + + public function test_activation_renames_legacy_faqs_before_checking_for_faqs() { + $faq = $this->create_legacy_faq(); + delete_option( FQJ_DATA_VERSION_OPTION ); + + fqj_activate(); + + $this->assertSame( FQJ_POST_TYPE, get_post( $faq )->post_type ); + // The site has FAQs, so the data migration still has to run on it. + $this->assertFalse( get_option( FQJ_DATA_VERSION_OPTION ) ); + } +} diff --git a/tests/php/test-queue.php b/tests/php/test-queue.php index 000f702..4601c85 100644 --- a/tests/php/test-queue.php +++ b/tests/php/test-queue.php @@ -285,6 +285,7 @@ public function test_process_now_uses_batch_size_when_no_limit() { public function test_admin_notice_shows_for_admins_with_pending_items() { wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) ); + set_current_screen( 'edit-' . FQJ_POST_TYPE ); ob_start(); fqj_admin_queue_notice(); @@ -306,4 +307,41 @@ public function test_admin_notice_hidden_for_non_admins() { fqj_admin_queue_notice(); $this->assertSame( '', ob_get_clean() ); } + + /** + * Screens the queue notice appears on. + * + * @dataProvider notice_screen_provider + * + * @param string $screen Screen ID. + * @param bool $expected Whether the notice is shown. + */ + public function test_admin_notice_only_on_plugin_screens( $screen, $expected ) { + wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) ); + fqj_queue_add_posts( array( 1 ) ); + set_current_screen( $screen ); + + ob_start(); + fqj_admin_queue_notice(); + $out = ob_get_clean(); + set_current_screen( 'front' ); + + if ( $expected ) { + $this->assertStringContainsString( '1 item pending', $out ); + } else { + $this->assertSame( '', $out ); + } + } + + public function notice_screen_provider() { + return array( + 'FAQ list' => array( 'edit-fqj_faq', true ), + 'FAQ editor' => array( 'fqj_faq', true ), + 'Health page' => array( 'fqj_faq_page_fqj-health', true ), + 'Settings page' => array( 'fqj_faq_page_fqj-settings', true ), + 'dashboard' => array( 'dashboard', false ), + 'posts list' => array( 'edit-post', false ), + 'plugins' => array( 'plugins', false ), + ); + } } diff --git a/tests/php/test-settings.php b/tests/php/test-settings.php index e765ff9..a46fad7 100644 --- a/tests/php/test-settings.php +++ b/tests/php/test-settings.php @@ -45,7 +45,7 @@ protected function save( $fields ) { public function test_submenu_is_registered() { global $submenu; fqj_register_settings_page(); - $slugs = wp_list_pluck( $submenu['edit.php?post_type=faq_item'], 2 ); + $slugs = wp_list_pluck( $submenu['edit.php?post_type=fqj_faq'], 2 ); $this->assertContains( 'fqj-settings', $slugs ); } diff --git a/tests/php/test-uninstall.php b/tests/php/test-uninstall.php index 5730075..67eb3f2 100644 --- a/tests/php/test-uninstall.php +++ b/tests/php/test-uninstall.php @@ -64,6 +64,7 @@ public function test_uninstall_removes_options_table_and_transients() { global $wpdb; update_option( FQJ_OPTION_KEY, array( 'cache_ttl' => 60 ) ); + update_option( FQJ_POST_TYPE_OPTION, FQJ_POST_TYPE ); fqj_update_option_no_autoload( FQJ_URL_MIGRATION_REPORT_OPTION, array( array( 'faq_id' => 1, 'old' => '', 'new' => '/x' ) ) ); set_transient( 'fqj_unmatched_urls_1_2', array( 'x' ), 600 ); $key = fqj_cache_key( 1 ); @@ -77,6 +78,7 @@ public function test_uninstall_removes_options_table_and_transients() { $this->assertFalse( get_option( '_transient_timeout_' . $key ) ); $this->assertFalse( get_option( 'fqj_cache_gen' ) ); $this->assertFalse( get_option( FQJ_URL_MIGRATION_REPORT_OPTION ) ); + $this->assertFalse( get_option( FQJ_POST_TYPE_OPTION ) ); $this->assertFalse( get_transient( 'fqj_unmatched_urls_1_2' ) ); $this->assertSame( 'y', get_transient( 'keep_me' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery @@ -93,7 +95,7 @@ public function test_uninstall_removes_log_queue_events_and_meta_but_keeps_faqs( set_transient( 'fqj_rejected_urls_1_2', array( 'bad' ), 600 ); wp_schedule_event( time() + 60, 'hourly', 'fqj_process_invalidation_queue' ); wp_schedule_single_event( time() + 60, 'fqj_data_migration' ); - $faq = self::factory()->post->create( array( 'post_type' => 'faq_item' ) ); + $faq = self::factory()->post->create( array( 'post_type' => 'fqj_faq' ) ); $this->post_ids[] = $faq; update_post_meta( $faq, 'fqj_assoc_type', 'urls' ); update_post_meta( $faq, 'fqj_assoc_data_json', '{}' ); @@ -115,19 +117,20 @@ public function test_uninstall_removes_log_queue_events_and_meta_but_keeps_faqs( } public function test_uninstall_deletes_faqs_when_filter_allows() { - $faq = self::factory()->post->create( array( 'post_type' => 'faq_item' ) ); + $faq = self::factory()->post->create( array( 'post_type' => 'fqj_faq' ) ); $draft = self::factory()->post->create( array( - 'post_type' => 'faq_item', + 'post_type' => 'fqj_faq', 'post_status' => 'draft', ) ); $trashed = self::factory()->post->create( array( - 'post_type' => 'faq_item', + 'post_type' => 'fqj_faq', 'post_status' => 'trash', ) ); + $legacy = self::factory()->post->create( array( 'post_type' => 'faq_item' ) ); $post = self::factory()->post->create(); $this->post_ids[] = $post; update_post_meta( $faq, 'fqj_assoc_type', 'urls' ); @@ -139,6 +142,7 @@ public function test_uninstall_deletes_faqs_when_filter_allows() { $this->assertNull( get_post( $faq ) ); $this->assertNull( get_post( $draft ) ); $this->assertNull( get_post( $trashed ) ); + $this->assertNull( get_post( $legacy ), 'FAQs never migrated off the legacy post type are deleted too.' ); $this->assertNotNull( get_post( $post ) ); } } diff --git a/tests/php/test-url-migration.php b/tests/php/test-url-migration.php index 00cc7e7..196cc0e 100644 --- a/tests/php/test-url-migration.php +++ b/tests/php/test-url-migration.php @@ -223,7 +223,7 @@ public function test_report_notice_shows_on_faq_list_until_dismissed() { $this->create_legacy_faq( array( home_url( '/?p=' . $post ) ), array( $post ) ); $this->migrate(); - set_current_screen( 'edit-faq_item' ); + set_current_screen( 'edit-fqj_faq' ); wp_set_current_user( $author ); ob_start(); fqj_url_migration_report_notice(); diff --git a/uninstall.php b/uninstall.php index b9a7af4..bfecec2 100644 --- a/uninstall.php +++ b/uninstall.php @@ -3,7 +3,7 @@ * Uninstall script for FAQ JSON-LD Manager. * * WARNING: This removes plugin options, scheduled events, transients, post meta and the mapping table - * permanently, on every site of a multisite network. The faq_item posts are kept unless the + * permanently, on every site of a multisite network. FAQ posts are kept unless the * `fqj_uninstall_delete_faqs` filter returns true. * * Plugin functions are not loaded during uninstall, so everything here is self-contained. @@ -22,7 +22,7 @@ global $wpdb; // Options. - foreach ( array( 'fqj_settings', 'fqj_db_version', 'fqj_data_version', 'fqj_data_migration_cursor', 'fqj_invalidation_log', 'fqj_last_queue_run', 'fqj_cache_gen', 'fqj_url_migration_report' ) as $option ) { + foreach ( array( 'fqj_settings', 'fqj_db_version', 'fqj_data_version', 'fqj_data_migration_cursor', 'fqj_invalidation_log', 'fqj_last_queue_run', 'fqj_cache_gen', 'fqj_url_migration_report', 'fqj_post_type' ) as $option ) { delete_option( $option ); } @@ -51,7 +51,7 @@ do { $faq_ids = get_posts( array( - 'post_type' => 'faq_item', + 'post_type' => array( 'fqj_faq', 'faq_item' ), // 'any' skips statuses excluded from search, such as trash. 'post_status' => array_keys( get_post_stati() ), 'posts_per_page' => 100, From c716dc6965d8f4f0003254e836b08d91732913d0 Mon Sep 17 00:00:00 2001 From: Soham Banerjee Date: Fri, 23 Oct 2026 15:29:29 +0530 Subject: [PATCH 2/6] Rename to Renderbit FAQ Structured Data with the rfsd prefix WordPress.org review: the name and slug were too generic, and prefixes must be at least 4 characters. Plugin name, slug and text domain become renderbit-faq-structured-data; every fqj_/FQJ_/fqj- name becomes rfsd_/RFSD_/rfsd-, including the CPT (rfsd_faq), tables, options, meta, hooks, public filters, AJAX actions, JS globals and the WP-CLI command (wp rfsd). The main file is renderbit-faq-structured-data.php. Co-Authored-By: Claude --- .cursor/Dockerfile | 2 +- .cursor/cloud-install.sh | 4 +- .cursor/environment.json | 2 +- .github/ISSUE_TEMPLATE/bug_report.yml | 4 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/copilot-instructions.md | 22 +- .github/workflows/deploy-wporg.yml | 8 +- .github/workflows/draft-release.yml | 2 +- .github/workflows/tests.yml | 8 +- AGENTS.md | 14 +- CLAUDE.md | 38 +-- CONTEXT.md | 2 +- CONTRIBUTING.md | 8 +- README.md | 32 +-- _config.yml | 2 +- assets/js/{fqj-admin.js => rfsd-admin.js} | 62 ++--- assets/js/{fqj-health.js => rfsd-health.js} | 38 +-- bin/bump-version.php | 72 ++--- bin/check-zip.sh | 10 +- composer.json | 10 +- composer.lock | 2 +- faq-jsonld.php | 231 --------------- includes/admin.php | 216 +++++++-------- .../{class-fqjcli.php => class-rfsd-cli.php} | 16 +- includes/coverage.php | 96 +++---- includes/frontend.php | 46 +-- includes/health.php | 164 +++++------ includes/indexer.php | 176 ++++++------ includes/queue.php | 116 ++++---- includes/settings.php | 108 ++++---- package-lock.json | 4 +- package.json | 4 +- phpcs.xml | 8 +- phpunit-multisite.xml.dist | 4 +- phpunit.xml.dist | 4 +- readme.txt | 24 +- renderbit-faq-structured-data.php | 231 +++++++++++++++ .../{fqj-admin.test.js => rfsd-admin.test.js} | 88 +++--- ...fqj-health.test.js => rfsd-health.test.js} | 70 ++--- tests/php/bootstrap.php | 10 +- ....php => class-rfsd-multisite-testcase.php} | 10 +- ...j-testcase.php => class-rfsd-testcase.php} | 18 +- tests/php/stubs/class-wp-cli.php | 2 +- tests/php/test-admin.php | 188 ++++++------- tests/php/test-ajax.php | 153 +++++----- tests/php/test-cli.php | 28 +- tests/php/test-coverage.php | 123 ++++---- tests/php/test-frontend.php | 82 +++--- tests/php/test-health.php | 50 ++-- tests/php/test-indexer.php | 206 +++++++------- tests/php/test-invalidation-hooks.php | 48 ++-- ...e-migration.php => test-legacy-import.php} | 54 ++-- tests/php/test-multisite-uninstall.php | 50 ++-- tests/php/test-multisite.php | 118 ++++---- tests/php/test-plugin.php | 156 ++++++----- tests/php/test-queue.php | 262 +++++++++--------- tests/php/test-settings.php | 114 ++++---- tests/php/test-uninstall.php | 101 ++++--- tests/php/test-url-migration.php | 64 ++--- tests/php/wp-tests-config.php | 2 +- uninstall.php | 40 +-- 61 files changed, 1944 insertions(+), 1885 deletions(-) rename assets/js/{fqj-admin.js => rfsd-admin.js} (80%) rename assets/js/{fqj-health.js => rfsd-health.js} (68%) delete mode 100644 faq-jsonld.php rename includes/{class-fqjcli.php => class-rfsd-cli.php} (80%) create mode 100644 renderbit-faq-structured-data.php rename tests/js/{fqj-admin.test.js => rfsd-admin.test.js} (73%) rename tests/js/{fqj-health.test.js => rfsd-health.test.js} (68%) rename tests/php/{class-fqj-multisite-testcase.php => class-rfsd-multisite-testcase.php} (84%) rename tests/php/{class-fqj-testcase.php => class-rfsd-testcase.php} (87%) rename tests/php/{test-post-type-migration.php => test-legacy-import.php} (58%) diff --git a/.cursor/Dockerfile b/.cursor/Dockerfile index 5898f3f..f8d431d 100644 --- a/.cursor/Dockerfile +++ b/.cursor/Dockerfile @@ -24,7 +24,7 @@ RUN printf '%s\n' \ 'export WP_TESTS_DB_USER=root' \ 'export WP_TESTS_DB_PASSWORD=root' \ 'export WP_TESTS_DB_HOST=127.0.0.1' \ - > /etc/profile.d/fqj-wp-tests-db.sh + > /etc/profile.d/rfsd-wp-tests-db.sh RUN echo 'ubuntu ALL=(ALL) NOPASSWD: /usr/sbin/service mariadb *' > /etc/sudoers.d/mariadb \ && chmod 440 /etc/sudoers.d/mariadb diff --git a/.cursor/cloud-install.sh b/.cursor/cloud-install.sh index 4deb9c2..603a325 100755 --- a/.cursor/cloud-install.sh +++ b/.cursor/cloud-install.sh @@ -4,8 +4,8 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" -if [[ ! -f /etc/profile.d/fqj-wp-tests-db.sh ]]; then - sudo tee /etc/profile.d/fqj-wp-tests-db.sh >/dev/null <<'EOF' +if [[ ! -f /etc/profile.d/rfsd-wp-tests-db.sh ]]; then + sudo tee /etc/profile.d/rfsd-wp-tests-db.sh >/dev/null <<'EOF' export WP_TESTS_DB_NAME=wordpress_test export WP_TESTS_DB_USER=root export WP_TESTS_DB_PASSWORD=root diff --git a/.cursor/environment.json b/.cursor/environment.json index 66ac950..3480ea8 100644 --- a/.cursor/environment.json +++ b/.cursor/environment.json @@ -1,5 +1,5 @@ { - "name": "FAQ JSON-LD WordPress Plugin", + "name": "Renderbit FAQ Structured Data WordPress Plugin", "build": { "dockerfile": "Dockerfile", "context": "." diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3f5d898..3ce993e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,5 +1,5 @@ name: Bug Report -description: Report a bug or issue with the FAQ JSON-LD Manager plugin +description: Report a bug or issue with the Renderbit FAQ Structured Data plugin title: "[Bug]: " labels: ["bug"] body: @@ -12,7 +12,7 @@ body: id: version attributes: label: Plugin Version - description: What version of FAQ JSON-LD Manager are you using? + description: What version of Renderbit FAQ Structured Data are you using? placeholder: e.g., 3.0.0 validations: required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9776822..9ac1f67 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,5 +1,5 @@ name: Feature Request -description: Suggest a new feature or improvement for the FAQ JSON-LD Manager plugin +description: Suggest a new feature or improvement for the Renderbit FAQ Structured Data plugin title: "[Feature]: " labels: ["enhancement"] body: diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index bbe5c74..1346777 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,4 +1,4 @@ -# Copilot Instructions for FAQ JSON-LD WordPress Plugin +# Copilot Instructions for Renderbit FAQ Structured Data WordPress Plugin ## Overview @@ -6,20 +6,20 @@ This plugin manages FAQ items as a custom post type (CPT) and injects FAQ JSON-L ## Architecture & Key Components -- **Custom Post Type:** `fqj_faq` (formerly `faq_item`) is registered for FAQ entries. -- **Mapping Table:** MySQL table (`fqj_mappings`) links FAQ items to posts, post types, terms, URLs, or global context. See `includes/indexer.php`. +- **Custom Post Type:** `rfsd_faq` (formerly `faq_item`) is registered for FAQ entries. +- **Mapping Table:** MySQL table (`rfsd_mappings`) links FAQ items to posts, post types, terms, URLs, or global context. See `includes/indexer.php`. - **JSON-LD Output:** Injected on the frontend using `includes/frontend.php`, with per-post transient caching for performance. - **Cache Invalidation:** Uses a background queue (`includes/queue.php`) processed by WP-Cron or WP-CLI. Queue state and logs are visible in the Health admin page. -- **Admin UI:** Custom meta boxes and settings in `includes/admin.php` and `includes/settings.php`. Select2 (bundled in `assets/vendor/select2`) is used for post selectors (see `assets/js/fqj-admin.js`). +- **Admin UI:** Custom meta boxes and settings in `includes/admin.php` and `includes/settings.php`. Select2 (bundled in `assets/vendor/select2`) is used for post selectors (see `assets/js/rfsd-admin.js`). - **Health & Diagnostics:** Admin page (`includes/health.php`) shows queue status, logs, and provides manual actions. -- **WP-CLI Integration:** `includes/class-fqjcli.php` provides commands for purging transients, processing the invalidation queue and showing its length. +- **WP-CLI Integration:** `includes/class-rfsd-cli.php` provides commands for purging transients, processing the invalidation queue and showing its length. ## Developer Workflows - **Activate/Upgrade:** On plugin activation, the mapping table is created/updated automatically. - **Settings:** Accessible under the FAQ Items menu. Options include cache TTL, batch size, and output type. - **Cache Management:** - - Manual: Use Health admin page or WP-CLI (`wp fqj purge-transients`, `wp fqj process-queue [--limit=N]`, `wp fqj queue-info`). + - Manual: Use Health admin page or WP-CLI (`wp rfsd purge-transients`, `wp rfsd process-queue [--limit=N]`, `wp rfsd queue-info`). - Automatic: Invalidation queue is processed every 5 minutes by WP-Cron. - **Debugging:** - Check the Health admin page for queue/logs. @@ -28,8 +28,8 @@ This plugin manages FAQ items as a custom post type (CPT) and injects FAQ JSON-L ## Project-Specific Patterns - **Mapping Structure:** All associations (post, post_type, term, url, global) are stored in a single table for fast lookups. -- **Transient Keys:** Format is `fqj_faq_json_{generation}_{post_id}`; always build it with `fqj_cache_key( $post_id )`. The mapping table name comes from `fqj_table_name()`. -- **Admin JS:** Uses Select2 for post selectors, AJAX for search (`fqj_search_posts` action). +- **Transient Keys:** Format is `rfsd_faq_json_{generation}_{post_id}`; always build it with `rfsd_cache_key( $post_id )`. The mapping table name comes from `rfsd_table_name()`. +- **Admin JS:** Uses Select2 for post selectors, AJAX for search (`rfsd_search_posts` action). - **Settings/Health Pages:** Registered as submenus under the FAQ Items CPT. ## Integration Points @@ -40,15 +40,15 @@ This plugin manages FAQ items as a custom post type (CPT) and injects FAQ JSON-L ## Key Files -- `faq-jsonld.php`: Main plugin loader and CPT registration. +- `renderbit-faq-structured-data.php`: Main plugin loader and CPT registration. - `includes/indexer.php`: Mapping table logic. - `includes/queue.php`: Invalidation queue logic. - `includes/frontend.php`: JSON-LD output and caching. - `includes/admin.php`: Admin UI/meta boxes. - `includes/settings.php`: Settings page. - `includes/health.php`: Health/diagnostics UI. -- `includes/class-fqjcli.php`: WP-CLI commands. -- `assets/js/fqj-admin.js`, `assets/js/fqj-health.js`: Admin JS (Select2, AJAX). +- `includes/class-rfsd-cli.php`: WP-CLI commands. +- `assets/js/rfsd-admin.js`, `assets/js/rfsd-health.js`: Admin JS (Select2, AJAX). ## Example: Adding a New Mapping Type diff --git a/.github/workflows/deploy-wporg.yml b/.github/workflows/deploy-wporg.yml index 7a4606c..eddb6f0 100644 --- a/.github/workflows/deploy-wporg.yml +++ b/.github/workflows/deploy-wporg.yml @@ -22,10 +22,10 @@ jobs: run: | tag="${GITHUB_REF#refs/tags/}" tag="${tag#v}" - header="$(sed -n 's/^ \* Version: *//p' faq-jsonld.php)" - constant="$(sed -n "s/^define( 'FQJ_VERSION', '\(.*\)' );$/\1/p" faq-jsonld.php)" + header="$(sed -n 's/^ \* Version: *//p' renderbit-faq-structured-data.php)" + constant="$(sed -n "s/^define( 'RFSD_VERSION', '\(.*\)' );$/\1/p" renderbit-faq-structured-data.php)" stable="$(sed -n 's/^Stable tag: *//p' readme.txt)" - echo "tag=$tag header=$header FQJ_VERSION=$constant stable=$stable" + echo "tag=$tag header=$header RFSD_VERSION=$constant stable=$stable" [ "$tag" = "$header" ] && [ "$tag" = "$constant" ] && [ "$tag" = "$stable" ] - name: Deploy to WordPress.org SVN @@ -33,4 +33,4 @@ jobs: env: SVN_USERNAME: ${{ secrets.SVN_USERNAME }} SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} - SLUG: faq-jsonld + SLUG: renderbit-faq-structured-data diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 7006952..bf6ff74 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -40,6 +40,6 @@ jobs: name: Release ${{ env.TAG_NAME }} draft: true generate_release_notes: true - files: faq-jsonld.zip + files: renderbit-faq-structured-data.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5eee2dd..ee3dd46 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,7 +32,7 @@ jobs: tools: none - name: Lint plugin files - run: find faq-jsonld.php uninstall.php includes -name '*.php' -print0 | xargs -0 -n1 php -l + run: find renderbit-faq-structured-data.php uninstall.php includes -name '*.php' -print0 | xargs -0 -n1 php -l phpcs: name: Coding standards @@ -59,12 +59,12 @@ jobs: # Check what ships (the git archive, as in the release zip and WordPress.org SVN), not the dev files. - name: Export plugin files - run: mkdir -p build/faq-jsonld && git archive HEAD | tar -x -C build/faq-jsonld + run: mkdir -p build/renderbit-faq-structured-data && git archive HEAD | tar -x -C build/renderbit-faq-structured-data - uses: WordPress/plugin-check-action@10857da14b6c2246d15402b3e69f777edcf8c12e # v1.1.9 with: - build-dir: build/faq-jsonld - slug: faq-jsonld + build-dir: build/renderbit-faq-structured-data + slug: renderbit-faq-structured-data strict: true phpunit: diff --git a/AGENTS.md b/AGENTS.md index 1fcf18e..b5e3f0a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,8 @@ # AGENTS.md -WordPress plugin `faq-jsonld`: FAQs as a `fqj_faq` CPT (`FQJ_POST_TYPE`; renamed from `faq_item` on upgrade), schema.org `FAQPage` JSON-LD printed on singular posts/pages. Procedural PHP, every function prefixed `fqj_`, no build step, no runtime Composer deps. +WordPress plugin `renderbit-faq-structured-data`: FAQs as a `rfsd_faq` CPT (`RFSD_POST_TYPE`; renamed from `faq_item` on upgrade), schema.org `FAQPage` JSON-LD printed on singular posts/pages. Procedural PHP, every function prefixed `rfsd_`, no build step, no runtime Composer deps. -**PHP ceiling is 7.2 for shipped code** — `composer.json` pins platform 7.4, so syntax up to 7.4 passes locally while CI's `php -l` matrix (7.2–8.4) fails. Write 7.2-compatible syntax in `faq-jsonld.php`, `uninstall.php`, `includes/`. +**PHP ceiling is 7.2 for shipped code** — `composer.json` pins platform 7.4, so syntax up to 7.4 passes locally while CI's `php -l` matrix (7.2–8.4) fails. Write 7.2-compatible syntax in `renderbit-faq-structured-data.php`, `uninstall.php`, `includes/`. Full architecture, data flow (save → index → render → invalidate), and versioning rules live in `CLAUDE.md` — read its Architecture section before touching `includes/indexer.php`, `includes/queue.php`, `includes/frontend.php`, or cache keys. Keep `CLAUDE.md` in sync if you change behavior it describes. @@ -11,13 +11,13 @@ Full architecture, data flow (save → index → render → invalidate), and ver ```bash composer install && npm install # setup; .env.testing must exist (copy .env.testing.example) -composer lint # phpcs, WordPress Coding Standards, text domain faq-jsonld +composer lint # phpcs, WordPress Coding Standards, text domain renderbit-faq-structured-data composer test # PHPUnit, single-site composer test:multisite # same suite as a multisite network vendor/bin/phpunit --filter test_name # one test vendor/bin/phpunit tests/php/test-queue.php # one file npm test # Jest (jsdom) for assets/js -npx jest tests/js/fqj-admin.test.js -t "name" # one JS test +npx jest tests/js/rfsd-admin.test.js -t "name" # one JS test ``` CI (`.github/workflows/tests.yml`) runs all of the above plus `WordPress/plugin-check` on every PR. Verify with `composer lint && composer test && npm test` before claiming done. @@ -29,6 +29,6 @@ CI (`.github/workflows/tests.yml`) runs all of the above plus `WordPress/plugin- - Test groups: network-only tests are tagged `@group ms-required`, single-site-only `@group ms-excluded`; each phpunit config excludes the other group, so a test in neither group runs twice and one in both runs never. - `composer build-zip` runs `git archive HEAD` — **uncommitted changes are not in the zip**. `composer check-zip` verifies contents against `.gitattributes` `export-ignore`, which also decides what ships to WordPress.org SVN. Add new dev-only files there. - Raw `$wpdb` queries use `%i` for table identifiers and carry targeted `phpcs:ignore`/`phpcs:disable … phpcs:enable` comments naming the specific sniffs — keep this pattern so `composer lint` stays clean. -- Two independent version constants: bump `FQJ_DB_VERSION` only when `CREATE TABLE` SQL in `fqj_create_table()` changes (dbDelta rules: no `IF NOT EXISTS`, two spaces after `PRIMARY KEY`, `KEY` not `INDEX`, no inline comments); bump `FQJ_DATA_VERSION` only to force a one-time reindex + cache purge. -- Transient keys are always built with `fqj_cache_key( $post_id )` (embeds `fqj_cache_gen`) — never hand-build `fqj_faq_json_*` keys, or purge-all breaks with a persistent object cache. -- Releasing: `composer bump-patch|minor|major` keeps four version strings in sync (`composer.json`, plugin header, `FQJ_VERSION`, `readme.txt` Stable tag); add the `readme.txt` changelog entry by hand. Pushing a `vX.Y.Z` tag to `main` creates a draft release; publishing it deploys to WordPress.org SVN. +- Two independent version constants: bump `RFSD_DB_VERSION` only when `CREATE TABLE` SQL in `rfsd_create_table()` changes (dbDelta rules: no `IF NOT EXISTS`, two spaces after `PRIMARY KEY`, `KEY` not `INDEX`, no inline comments); bump `RFSD_DATA_VERSION` only to force a one-time reindex + cache purge. +- Transient keys are always built with `rfsd_cache_key( $post_id )` (embeds `rfsd_cache_gen`) — never hand-build `rfsd_faq_json_*` keys, or purge-all breaks with a persistent object cache. +- Releasing: `composer bump-patch|minor|major` keeps four version strings in sync (`composer.json`, plugin header, `RFSD_VERSION`, `readme.txt` Stable tag); add the `readme.txt` changelog entry by hand. Pushing a `vX.Y.Z` tag to `main` creates a draft release; publishing it deploys to WordPress.org SVN. diff --git a/CLAUDE.md b/CLAUDE.md index 59623b6..1c3a788 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,61 +2,61 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -WordPress plugin (slug `faq-jsonld`, published on WordPress.org) that manages FAQs as a `fqj_faq` custom post type (`FQJ_POST_TYPE`) and prints schema.org `FAQPage` JSON-LD in `wp_head` on the singular posts/pages each FAQ is mapped to. Procedural PHP, every function prefixed `fqj_`; no build step and no runtime Composer dependencies. Supports WordPress 6.5+ and PHP 7.2+, so don't use syntax newer than PHP 7.2 in shipped code. +WordPress plugin (slug `renderbit-faq-structured-data`, published on WordPress.org) that manages FAQs as a `rfsd_faq` custom post type (`RFSD_POST_TYPE`) and prints schema.org `FAQPage` JSON-LD in `wp_head` on the singular posts/pages each FAQ is mapped to. Procedural PHP, every function prefixed `rfsd_`; no build step and no runtime Composer dependencies. Supports WordPress 6.5+ and PHP 7.2+, so don't use syntax newer than PHP 7.2 in shipped code. ## Commands ```bash composer install && npm install -composer lint # phpcs with WordPress Coding Standards (phpcs.xml), text domain faq-jsonld +composer lint # phpcs with WordPress Coding Standards (phpcs.xml), text domain renderbit-faq-structured-data composer test # PHPUnit integration suite, single-site composer test:multisite # same suite as a multisite network vendor/bin/phpunit --filter test_name_here # one test vendor/bin/phpunit tests/php/test-queue.php # one file npm test # Jest (jsdom) tests for assets/js -npx jest tests/js/fqj-admin.test.js -t "name" # one JS test +npx jest tests/js/rfsd-admin.test.js -t "name" # one JS test composer build-zip && composer check-zip # release zip from `git archive HEAD`, then verify contents composer bump-patch # or bump-minor / bump-major ``` -PHPUnit runs against a real MySQL/MariaDB database that the suite **wipes on every run**. Settings come from `.env.testing` (copy `.env.testing.example`); shell env vars win. Tests are loaded by `tests/php/bootstrap.php`, which loads the plugin on `muplugins_loaded` and calls `fqj_activate()` on `init`. Extend `FQJ_TestCase` (`tests/php/class-fqj-testcase.php`, has `create_faq()` helpers) or `FQJ_Multisite_TestCase`. Tag network-only tests `@group ms-required` and single-site-only tests `@group ms-excluded`; each config excludes the other group. PHPUnit converts notices, warnings and deprecations to failures. WP-CLI is stubbed in `tests/php/stubs/class-wp-cli.php`. +PHPUnit runs against a real MySQL/MariaDB database that the suite **wipes on every run**. Settings come from `.env.testing` (copy `.env.testing.example`); shell env vars win. Tests are loaded by `tests/php/bootstrap.php`, which loads the plugin on `muplugins_loaded` and calls `rfsd_activate()` on `init`. Extend `RFSD_TestCase` (`tests/php/class-rfsd-testcase.php`, has `create_faq()` helpers) or `RFSD_Multisite_TestCase`. Tag network-only tests `@group ms-required` and single-site-only tests `@group ms-excluded`; each config excludes the other group. PHPUnit converts notices, warnings and deprecations to failures. WP-CLI is stubbed in `tests/php/stubs/class-wp-cli.php`. ## Architecture -`faq-jsonld.php` defines constants, requires every file in `includes/`, registers the CPT (renaming FAQs stored under a post type in `FQJ_LEGACY_POST_TYPES` once per site, on `plugins_loaded` priority 5 and on activation, before the schema upgrade and data migration), and handles activation/deactivation (looping over all sites via `fqj_for_each_site()` on network activation and setting up new sites on `wp_initialize_site`). +`renderbit-faq-structured-data.php` defines constants, requires every file in `includes/`, registers the CPT (renaming FAQs stored under a post type in `RFSD_LEGACY_POST_TYPES` once per site, on `plugins_loaded` priority 5 and on activation, before the schema upgrade and data migration), and handles activation/deactivation (looping over all sites via `rfsd_for_each_site()` on network activation and setting up new sites on `wp_initialize_site`). Data flow: -1. **Save (`includes/admin.php`)**: the meta box stores the FAQ's associations as JSON in post meta `fqj_assoc_data_json` (keys `urls`, `posts`, `post_types`, `terms`, `global`), then the index is rebuilt. -2. **Index (`includes/indexer.php`)**: `fqj_rebuild_index_for_faq()` replaces the FAQ's rows in the `{prefix}fqj_mappings` table (`faq_id`, `mapping_type` ∈ post/post_type/term/url/global, `mapping_value`). URLs go through `fqj_normalize_url()`, which stores URLs on this site relative to the site root (`/about`, `/`, `/?p=12`). A URL association covers whichever post's permalink equals that URL now, so it never gets a `post` row (ADR-0001). Old and new mappings are both passed to `fqj_enqueue_invalidation_for_mappings()`. Hooks on status transitions, post updates, deletes, term changes and permalink changes keep the index and cache in sync. -3. **Render (`includes/frontend.php`)**: on `is_singular()` only, builds candidate (type, value) pairs for the current post (its ID, post type, terms in all its taxonomies, normalized permalink, global; the legacy absolute and trailing-slash URL shapes only while data version is below 3), runs one query against the mapping table, and caches the `' ); + set_transient( rfsd_cache_key( $post ), '' ); $html = $this->render_head_for( $post ); $this->assertStringContainsString( '(cached)', $html ); @@ -97,21 +97,21 @@ public function test_cached_output_is_served_from_transient() { public function test_blank_cached_value_reports_unknown_state() { $post = self::factory()->post->create(); - set_transient( fqj_cache_key( $post ), ' ' ); + set_transient( rfsd_cache_key( $post ), ' ' ); $this->assertStringContainsString( 'cached, unknown state', $this->render_head_for( $post ) ); } public function test_generated_output_is_cached_with_ttl() { - update_option( FQJ_OPTION_KEY, array( 'cache_ttl' => 600 ) ); + update_option( RFSD_OPTION_KEY, array( 'cache_ttl' => 600 ) ); $post = self::factory()->post->create(); $this->create_faq( 'Q', 'A', array( 'posts' => array( $post ) ) ); $html = $this->render_head_for( $post ); - $cached = get_transient( fqj_cache_key( $post ) ); + $cached = get_transient( rfsd_cache_key( $post ) ); $this->assertStringStartsWith( '