diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f6faee6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..1ead85e --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,41 @@ +name: Code Scanning + +on: + pull_request: + push: + branches: + - main + schedule: + - cron: "37 8 * * 1" + +permissions: + contents: read + security-events: write + +jobs: + codeql: + name: CodeQL (JavaScript) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: github/codeql-action/init@v4 + with: + languages: javascript-typescript + + - uses: github/codeql-action/analyze@v4 + + semgrep-php: + name: Semgrep (PHP) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - run: python -m pip install --upgrade semgrep + + - run: semgrep scan --config p/php --sarif --output semgrep.sarif plugin/waypoints + + - uses: github/codeql-action/upload-sarif@v4 + if: ${{ always() }} + with: + sarif_file: semgrep.sarif diff --git a/.github/workflows/plugin-quality.yml b/.github/workflows/plugin-quality.yml index 4079a16..1dcd2c8 100644 --- a/.github/workflows/plugin-quality.yml +++ b/.github/workflows/plugin-quality.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: plugin/plan-your-day + working-directory: plugin/waypoints steps: - uses: actions/checkout@v4 @@ -22,7 +22,7 @@ jobs: - uses: ramsey/composer-install@v3 with: - working-directory: plugin/plan-your-day + working-directory: plugin/waypoints - run: composer lint @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: plugin/plan-your-day + working-directory: plugin/waypoints steps: - uses: actions/checkout@v4 @@ -41,7 +41,7 @@ jobs: - uses: ramsey/composer-install@v3 with: - working-directory: plugin/plan-your-day + working-directory: plugin/waypoints - run: composer phpcs @@ -49,7 +49,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: plugin/plan-your-day + working-directory: plugin/waypoints steps: - uses: actions/checkout@v4 @@ -60,7 +60,7 @@ jobs: - uses: ramsey/composer-install@v3 with: - working-directory: plugin/plan-your-day + working-directory: plugin/waypoints - run: composer phpunit @@ -76,7 +76,7 @@ jobs: - uses: ramsey/composer-install@v3 with: - working-directory: plugin/plan-your-day + working-directory: plugin/waypoints - uses: actions/setup-node@v4 with: @@ -101,7 +101,7 @@ jobs: - uses: ramsey/composer-install@v3 with: - working-directory: plugin/plan-your-day + working-directory: plugin/waypoints composer-options: "--no-dev --optimize-autoloader" - uses: actions/setup-node@v4 @@ -114,7 +114,7 @@ jobs: run: | trap 'status=$?; if [ "$status" -ne 0 ]; then docker ps -a || true; find "$WP_ENV_HOME" -maxdepth 3 -type f -print || true; fi; exit "$status"' EXIT - plugin_dir="$(realpath ./plugin/plan-your-day)" + plugin_dir="$(realpath ./plugin/waypoints)" cat > .wp-env.json <&2 + exit 1 + fi + + echo "artifact=$artifact" >> "$GITHUB_OUTPUT" + + - name: Unpack release zip + id: unpack + run: | + unpack_dir="$RUNNER_TEMP/wp-submission-package" + rm -rf "$unpack_dir" + mkdir -p "$unpack_dir" + unzip -q "${{ steps.build.outputs.artifact }}" -d "$unpack_dir" + + mapfile -t roots < <(find "$unpack_dir" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort) + if [ "${#roots[@]}" -ne 1 ]; then + printf 'Release zip must contain exactly one top-level plugin directory. Found: %s\n' "${roots[*]}" >&2 + exit 1 + fi + + echo "dir=$unpack_dir/${roots[0]}" >> "$GITHUB_OUTPUT" + echo "slug=${roots[0]}" >> "$GITHUB_OUTPUT" + + - name: Run WordPress Plugin Check against artifact + run: | + export WP_ENV_HOME="$RUNNER_TEMP/wp-env" + trap 'status=$?; if [ "$status" -ne 0 ]; then docker ps -a || true; find "$WP_ENV_HOME" -maxdepth 3 -type f -print || true; fi; exit "$status"' EXIT + + plugin_dir="${{ steps.unpack.outputs.dir }}" + plugin_slug="${{ steps.unpack.outputs.slug }}" + + cat > .wp-env.json < "$wp_env_tools/package.json" <<'EOF' + { + "private": true, + "dependencies": { + "@wordpress/env": "11.5.0" + }, + "overrides": { + "rimraf": { + "glob": "^13.0.6" + } + } + } + EOF + + npm --prefix "$wp_env_tools" --no-audit --no-fund install + export PATH="$wp_env_tools/node_modules/.bin:$PATH" + + wp-env start --update + plugin_name="$(wp-env run cli wp plugin list --field=name | tr -d '\r' | grep -E "^${plugin_slug}(/|$)" | head -n1)" + if [ -z "$plugin_name" ]; then + echo "Unable to find packaged plugin in WordPress plugin list." >&2 + wp-env run cli wp plugin list + exit 1 + fi + + wp-env run cli wp plugin activate "$plugin_name" + + set +e + wp-env run cli wp plugin check "$plugin_name" \ + --format=json \ + --ignore-warnings \ + --require=./wp-content/plugins/plugin-check/cli.php \ + > "$RUNNER_TEMP/plugin-check-results.txt" + status=$? + set -e + + cat "$RUNNER_TEMP/plugin-check-results.txt" + if grep -Eq '"type"[[:space:]]*:[[:space:]]*"ERROR"' "$RUNNER_TEMP/plugin-check-results.txt"; then + exit 1 + fi + + exit "$status" + + - name: Run submission metadata scan + working-directory: ${{ env.PLUGIN_DIR }} + run: | + php tools/check-wp-submission-readiness.php \ + --plugin-dir=. \ + --artifact="${{ steps.build.outputs.artifact }}" \ + --expected-name="$EXPECTED_NAME" \ + --expected-slug="$EXPECTED_SLUG" + + - uses: actions/upload-artifact@v4 + if: ${{ always() }} + with: + name: wp-submission-package + path: ${{ steps.build.outputs.artifact }} + if-no-files-found: ignore + + - uses: actions/upload-artifact@v4 + if: ${{ always() }} + with: + name: plugin-check-results + path: ${{ runner.temp }}/plugin-check-results.txt + if-no-files-found: ignore + + - name: Stop wp-env + if: ${{ always() }} + run: | + export PATH="$RUNNER_TEMP/wp-env-tools/node_modules/.bin:$PATH" + if command -v wp-env >/dev/null 2>&1; then + wp-env destroy --force || true + fi diff --git a/.gitignore b/.gitignore index 2589387..2be6490 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # Runtime/session/cache output that should not be committed. *.log *.cache +readme.html +README.html dist/ tmp/ temp/ @@ -15,6 +17,6 @@ Thumbs.db # Dependency folders if tooling is added later. node_modules/ vendor/ -plugin/plan-your-day/.phpunit.cache/ +plugin/waypoints/.phpunit.cache/ playwright-report/ test-results/ diff --git a/AGENTS.md b/AGENTS.md index 984690e..855eadc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,13 +2,13 @@ ## Project -This repository contains the standalone **Plan Your Day** WordPress plugin. The -plugin source lives in `plugin/plan-your-day/`. +This repository contains the standalone **Waypoints** WordPress plugin. The +plugin source lives in `plugin/waypoints/`. -Plan Your Day is a reusable plugin. It must not be branded to, named after, or architecturally tied to any specific client/site. +Waypoints is a reusable plugin. It must not be branded to, named after, or architecturally tied to any specific client/site. -The latest published release is v0.5. The project is preparing for v1.0 with -focused public-release hardening and cleanup. +The latest published release is v1.0. The project is in public-release +hardening and cleanup. ## Hard rules @@ -42,7 +42,7 @@ The plugin currently supports: - admin-editable interface copy where the setting is still useful, - admin-editable/custom categories with draggable ordering. -## Current v1.0 direction +## Current Release-Hardening Direction Prioritize release readiness over new feature breadth. Current hardening lanes include: @@ -53,9 +53,10 @@ include: - Google geocode failure handling. - Stable, quiet WordPress Plugin Check workflow coverage. -PHPStan is not part of the current v1.0 gate. A one-off scan without WordPress -stubs reports missing WordPress symbols, so do not add PHPStan to CI unless the -project also adds reproducible WordPress-aware tooling. +PHPStan is available through repo-local Composer tooling and is included in the +stricter WordPress.org submission-readiness workflow. Keep the regular `Plugin +Quality` workflow focused on PHP syntax, PHPCS, PHPUnit, browser smoke, and +WordPress Plugin Check unless there is a clear reason to broaden it. ## UI/accessibility expectations @@ -85,9 +86,9 @@ project also adds reproducible WordPress-aware tooling. Before finishing a code task, choose checks that match the change. Common checks include: -- `cd plugin/plan-your-day && composer test` +- `cd plugin/waypoints && composer test` - `npm ci && npx playwright install chromium && npm run browser-smoke` -- `find plugin/plan-your-day -name '*.php' -print -exec php -l {} \;` +- `find plugin/waypoints -name '*.php' -print -exec php -l {} \;` - GitHub `Plugin Quality` workflow for PHP syntax, PHPCS, PHPUnit, browser smoke, and WordPress Plugin Check. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md index 920e371..201e610 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,12 @@ Waypoints is a configurable WordPress plugin for building day-trip planners with Google Maps and Places data. The plugin source lives in -`plugin/plan-your-day/`. +`plugin/waypoints/`. ## Status -The latest published release is **v0.5**. The project is in v1.0 release -preparation, with UI polish and final hardening work happening in focused pull -requests. +The latest published release is **v1.0**. The v1.0 milestone completes the MVP +release-readiness pass for a reusable WordPress.org submission candidate. Completed foundation work includes: @@ -40,7 +39,7 @@ Completed foundation work includes: - GitHub Actions quality checks for PHP syntax, PHPCS, PHPUnit, browser smoke, and WordPress Plugin Check. -Current v1.0 hardening work is split into open PRs for: +The v1.0 hardening pass included: - Removing bundled icon assets in favor of CSS-only UI details. - Making REST token bootstrap cache behavior safer. @@ -48,9 +47,10 @@ Current v1.0 hardening work is split into open PRs for: - Hardening Google geocode failure handling. - Keeping the Plugin Check workflow stable and quiet. -PHPStan is not part of the current v1.0 gate. A one-off scan without WordPress -stubs only reports missing WordPress symbols, so the project is relying on the -checks above for this release. +PHPStan is available through the repo-local Composer tooling and is included in +the stricter WordPress.org submission-readiness scan. The regular `Plugin +Quality` workflow remains focused on PHP syntax, PHPCS, PHPUnit, browser smoke, +and WordPress Plugin Check. ## Requirements @@ -62,24 +62,34 @@ checks above for this release. ## Repository Layout -- `plugin/plan-your-day/` contains the WordPress plugin source. -- `plugin/plan-your-day/plan-your-day.php` is the main plugin file. -- `plugin/plan-your-day/src/` contains namespaced plugin classes. -- `plugin/plan-your-day/src/Settings/` contains option defaults and +- `plugin/waypoints/` contains the WordPress plugin source. +- The root PHP bootstrap file contains the WordPress plugin headers. +- `plugin/waypoints/src/` contains namespaced plugin classes. +- `plugin/waypoints/src/Settings/` contains option defaults and sanitization. -- `plugin/plan-your-day/src/Admin/` contains the settings UI. -- `plugin/plan-your-day/src/Google/` contains Google API client and cache +- `plugin/waypoints/src/Admin/` contains the settings UI. +- `plugin/waypoints/src/Google/` contains Google API client and cache classes. - `docs/` contains installation, usage, admin, release, architecture, settings, security, troubleshooting, and historical planning notes for the plugin. ## Naming Note -The public plugin name is **Waypoints**. The source still uses `plan-your-day` -for the plugin directory and text domain, `[plan_your_day]` for the shortcode, -`plan_your_day_*` for settings/options/hooks, `Acodebeard\PlanYourDay` for the -PHP namespace, and related REST, asset, and CSS identifiers because the plugin -was renamed after those compatibility surfaces already existed. +The public plugin name is **Waypoints**. The source directory, text domain, +REST namespace, block name, and preferred shortcode now use `waypoints`. Some +legacy compatibility surfaces still use identifiers from the original name: +`[plan_your_day]` remains as a shortcode alias, `plan_your_day_*` remains for +settings/options/hooks, `Acodebeard\PlanYourDay` remains the PHP namespace, and +some asset/CSS identifiers remain unchanged to avoid unnecessary migration +risk. + +## Credits + +Special thanks to [Hagan Franks](https://github.com/hagan) and +[Christopher Reaume](https://github.com/datapoke) for development help. + +Thanks also to [Destination Kona Coast](https://destinationkonacoast.com) for +the idea that started the project. ## Documentation @@ -87,10 +97,14 @@ Start with [docs/README.md](docs/README.md). Current docs cover installation, frontend usage, admin workflows, release steps, architecture, settings, security, and troubleshooting. +## License + +Waypoints is licensed under GPLv2 or later. See [LICENSE](LICENSE). + ## Local Source Installation -1. Copy or symlink `plugin/plan-your-day/` into a WordPress installation at - `wp-content/plugins/plan-your-day/`. +1. Copy or symlink `plugin/waypoints/` into a WordPress installation at + `wp-content/plugins/waypoints/`. 2. From the plugin directory, install the Composer autoloader: ```sh @@ -106,24 +120,35 @@ sites do not need to run Composer. ## Build Release Zip -From `plugin/plan-your-day/`, build an installable WordPress admin zip with: +From `plugin/waypoints/`, build an installable WordPress admin zip with: ```sh ./tools/build-release-zip.sh ``` -The script creates `dist/plan-your-day-0.5.zip` at the repository root, +The script creates `dist/waypoints-1.0.zip` at the repository root, installs production-only Composer autoload files into a temporary staging copy, -and packages the final artifact with a top-level `plan-your-day/` directory +and packages the final artifact with a top-level `waypoints/` directory suitable for **Plugins > Add New > Upload Plugin**. See [docs/RELEASES.md](docs/RELEASES.md) for the full manual GitHub release workflow and the metadata that needs to stay aligned. +## WordPress.org Submission Readiness + +The GitHub Actions workflow `WP Submission Readiness` is the go-to release +candidate scan before submitting to WordPress.org. It is reusable through +`workflow_call` and can also be run manually from the Actions tab. + +The scan builds the release zip, runs the normal PHP checks, PHPStan, browser +smoke coverage, WordPress Plugin Check against the packaged artifact, and a +repo-local metadata check. The metadata check intentionally expects **Waypoints** +to use the permanent WordPress.org slug and text domain `waypoints`. + ## Configuration -Plugin settings are stored in the `plan_your_day_settings` option and are -managed through the WordPress Settings API. Current settings include: +Plugin settings are stored in a single WordPress option and are managed through +the WordPress Settings API. Current settings include: - Default location label, address/search phrase, latitude, longitude, and Place ID. @@ -147,8 +172,8 @@ Server-side Google API keys must not be exposed to frontend runtime config. - Do not rely on PHP sessions in plugin code. - Use WordPress-native APIs for escaping, sanitization, options, REST, transients/object cache, HTTP requests, and asset registration. -- Treat `docs/PLAN-YOUR-DAY-PLUGIN-TODO.md` and - `docs/PLAN-YOUR-DAY-PLUGIN-ISSUES.md` as historical planning notes unless +- Treat `docs/WAYPOINTS-PLUGIN-TODO.md` and + `docs/WAYPOINTS-PLUGIN-ISSUES.md` as historical planning notes unless they are explicitly refreshed. ## Useful Checks @@ -156,7 +181,7 @@ Server-side Google API keys must not be exposed to frontend runtime config. Run the PHP checks from the plugin directory: ```sh -cd plugin/plan-your-day +cd plugin/waypoints composer test ``` @@ -171,13 +196,13 @@ npm run browser-smoke Run a quick PHP syntax-only sweep from the repository root: ```sh -find plugin/plan-your-day -name '*.php' -print -exec php -l {} \; +find plugin/waypoints -name '*.php' -print -exec php -l {} \; ``` Search for legacy integration-specific strings in plugin code: ```sh -rg "localhost|example\\.test|Kona|pier" plugin/plan-your-day +rg "localhost|example\\.test|Kona|pier" plugin/waypoints ``` The plugin PHPUnit suite covers current service, settings, and activation diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..c8794f6 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report suspected vulnerabilities through GitHub Private Vulnerability +Reporting for this repository: + +https://github.com/acodebeard/Waypoints/security/advisories/new + +Do not open a public Issue for vulnerability details. Include the affected +version or commit, reproduction steps, impact, and any relevant logs or proof of +concept details. + +## Supported Versions + +Security fixes are prioritized for the latest published release and the current +default branch. + +## Security Notes + +The implementation security model is documented in `docs/SECURITY.md`. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 6c687f2..259ae52 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -8,7 +8,7 @@ planners with Google Maps and Places data. The main plugin file is: ```text -plugin/plan-your-day/plan-your-day.php +plugin/waypoints/root PHP bootstrap file ``` It defines plugin constants, requires `vendor/autoload.php`, registers @@ -19,7 +19,7 @@ activation/deactivation hooks, and boots `Acodebeard\PlanYourDay\Plugin`. Current plugin source lives under: ```text -plugin/plan-your-day/src/ +plugin/waypoints/src/ ``` Implemented layers: @@ -57,7 +57,7 @@ controls, and a Google API cache clear tool. Editable frontend copy defaults and field metadata live in: ```text -plugin/plan-your-day/src/Frontend/InterfaceCopy.php +plugin/waypoints/src/Frontend/InterfaceCopy.php ``` `Settings` stores those values inside the main option under `interface_copy`, @@ -132,7 +132,7 @@ Each row contains: Built-in starter rows for fresh installs live in: ```text -plugin/plan-your-day/src/Planner/CategoryCatalog.php +plugin/waypoints/src/Planner/CategoryCatalog.php ``` Use `CategoryCatalog::default_rows()` when changing the starter list for fresh diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index d05f1c6..2f997dc 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -13,26 +13,26 @@ The plugin source lives at: ```text -plugin/plan-your-day/ +plugin/waypoints/ ``` For local development, copy or symlink that directory into a WordPress install: ```sh -ln -s /path/to/plan-your-day/plugin/plan-your-day /path/to/wordpress/wp-content/plugins/plan-your-day +ln -s /path/to/waypoints/plugin/waypoints /path/to/wordpress/wp-content/plugins/waypoints ``` Generate the Composer autoloader from the plugin directory: ```sh -cd /path/to/plan-your-day/plugin/plan-your-day +cd /path/to/waypoints/plugin/waypoints composer install ``` Then activate the plugin from the WordPress admin Plugins screen or with WP-CLI: ```sh -wp --path=/path/to/wordpress plugin activate plan-your-day +wp --path=/path/to/wordpress plugin activate waypoints ``` In the WordPress admin, the plugin appears as **Waypoints**. Open @@ -44,8 +44,8 @@ One local development setup can look like this: ```text /path/to/wordpress -/path/to/wordpress/wp-content/plugins/plan-your-day - -> /path/to/plan-your-day/plugin/plan-your-day +/path/to/wordpress/wp-content/plugins/waypoints + -> /path/to/waypoints/plugin/waypoints ``` Useful local commands: @@ -54,13 +54,13 @@ Useful local commands: wp \ --path=/path/to/wordpress \ --url=https://example.test \ - plugin status plan-your-day \ + plugin status waypoints \ ``` The admin settings screen is: ```text -https://example.test/wp-admin/options-general.php?page=plan-your-day +https://example.test/wp-admin/options-general.php?page=waypoints ``` ## Activation Effects @@ -90,9 +90,9 @@ exclusions separately from source control ignore rules. Build the installable artifact from the source checkout with: ```sh -cd plugin/plan-your-day +cd plugin/waypoints ./tools/build-release-zip.sh ``` -The script writes `dist/plan-your-day-0.5.zip` at the repository root, which +The script writes `dist/waypoints-1.0.zip` at the repository root, which can be uploaded through the WordPress Plugins screen. diff --git a/docs/ISSUE-20-PLUGIN-SCAFFOLD-PLAN.md b/docs/ISSUE-20-PLUGIN-SCAFFOLD-PLAN.md index 87d8065..11cbbd8 100644 --- a/docs/ISSUE-20-PLUGIN-SCAFFOLD-PLAN.md +++ b/docs/ISSUE-20-PLUGIN-SCAFFOLD-PLAN.md @@ -1,14 +1,14 @@ # Issue 20: Generic WordPress Plugin Scaffold Plan -GitHub issue: https://github.com/acodebeard/plan-your-day/issues/20 +GitHub issue: https://github.com/acodebeard/waypoints/issues/20 -Historical note: this was the original scaffold plan from before the public -plugin name changed to Waypoints. Treat the names in this file as historical +Historical note: this was the original scaffold plan from before the final +public naming cleanup. Treat the legacy internal identifiers in this file as scaffold context, not current public naming guidance. ## Summary -Create the initial WordPress plugin scaffold for Plan Your Day. The scaffold should define the plugin directory structure, bootstrap file, plugin headers, namespace, constants, activation and deactivation hooks, version options, and release metadata placeholders. +Create the initial WordPress plugin scaffold for Waypoints. The scaffold should define the plugin directory structure, bootstrap file, plugin headers, namespace, constants, activation and deactivation hooks, version options, and release metadata placeholders. This issue is limited to scaffold work. The broader plugin implementation can continue in later issues once the scaffold is in place. @@ -25,7 +25,7 @@ This issue is limited to scaffold work. The broader plugin implementation can co Create the plugin under: ```text -plugin/plan-your-day/ +plugin/waypoints/ ``` This keeps the new plugin scaffold isolated from the rest of the implementation while the initial structure lands. @@ -34,10 +34,10 @@ This keeps the new plugin scaffold isolated from the rest of the implementation Use these identifiers consistently: -- Plugin name: `Plan Your Day` -- Plugin slug: `plan-your-day` -- Text domain: `plan-your-day` -- Main plugin file: `plan-your-day.php` +- Plugin name: `Waypoints` +- Plugin slug: `waypoints` +- Text domain: `waypoints` +- Main plugin file: root PHP bootstrap file - PHP namespace: `PlanYourDay` - Option prefix: `plan_your_day_` - Constant prefix: `PLAN_YOUR_DAY_` @@ -49,9 +49,9 @@ Do not introduce destination-specific naming or references, including Kona, Dest ## Files To Add ```text -plugin/plan-your-day/ +plugin/waypoints/ ├── .distignore -├── plan-your-day.php +├── root PHP bootstrap file ├── readme.txt ├── release.json └── src/ @@ -63,7 +63,7 @@ plugin/plan-your-day/ ## Implementation Checklist 1. Create the plugin directory structure. -2. Add `plan-your-day.php` with valid WordPress plugin headers. +2. Add the root PHP bootstrap file with valid WordPress plugin headers. 3. Add `defined( 'ABSPATH' ) || exit;` to prevent direct access. 4. Define initial plugin constants: - `PLAN_YOUR_DAY_VERSION` @@ -92,13 +92,13 @@ The main plugin file should include WordPress plugin headers similar to: ```php /** - * Plugin Name: Plan Your Day + * Plugin Name: Waypoints * Description: A configurable day planning plugin for WordPress. * Version: 0.1.0 * Requires at least: 6.4 * Requires PHP: 8.1 * Author: acodebeard - * Text Domain: plan-your-day + * Text Domain: waypoints * Domain Path: /languages * License: GPL-2.0-or-later */ @@ -127,28 +127,28 @@ Set `distribution` to a placeholder value such as `undecided`. Run PHP syntax checks: ```bash -php -l plugin/plan-your-day/plan-your-day.php -php -l plugin/plan-your-day/src/Plugin.php -php -l plugin/plan-your-day/src/Activator.php -php -l plugin/plan-your-day/src/Deactivator.php +php -l plugin/waypoints/.php +php -l plugin/waypoints/src/Plugin.php +php -l plugin/waypoints/src/Activator.php +php -l plugin/waypoints/src/Deactivator.php ``` Confirm no destination-specific references were introduced: ```bash -rg "Kona|Destination Kona Coast|Kailua Pier|DKC|dkc" plugin/plan-your-day +rg "Kona|Destination Kona Coast|Kailua Pier|DKC|dkc" plugin/waypoints ``` Confirm the expected files exist: ```bash -find plugin/plan-your-day -maxdepth 3 -type f | sort +find plugin/waypoints -maxdepth 3 -type f | sort ``` ## Definition Of Done -- [x] The plugin scaffold exists under `plugin/plan-your-day/`. -- [x] The main plugin file has valid headers and generic Plan Your Day metadata. +- [x] The plugin scaffold exists under `plugin/waypoints/`. +- [x] The main plugin file has valid headers and generic Waypoints metadata. - [x] Constants are defined with the `PLAN_YOUR_DAY_` prefix. - [x] Plugin PHP classes use the `PlanYourDay` namespace. - [x] Activation stores version and schema version options. diff --git a/docs/QA.md b/docs/QA.md index b66c6c4..db36908 100644 --- a/docs/QA.md +++ b/docs/QA.md @@ -28,7 +28,7 @@ meant to catch frontend regressions in the plugin's own public flow. From the repo root: ```bash -cd plugin/plan-your-day +cd plugin/waypoints composer install cd ../.. npm ci diff --git a/docs/README.md b/docs/README.md index 9521188..ec477a0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -39,6 +39,6 @@ land: These files are preserved as planning snapshots, not as the current source of truth for implementation status: -- [Plugin TODO Snapshot](PLAN-YOUR-DAY-PLUGIN-TODO.md) -- [GitHub Issue Draft Snapshot](PLAN-YOUR-DAY-PLUGIN-ISSUES.md) +- [Plugin TODO Snapshot](WAYPOINTS-PLUGIN-TODO.md) +- [GitHub Issue Draft Snapshot](WAYPOINTS-PLUGIN-ISSUES.md) - [Issue 20 Plugin Scaffold Plan](ISSUE-20-PLUGIN-SCAFFOLD-PLAN.md) diff --git a/docs/RELEASES.md b/docs/RELEASES.md index c92aaff..5f1d6ed 100644 --- a/docs/RELEASES.md +++ b/docs/RELEASES.md @@ -7,12 +7,12 @@ private updater or a custom `Update URI` workflow right now. Keep these values aligned before building a release: -- plugin header `Version` in `plugin/plan-your-day/plan-your-day.php` +- plugin header `Version` in the root PHP bootstrap file - `PLAN_YOUR_DAY_VERSION` - `PLAN_YOUR_DAY_SCHEMA_VERSION` -- `plugin/plan-your-day/release.json` +- `plugin/waypoints/release.json` - the artifact filename in `release.json` -- the `Unreleased` and versioned entries in `plugin/plan-your-day/readme.txt` +- the `Unreleased` and versioned entries in `plugin/waypoints/readme.txt` The release builder validates that: @@ -26,14 +26,14 @@ The release builder validates that: 2. Run the plugin test suite: ```sh - cd plugin/plan-your-day + cd plugin/waypoints composer test ``` 3. Build the installable release zip: ```sh - cd plugin/plan-your-day + cd plugin/waypoints ./tools/build-release-zip.sh ``` @@ -46,7 +46,7 @@ The release builder validates that: The current release artifact is an installable WordPress admin zip with: -- a top-level `plan-your-day/` directory +- a top-level `waypoints/` directory - production Composer autoload files included - development-only files excluded through `.distignore` diff --git a/docs/SETTINGS.md b/docs/SETTINGS.md index d2edf6f..c3b2899 100644 --- a/docs/SETTINGS.md +++ b/docs/SETTINGS.md @@ -66,7 +66,7 @@ Each saved category row includes: Built-in starter rows for fresh installs live in: ```text -plugin/plan-your-day/src/Planner/CategoryCatalog.php +plugin/waypoints/src/Planner/CategoryCatalog.php ``` Important behavior: @@ -97,7 +97,7 @@ plan_your_day_settings[interface_copy] Default copy definitions live in: ```text -plugin/plan-your-day/src/Frontend/InterfaceCopy.php +plugin/waypoints/src/Frontend/InterfaceCopy.php ``` Important behavior: diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 661a571..36cdb3c 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -5,7 +5,7 @@ Source checkouts require Composer: ```sh -cd plugin/plan-your-day +cd plugin/waypoints composer install ``` @@ -27,7 +27,7 @@ explicitly: php /path/to/wp \ --path=/path/to/wordpress \ --url=https://example.test \ - plugin status plan-your-day \ + plugin status waypoints \ ``` ## WordPress Says The Site Is Not Installed diff --git a/docs/USAGE.md b/docs/USAGE.md index 5ba3692..eb4cd58 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -47,10 +47,10 @@ to place the planner without writing shortcode manually. ## Shortcode -The shortcode tag is: +The preferred shortcode tag is: ```text -[plan_your_day] +[waypoints] ``` Supported attributes: @@ -61,13 +61,16 @@ Supported attributes: Examples: ```text -[plan_your_day] +[waypoints] ``` ```text -[plan_your_day action_url="https://example.test/plan-your-day/"] +[waypoints action_url="https://example.test/waypoints/"] ``` +The legacy `[plan_your_day]` shortcode remains available for content created +before the public plugin name changed. + Use the shortcode in classic content, a Shortcode block, widget areas that allow shortcodes, or any other editor flow where you want explicit markup control. diff --git a/docs/PLAN-YOUR-DAY-PLUGIN-ISSUES.md b/docs/WAYPOINTS-PLUGIN-ISSUES.md similarity index 99% rename from docs/PLAN-YOUR-DAY-PLUGIN-ISSUES.md rename to docs/WAYPOINTS-PLUGIN-ISSUES.md index ebcce52..db02714 100644 --- a/docs/PLAN-YOUR-DAY-PLUGIN-ISSUES.md +++ b/docs/WAYPOINTS-PLUGIN-ISSUES.md @@ -1,4 +1,4 @@ -# Plan Your Day Plugin GitHub Issue Drafts +# Waypoints Plugin GitHub Issue Drafts > Historical planning snapshot: these issue drafts were prepared before the > implementation backlog moved into GitHub. They are preserved for context, not @@ -8,7 +8,7 @@ Title: Create generic WordPress plugin scaffold -Summary: Create the initial plugin directory structure, main bootstrap file, plugin headers, namespace, constants, activation/deactivation hooks, and release metadata. The scaffold must use generic Plan Your Day naming and avoid destination-specific references. +Summary: Create the initial plugin directory structure, main bootstrap file, plugin headers, namespace, constants, activation/deactivation hooks, and release metadata. The scaffold must use generic Waypoints naming and avoid destination-specific references. Acceptance Criteria: - [x] Plugin has a valid main plugin file with WordPress headers. diff --git a/docs/PLAN-YOUR-DAY-PLUGIN-TODO.md b/docs/WAYPOINTS-PLUGIN-TODO.md similarity index 98% rename from docs/PLAN-YOUR-DAY-PLUGIN-TODO.md rename to docs/WAYPOINTS-PLUGIN-TODO.md index 4a59517..6abc19f 100644 --- a/docs/PLAN-YOUR-DAY-PLUGIN-TODO.md +++ b/docs/WAYPOINTS-PLUGIN-TODO.md @@ -1,4 +1,4 @@ -# Plan Your Day Plugin TODO +# Waypoints Plugin TODO > Historical planning snapshot: this checklist was drafted during the early > plugin port and backlog planning work. It is not maintained as the live @@ -7,14 +7,14 @@ ## Overview -Continue hardening Plan Your Day as a generic, maintainable WordPress plugin. +Continue hardening Waypoints as a generic, maintainable WordPress plugin. The plugin must not contain inherent references to Kona or any other specific location. Site-specific destination behavior should come from admin settings, including a required default location. ## Goals -- [ ] Package Plan Your Day as an installable WordPress plugin. +- [ ] Package Waypoints as an installable WordPress plugin. - [ ] Keep the plugin generic and location-agnostic. - [ ] Allow admins to configure the default location. - [ ] Allow admins to configure as much planner behavior and copy as practical. diff --git a/package-lock.json b/package-lock.json index 63106c7..600db2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,10 +1,10 @@ { - "name": "plan-your-day-qa", + "name": "waypoints-qa", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "plan-your-day-qa", + "name": "waypoints-qa", "devDependencies": { "@playwright/test": "1.59.1" } diff --git a/package.json b/package.json index 653b1de..c539e85 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "plan-your-day-qa", + "name": "waypoints-qa", "private": true, - "description": "Browser smoke tests for the Plan Your Day plugin repository.", + "description": "Browser smoke tests for the Waypoints plugin repository.", "scripts": { "browser-smoke": "playwright test" }, diff --git a/playwright.config.js b/playwright.config.js index 3921fa0..15ec5cc 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -15,10 +15,10 @@ module.exports = defineConfig({ }, webServer: { command: - 'mkdir -p tmp && rm -f tmp/plan-your-day-browser-state.json && ' + + 'mkdir -p tmp && rm -f tmp/waypoints-browser-state.json && ' + `PLAN_YOUR_DAY_BROWSER_BASE_URL=${baseURL} ` + - 'PLAN_YOUR_DAY_BROWSER_STATE_FILE=tmp/plan-your-day-browser-state.json ' + - 'php -S 127.0.0.1:9080 -t . plugin/plan-your-day/tests/browser-app/router.php', + 'PLAN_YOUR_DAY_BROWSER_STATE_FILE=tmp/waypoints-browser-state.json ' + + 'php -S 127.0.0.1:9080 -t . plugin/waypoints/tests/browser-app/router.php', url: `${baseURL}/__health`, reuseExistingServer: !process.env.CI, timeout: 30_000, diff --git a/plugin/plan-your-day/assets/css/plan.min.css b/plugin/plan-your-day/assets/css/plan.min.css deleted file mode 100644 index 22e80b7..0000000 --- a/plugin/plan-your-day/assets/css/plan.min.css +++ /dev/null @@ -1 +0,0 @@ -@font-face{font-family:"Noto Sans";src:url("../fonts/noto-sans-v42-latin-ext-300.woff2") format("woff2");font-style:normal;font-weight:300;font-display:swap}@font-face{font-family:"Noto Sans";src:url("../fonts/noto-sans-v42-latin-ext-regular.woff2") format("woff2");font-style:normal;font-weight:400;font-display:swap}@font-face{font-family:"Noto Sans";src:url("../fonts/noto-sans-v42-latin-ext-700.woff2") format("woff2");font-style:normal;font-weight:700;font-display:swap}.plan-your-day,.plan-your-day *,.plan-your-day ::after,.plan-your-day ::before{box-sizing:border-box}.plan-your-day{--focus-ring-color:#1168d8;--focus-ring-offset-color:#ffffff;--pyd-font-family:"Noto Sans",system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;--pyd-text:#173038;--pyd-heading:#102027;--pyd-hero-heading:#112127;--pyd-muted:#35545d;--pyd-accent:#8a4f0f;--pyd-accent-soft:rgba(138, 79, 15, 0.2);--pyd-accent-border:rgba(138, 79, 15, 0.42);--pyd-border-subtle:rgba(23, 48, 56, 0.08);--pyd-border:rgba(23, 48, 56, 0.12);--pyd-border-medium:rgba(23, 48, 56, 0.14);--pyd-border-strong:rgba(23, 48, 56, 0.16);--pyd-border-emphasis:rgba(23, 48, 56, 0.22);--pyd-control-mark-border:rgba(23, 48, 56, 0.35);--pyd-surface-bg:radial-gradient(circle at top right, rgba(241, 181, 67, 0.18), transparent 26%),linear-gradient(180deg, #fffdf8 0%, #f4ecdb 100%);--pyd-surface-shadow:0 0.85rem 2rem rgba(23, 48, 56, 0.12);--pyd-card-bg:rgba(255, 255, 255, 0.82);--pyd-card-shadow:0 0.65rem 1.45rem rgba(23, 48, 56, 0.06);--pyd-panel-bg:#fffefb;--pyd-option-bg:#fffdf9;--pyd-option-selected-bg:linear-gradient(180deg, #fffaf0 0%, #f7ecd6 100%);--pyd-option-selected-shadow:0 0.85rem 1.8rem rgba(138, 79, 15, 0.12);--pyd-radio-bg:#ffffff;--pyd-radio-checked-bg:radial-gradient(circle at center, var(--pyd-accent) 0 45%, transparent 48% 100%),#fff7e9;--pyd-chip-bg:rgba(23, 48, 56, 0.08);--pyd-hero-note-bg:rgba(255, 255, 255, 0.72);--pyd-primary-bg:#173038;--pyd-primary-hover-bg:#0f2228;--pyd-primary-text:#fffdf7;--pyd-primary-soft-text:#fffaf0;--pyd-secondary-hover-bg:#f6eddb;--pyd-interactive-hover-bg:rgba(255, 255, 255, 0.38);--pyd-item-bg:linear-gradient(180deg, #fffdf9 0%, #f7f0e0 100%);--pyd-item-hover-shadow:0 0.85rem 1.8rem rgba(23, 48, 56, 0.08);--pyd-item-drag-shadow:0 1.1rem 2rem rgba(23, 48, 56, 0.14);--pyd-feedback-border:rgba(255, 255, 255, 0.55);--pyd-feedback-shadow:0 0.5rem 1rem rgba(23, 48, 56, 0.18);--pyd-disabled-bg:rgba(255, 255, 255, 0.65);--pyd-disabled-text:#5c737a;--pyd-empty-border:rgba(23, 48, 56, 0.26);--pyd-empty-bg:rgba(255, 255, 255, 0.62);--pyd-summary-bg:linear-gradient(180deg, #fffdf7 0%, #f8f1e0 100%);--pyd-summary-tile-bg:rgba(255, 255, 255, 0.7);--pyd-note-border:rgba(23, 48, 56, 0.12);--pyd-note-bg:rgba(223, 236, 241, 0.75);--pyd-note-text:#163038;--pyd-warning-border:rgba(138, 79, 15, 0.2);--pyd-warning-bg:rgba(255, 243, 225, 0.9);--pyd-warning-text:#6e410d;--pyd-success:#167c3b;--pyd-danger:#a73525;--pyd-map-bg:#f3ede0;--pyd-scrollbar-track:rgba(23, 48, 56, 0.08);--pyd-scrollbar-thumb:rgba(138, 79, 15, 0.55);--pyd-scrollbar-thumb-border:rgba(255, 255, 255, 0.65);color-scheme:light;font-family:var(--pyd-font-family);line-height:1.5;padding:0}.plan-your-day[data-plan-color-mode=dark]{--focus-ring-color:#9fc5ff;--focus-ring-offset-color:#232323;--pyd-text:#f3f1ea;--pyd-heading:#fffaf0;--pyd-hero-heading:#fffdf7;--pyd-muted:#c5d0d1;--pyd-accent:#f1b543;--pyd-accent-soft:rgba(241, 181, 67, 0.24);--pyd-accent-border:rgba(241, 181, 67, 0.55);--pyd-border-subtle:rgba(255, 255, 255, 0.08);--pyd-border:rgba(255, 255, 255, 0.12);--pyd-border-medium:rgba(255, 255, 255, 0.14);--pyd-border-strong:rgba(255, 255, 255, 0.18);--pyd-border-emphasis:rgba(255, 255, 255, 0.26);--pyd-control-mark-border:rgba(255, 255, 255, 0.42);--pyd-surface-bg:radial-gradient(circle at top right, rgba(241, 181, 67, 0.14), transparent 28%),linear-gradient(180deg, #2e2e2e 0%, #232323 100%);--pyd-surface-shadow:0 0.85rem 2rem rgba(0, 0, 0, 0.34);--pyd-card-bg:rgba(48, 48, 48, 0.92);--pyd-card-shadow:0 0.65rem 1.45rem rgba(0, 0, 0, 0.24);--pyd-panel-bg:#2b2b2b;--pyd-option-bg:#292929;--pyd-option-selected-bg:linear-gradient(180deg, #3a3427 0%, #2b2925 100%);--pyd-option-selected-shadow:0 0.85rem 1.8rem rgba(0, 0, 0, 0.28);--pyd-radio-bg:#232323;--pyd-radio-checked-bg:radial-gradient(circle at center, var(--pyd-accent) 0 45%, transparent 48% 100%),#302818;--pyd-chip-bg:rgba(255, 255, 255, 0.1);--pyd-hero-note-bg:rgba(255, 255, 255, 0.08);--pyd-primary-bg:#f1b543;--pyd-primary-hover-bg:#ffd36b;--pyd-primary-text:#232323;--pyd-primary-soft-text:#232323;--pyd-secondary-hover-bg:rgba(241, 181, 67, 0.16);--pyd-interactive-hover-bg:rgba(255, 255, 255, 0.08);--pyd-item-bg:linear-gradient(180deg, #303030 0%, #282828 100%);--pyd-item-hover-shadow:0 0.85rem 1.8rem rgba(0, 0, 0, 0.26);--pyd-item-drag-shadow:0 1.1rem 2rem rgba(0, 0, 0, 0.36);--pyd-feedback-border:rgba(241, 181, 67, 0.36);--pyd-feedback-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.28);--pyd-disabled-bg:rgba(255, 255, 255, 0.08);--pyd-disabled-text:#9aa6a8;--pyd-empty-border:rgba(255, 255, 255, 0.24);--pyd-empty-bg:rgba(255, 255, 255, 0.07);--pyd-summary-bg:linear-gradient(180deg, #303030 0%, #292929 100%);--pyd-summary-tile-bg:rgba(255, 255, 255, 0.08);--pyd-note-border:rgba(145, 198, 217, 0.28);--pyd-note-bg:rgba(62, 91, 101, 0.5);--pyd-note-text:#e9f6f9;--pyd-warning-border:rgba(241, 181, 67, 0.34);--pyd-warning-bg:rgba(78, 57, 24, 0.68);--pyd-warning-text:#ffe1a3;--pyd-success:#78d690;--pyd-danger:#ff9a8a;--pyd-map-bg:#303030;--pyd-scrollbar-track:rgba(255, 255, 255, 0.08);--pyd-scrollbar-thumb:rgba(241, 181, 67, 0.62);--pyd-scrollbar-thumb-border:rgba(35, 35, 35, 0.9);color-scheme:dark}@media (prefers-color-scheme:dark){.plan-your-day[data-plan-color-mode-default=system]:not([data-plan-color-mode]){--focus-ring-color:#9fc5ff;--focus-ring-offset-color:#232323;--pyd-text:#f3f1ea;--pyd-heading:#fffaf0;--pyd-hero-heading:#fffdf7;--pyd-muted:#c5d0d1;--pyd-accent:#f1b543;--pyd-accent-soft:rgba(241, 181, 67, 0.24);--pyd-accent-border:rgba(241, 181, 67, 0.55);--pyd-border-subtle:rgba(255, 255, 255, 0.08);--pyd-border:rgba(255, 255, 255, 0.12);--pyd-border-medium:rgba(255, 255, 255, 0.14);--pyd-border-strong:rgba(255, 255, 255, 0.18);--pyd-border-emphasis:rgba(255, 255, 255, 0.26);--pyd-control-mark-border:rgba(255, 255, 255, 0.42);--pyd-surface-bg:radial-gradient(circle at top right, rgba(241, 181, 67, 0.14), transparent 28%),linear-gradient(180deg, #2e2e2e 0%, #232323 100%);--pyd-surface-shadow:0 0.85rem 2rem rgba(0, 0, 0, 0.34);--pyd-card-bg:rgba(48, 48, 48, 0.92);--pyd-card-shadow:0 0.65rem 1.45rem rgba(0, 0, 0, 0.24);--pyd-panel-bg:#2b2b2b;--pyd-option-bg:#292929;--pyd-option-selected-bg:linear-gradient(180deg, #3a3427 0%, #2b2925 100%);--pyd-option-selected-shadow:0 0.85rem 1.8rem rgba(0, 0, 0, 0.28);--pyd-radio-bg:#232323;--pyd-radio-checked-bg:radial-gradient(circle at center, var(--pyd-accent) 0 45%, transparent 48% 100%),#302818;--pyd-chip-bg:rgba(255, 255, 255, 0.1);--pyd-hero-note-bg:rgba(255, 255, 255, 0.08);--pyd-primary-bg:#f1b543;--pyd-primary-hover-bg:#ffd36b;--pyd-primary-text:#232323;--pyd-primary-soft-text:#232323;--pyd-secondary-hover-bg:rgba(241, 181, 67, 0.16);--pyd-interactive-hover-bg:rgba(255, 255, 255, 0.08);--pyd-item-bg:linear-gradient(180deg, #303030 0%, #282828 100%);--pyd-item-hover-shadow:0 0.85rem 1.8rem rgba(0, 0, 0, 0.26);--pyd-item-drag-shadow:0 1.1rem 2rem rgba(0, 0, 0, 0.36);--pyd-feedback-border:rgba(241, 181, 67, 0.36);--pyd-feedback-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.28);--pyd-disabled-bg:rgba(255, 255, 255, 0.08);--pyd-disabled-text:#9aa6a8;--pyd-empty-border:rgba(255, 255, 255, 0.24);--pyd-empty-bg:rgba(255, 255, 255, 0.07);--pyd-summary-bg:linear-gradient(180deg, #303030 0%, #292929 100%);--pyd-summary-tile-bg:rgba(255, 255, 255, 0.08);--pyd-note-border:rgba(145, 198, 217, 0.28);--pyd-note-bg:rgba(62, 91, 101, 0.5);--pyd-note-text:#e9f6f9;--pyd-warning-border:rgba(241, 181, 67, 0.34);--pyd-warning-bg:rgba(78, 57, 24, 0.68);--pyd-warning-text:#ffe1a3;--pyd-success:#78d690;--pyd-danger:#ff9a8a;--pyd-map-bg:#303030;--pyd-scrollbar-track:rgba(255, 255, 255, 0.08);--pyd-scrollbar-thumb:rgba(241, 181, 67, 0.62);--pyd-scrollbar-thumb-border:rgba(35, 35, 35, 0.9);color-scheme:dark}}.plan-your-day img{display:block;max-width:100%}.plan-your-day button,.plan-your-day input,.plan-your-day select,.plan-your-day textarea{touch-action:manipulation}.plan-your-day button{cursor:pointer}.plan-your-day a{color:inherit}.plan-your-day .screen-reader-text,.plan-your-day .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.plan-your-day{width:min(100% - 2rem,1180px);margin:clamp(1rem,4vw,3rem) auto;color:var(--pyd-text);container-type:inline-size}.plan-your-day [hidden]{display:none!important}.plan-your-day__surface{padding:clamp(1rem,1.6vw,1.5rem);border:thin solid var(--pyd-border);border-radius:1.5rem;background:var(--pyd-surface-bg);box-shadow:var(--pyd-surface-shadow)}.plan-your-day__hero{display:grid;gap:.8rem;margin-bottom:1.2rem;padding-bottom:1rem;border-bottom:thin solid var(--pyd-border)}.plan-your-day__eyebrow,.plan-your-day__summary-eyebrow,.plan-your-day__trip-kicker{margin:0;font-size:.78rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--pyd-accent)}.plan-your-day__hero h2{font-size:clamp(1.8rem, 3.3vw, 2.45rem);line-height:1;color:var(--pyd-hero-heading);font-weight:700}.plan-your-day__auto-note,.plan-your-day__card-header p,.plan-your-day__category-description,.plan-your-day__hero-note,.plan-your-day__input-help,.plan-your-day__intro,.plan-your-day__noscript-note,.plan-your-day__preview-empty p,.plan-your-day__result-meta,.plan-your-day__results-empty p,.plan-your-day__start-description,.plan-your-day__summary-overview,.plan-your-day__trip-empty p,.plan-your-day__trip-meta{margin:0;font-size:.95rem;line-height:1.5;color:var(--pyd-muted)}.plan-your-day__hero-note{max-width:33rem;padding:.75rem .9rem;border-radius:1rem;background:var(--pyd-hero-note-bg);border:thin solid var(--pyd-border-subtle)}.plan-your-day__layout{display:grid;gap:1.2rem;align-items:start}.plan-your-day__controls,.plan-your-day__preview-panel{display:grid;gap:.85rem;min-width:0}.plan-your-day__card{min-width:0;padding:1rem;border:thin solid var(--pyd-border);border-radius:1.2rem;background:var(--pyd-card-bg);box-shadow:var(--pyd-card-shadow)}.plan-your-day__card-header,.plan-your-day__summary-top{display:flex;align-items:start;justify-content:space-between;gap:1rem;margin-bottom:.8rem}.plan-your-day__trip-header-actions{display:inline-flex;align-items:center;justify-content:flex-end;gap:.6rem;flex-wrap:wrap}.plan-your-day__card h3,.plan-your-day__card h4{color:var(--pyd-heading);font-weight:700;margin-top:0}.plan-your-day__card h3{font-size:1.2rem;margin-bottom:.25rem}.plan-your-day__fieldset{border:0;padding:0;margin:0}.plan-your-day__count-pill,.plan-your-day__summary-count{display:inline-flex;align-items:center;justify-content:center;min-height:1.8rem;padding:.3rem .7rem;border-radius:999px;background:var(--pyd-primary-bg);color:var(--pyd-primary-soft-text);font-size:.85rem;font-weight:700;white-space:nowrap}.plan-your-day__category-grid,.plan-your-day__results-list,.plan-your-day__start-options,.plan-your-day__summary-grid,.plan-your-day__trip-list{display:grid;gap:.7rem}.plan-your-day__category-option,.plan-your-day__start-option{display:block;cursor:pointer}.plan-your-day__category-option input,.plan-your-day__start-option input{position:absolute;opacity:0;pointer-events:none}.plan-your-day__category-option-body,.plan-your-day__start-option-body{display:grid;gap:.35rem;min-height:100%;padding:.85rem .9rem .85rem 2.7rem;border:thin solid var(--pyd-border-strong);border-radius:1rem;background:var(--pyd-option-bg);position:relative;transition:border-color .2s ease,transform .2s ease,box-shadow .2s ease,background-color .2s ease}.plan-your-day__category-option-body::before,.plan-your-day__start-option-body::before{content:"";position:absolute;top:.92rem;left:.9rem;width:1.05rem;height:1.05rem;border:2px solid var(--pyd-control-mark-border);border-radius:999px;background:var(--pyd-radio-bg)}.plan-your-day__start-option:active .plan-your-day__start-option-body,.plan-your-day__start-option:hover .plan-your-day__start-option-body{border-color:var(--pyd-accent-border);background:var(--pyd-secondary-hover-bg);box-shadow:var(--pyd-item-hover-shadow);transform:translateY(-1px)}.plan-your-day__category-option input:checked+.plan-your-day__category-option-body,.plan-your-day__start-option input:checked+.plan-your-day__start-option-body{border-color:var(--pyd-accent-border);background:var(--pyd-option-selected-bg);box-shadow:var(--pyd-option-selected-shadow);transform:translateY(-1px)}.plan-your-day__category-option input:checked+.plan-your-day__category-option-body::before,.plan-your-day__start-option input:checked+.plan-your-day__start-option-body::before{border-color:var(--pyd-accent);background:var(--pyd-radio-checked-bg)}.plan-your-day__category-radio:focus-visible+.plan-your-day__category-option-body,.plan-your-day__category-search input:focus-visible,.plan-your-day__category-search-button:focus-visible,.plan-your-day__category-trigger:focus-visible,.plan-your-day__clear-link:focus-visible,.plan-your-day__color-mode-toggle:focus-visible,.plan-your-day__custom-start input:focus-visible,.plan-your-day__load-more-button:focus-visible,.plan-your-day__maps-link:focus-visible,.plan-your-day__result-add:focus-visible,.plan-your-day__result-link:focus-visible,.plan-your-day__start-option input:focus-visible+.plan-your-day__start-option-body,.plan-your-day__start-toggle:focus-visible,.plan-your-day__submit:focus-visible,.plan-your-day__trip-tools a:focus-visible,.plan-your-day__trip-tools button:focus-visible{outline:3px solid var(--focus-ring-color);outline-offset:3px;box-shadow:0 0 0 3px var(--focus-ring-offset-color)}.plan-your-day__category-title,.plan-your-day__start-title{display:block;color:var(--pyd-heading);font-size:1rem;font-weight:700}.plan-your-day__category-accordion{display:grid;gap:.7rem}.plan-your-day__category-search{margin-bottom:.85rem}.plan-your-day__category-search-controls{display:grid;gap:.65rem}.plan-your-day__category-search input{width:100%;min-height:2.85rem;padding:.75rem .9rem;border:thin solid var(--pyd-border-strong);border-radius:.5rem;background:var(--pyd-panel-bg);color:var(--pyd-heading)}.plan-your-day__category-search-button{justify-self:start}.plan-your-day__custom-search-results{margin-bottom:.85rem}.plan-your-day__category-accordion-item{border:thin solid var(--pyd-border-medium);border-radius:1rem;background:var(--pyd-item-bg);transition:border-color .18s ease,box-shadow .18s ease,background-color .18s ease}.plan-your-day__category-accordion-item.is-expanded{border-color:var(--pyd-accent-border);box-shadow:var(--pyd-item-hover-shadow)}.plan-your-day__category-accordion-item.is-expanded>h4 button{box-shadow:0 4px 5px -5px #333333d7}.plan-your-day__category-accordion-heading{margin:0}.plan-your-day__category-trigger{appearance:none;display:flex;align-items:center;justify-content:space-between;gap:1rem;width:100%;padding:.95rem 1rem;border:0;border-radius:1rem;background:0 0;color:inherit;text-align:left;cursor:pointer;transition:transform .18s ease,background-color .18s ease,color .18s ease}.plan-your-day__category-trigger:active,.plan-your-day__category-trigger:hover{transform:translateY(-1px);background:var(--pyd-interactive-hover-bg)}.plan-your-day__category-trigger-copy{display:grid;gap:.18rem;min-width:0;flex:1 1 auto}.plan-your-day__category-trigger-side{display:inline-flex;align-items:center;gap:.7rem;flex-shrink:0}.plan-your-day__category-trigger-chevron{width:.72rem;height:.72rem;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .18s ease}.plan-your-day__category-trigger[aria-expanded=true] .plan-your-day__category-trigger-chevron{transform:rotate(-135deg)}.plan-your-day__category-panel{padding:.66rem 1rem 1rem}.plan-your-day__custom-search-header{margin:0}.plan-your-day__custom-search-header h4{margin:0;font-weight:700}.plan-your-day__category-results-scroll{max-height:min(28rem,65vh);overflow-y:auto;padding-right:.35rem;scrollbar-width:thin;scrollbar-color:var(--pyd-scrollbar-thumb) var(--pyd-scrollbar-track);transition:opacity .18s ease,filter .18s ease}.plan-your-day__category-results-scroll::-webkit-scrollbar{width:.65rem}.plan-your-day__category-results-scroll::-webkit-scrollbar-track{background:var(--pyd-scrollbar-track);border-radius:999px}.plan-your-day__category-results-scroll::-webkit-scrollbar-thumb{background:var(--pyd-scrollbar-thumb);border-radius:999px;border:2px solid var(--pyd-scrollbar-thumb-border)}.plan-your-day__start-toggle{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:.55rem;min-height:2.5rem;padding:.55rem .9rem;border:thin solid var(--pyd-border-medium);border-radius:999px;background:var(--pyd-panel-bg);color:var(--pyd-text);font-size:.88rem;font-weight:700;line-height:1;cursor:pointer;transition:transform .18s ease,border-color .18s ease,background-color .18s ease,color .18s ease}.plan-your-day__start-toggle:active,.plan-your-day__start-toggle:hover{transform:translateY(-1px);border-color:var(--pyd-accent-border);background:var(--pyd-secondary-hover-bg)}.plan-your-day__hero-tools{display:flex;align-items:center;justify-content:flex-start}.plan-your-day__color-mode-toggle{appearance:none;display:inline-flex;position:relative;align-items:center;gap:.55rem;min-height:2.5rem;padding:.45rem .75rem .45rem .5rem;border:thin solid var(--pyd-border-medium);border-radius:999px;background:var(--pyd-panel-bg);color:var(--pyd-text);cursor:pointer;font-family:inherit;font-size:.88rem;font-weight:700;line-height:1;transition:transform .18s ease,border-color .18s ease,background-color .18s ease,color .18s ease}.plan-your-day__color-mode-toggle::before{content:"";width:2.2rem;height:1.25rem;border-radius:999px;background:var(--pyd-chip-bg);box-shadow:inset 0 0 0 thin var(--pyd-border)}.plan-your-day__color-mode-toggle::after{content:"";position:absolute;width:.85rem;height:.85rem;margin-left:.2rem;border-radius:999px;background:var(--pyd-primary-bg);transform:translateX(0);transition:background-color .18s ease,transform .18s ease}.plan-your-day__color-mode-toggle[aria-pressed=true]::after{transform:translateX(.95rem)}.plan-your-day__color-mode-toggle:active,.plan-your-day__color-mode-toggle:hover{transform:translateY(-1px);border-color:var(--pyd-accent-border);background:var(--pyd-secondary-hover-bg)}.plan-your-day__start-toggle-chevron{width:.6rem;height:.6rem;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-135deg);transition:transform .18s ease}.plan-your-day__start-toggle.is-collapsed .plan-your-day__start-toggle-chevron{transform:rotate(45deg)}.plan-your-day__start-panel{will-change:height}.plan-your-day [data-plan-preview-card][aria-busy=true] .plan-your-day__map-wrap,.plan-your-day [data-plan-preview-card][aria-busy=true] .plan-your-day__summary,.plan-your-day [data-plan-trip-region][aria-busy=true],.plan-your-day__category-results-scroll[aria-busy=true]{opacity:.72;filter:saturate(.88)}.plan-your-day [data-plan-preview-card][aria-busy=true],.plan-your-day [data-plan-trip-region][aria-busy=true],.plan-your-day__category-results-scroll[aria-busy=true]{cursor:progress}.plan-your-day__result-distance{margin:0;font-size:.88rem;font-weight:700;color:var(--pyd-accent)}.plan-your-day__results-list,.plan-your-day__trip-list{margin:0;padding:0;list-style:none}.plan-your-day__load-more{display:flex;justify-content:center;margin-top:.85rem}.plan-your-day__result-item,.plan-your-day__trip-item{display:flex;flex-wrap:wrap;gap:.8rem;position:relative;padding:.85rem;border:thin solid var(--pyd-border-medium);border-radius:.5rem;background:var(--pyd-item-bg);transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease}.plan-your-day__result-copy,.plan-your-day__trip-copy{display:grid;gap:.15rem;min-width:0}.plan-your-day__result-copy h4,.plan-your-day__trip-copy h4{font-size:1rem;margin:0;font-weight:700}.plan-your-day__result-tools,.plan-your-day__trip-tools{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.plan-your-day__category-search-button,.plan-your-day__clear-link,.plan-your-day__load-more-button,.plan-your-day__maps-link,.plan-your-day__reorder-disabled,.plan-your-day__result-add,.plan-your-day__result-link,.plan-your-day__submit,.plan-your-day__trip-tools a,.plan-your-day__trip-tools button{display:inline-flex;align-items:center;justify-content:center;min-height:2.625rem;height:42px;padding:.65rem .95rem;border:thin solid transparent;border-radius:999px;appearance:none;cursor:pointer;font-family:inherit;font-size:.9rem;font-weight:700;line-height:1;text-decoration:none;transition:transform .18s ease,background-color .18s ease,color .18s ease,border-color .18s ease,box-shadow .18s ease}.plan-your-day__load-more-button,.plan-your-day__maps-link,.plan-your-day__result-link,.plan-your-day__trip-tools a,.plan-your-day__trip-tools button{border-color:var(--pyd-border-strong);background:var(--pyd-panel-bg);color:var(--pyd-text)}.plan-your-day__load-more-button:active:not(:disabled),.plan-your-day__load-more-button:hover:not(:disabled),.plan-your-day__maps-link:active:not(.is-disabled),.plan-your-day__maps-link:hover:not(.is-disabled),.plan-your-day__result-link:active,.plan-your-day__result-link:hover,.plan-your-day__trip-tools a:active,.plan-your-day__trip-tools a:hover,.plan-your-day__trip-tools button:active:not(:disabled),.plan-your-day__trip-tools button:hover:not(:disabled){transform:translateY(-1px);border-color:var(--pyd-accent-border);background:var(--pyd-secondary-hover-bg)}.plan-your-day__category-search-button,.plan-your-day__load-more-button,.plan-your-day__result-add,.plan-your-day__submit{background:var(--pyd-primary-bg);color:var(--pyd-primary-text)}.plan-your-day__category-search-button:active,.plan-your-day__category-search-button:hover,.plan-your-day__load-more-button:active:not(:disabled),.plan-your-day__load-more-button:hover:not(:disabled),.plan-your-day__result-add:active,.plan-your-day__result-add:hover,.plan-your-day__submit:active,.plan-your-day__submit:hover{transform:translateY(-1px);background:var(--pyd-primary-hover-bg)}.plan-your-day__load-more-button{min-width:11rem}.plan-your-day__load-more-button.is-loading,.plan-your-day__load-more-button:disabled{cursor:progress;opacity:.82;transform:none}.plan-your-day__result-added{display:inline-flex;align-items:center;justify-content:center;min-height:2.625rem;padding:.65rem .95rem;border-radius:999px;background:var(--pyd-chip-bg);color:var(--pyd-text);font-size:.9rem;font-weight:700}.plan-your-day__load-more{display:flex;justify-content:flex-start;margin-top:.9rem}.plan-your-day__load-more-button.is-loading,.plan-your-day__load-more-button:disabled{opacity:.72;cursor:progress;transform:none}.plan-your-day__waypoint-feedback{position:absolute;top:.65rem;right:.65rem;z-index:2;max-width:calc(100% - 1.3rem);padding:.45rem .7rem;border:thin solid var(--pyd-feedback-border);border-radius:.5rem;background:var(--pyd-primary-bg);box-shadow:var(--pyd-feedback-shadow);color:var(--pyd-primary-text);font-size:.85rem;font-weight:700;line-height:1.2;pointer-events:none}.plan-your-day.is-enhanced .plan-your-day__trip-item:hover{border-color:var(--pyd-accent-border);box-shadow:var(--pyd-item-hover-shadow)}.plan-your-day__trip-item.is-dragging{opacity:.7;transform:scale(.985);box-shadow:var(--pyd-item-drag-shadow)}.plan-your-day__trip-item.is-drop-before{box-shadow:inset 0 4px 0 var(--pyd-accent)}.plan-your-day__trip-item.is-drop-after{box-shadow:inset 0 -4px 0 var(--pyd-accent)}.plan-your-day__trip-main{display:grid;grid-template-columns:auto 1fr;gap:.75rem}.plan-your-day__trip-number{display:inline-flex;align-items:center;justify-content:center;width:2.1rem;height:2.1rem;border-radius:999px;background:var(--pyd-primary-bg);color:var(--pyd-primary-text);font-size:.95rem;font-weight:700}.plan-your-day__drag-handle{display:none;align-items:center;justify-content:center;min-width:4rem;padding:.6rem 16px .4rem 32px;border-radius:1000px;background:var(--pyd-primary-bg);color:var(--pyd-primary-text);font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;position:relative;height:42px}.plan-your-day__drag-handle:before{content:'';display:inline-block;width:10px;height:18px;background-image:repeating-radial-gradient(circle,currentColor 0 2px,transparent 2.5px 100%);background-size:5px 6px;background-position:center;position:absolute;left:8px;top:5px;bottom:5px;margin:auto 0}.plan-your-day.is-enhanced .plan-your-day__drag-handle{display:inline-flex}.plan-your-day__reorder-links{display:inline-flex;gap:.4rem;flex-wrap:wrap}.plan-your-day__reorder-disabled{opacity:.45;cursor:not-allowed;border-color:var(--pyd-border);background:var(--pyd-disabled-bg);color:var(--pyd-disabled-text)}.plan-your-day__preview-empty,.plan-your-day__results-empty,.plan-your-day__trip-empty{display:grid;gap:.55rem;place-items:start;padding:1.1rem;border:thin dashed var(--pyd-empty-border);border-radius:.5rem;background:var(--pyd-empty-bg)}.plan-your-day__preview-empty h4,.plan-your-day__results-empty h4,.plan-your-day__trip-empty h4{font-size:1.15rem;font-weight:700}.plan-your-day__custom-start{margin-top:.85rem}.plan-your-day__custom-start label{display:inline-block;margin-bottom:.45rem;font-weight:700;color:var(--pyd-heading)}.plan-your-day__custom-start-field{position:relative}.plan-your-day__custom-start input{width:100%;min-height:2.85rem;padding:.75rem 2.85rem .75rem .9rem;border:thin solid var(--pyd-border-strong);border-radius:.5rem;background:var(--pyd-panel-bg);color:var(--pyd-heading)}.plan-your-day__custom-start-indicator{position:absolute;top:50%;right:.9rem;display:none;width:1rem;height:1rem;pointer-events:none;transform:translateY(-50%)}.plan-your-day__custom-start[data-plan-custom-start-state=checking] .plan-your-day__custom-start-indicator,.plan-your-day__custom-start[data-plan-custom-start-state=found] .plan-your-day__custom-start-indicator,.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator{display:block}.plan-your-day__custom-start[data-plan-custom-start-state=checking] .plan-your-day__custom-start-indicator{border:2px solid var(--pyd-border-emphasis);border-top-color:var(--pyd-accent);border-radius:999px;animation:plan-your-day-spin .8s linear infinite}.plan-your-day__custom-start[data-plan-custom-start-state=found] .plan-your-day__custom-start-indicator::before{content:"";position:absolute;top:.05rem;left:.3rem;width:.38rem;height:.72rem;border-right:.18rem solid var(--pyd-success);border-bottom:.18rem solid var(--pyd-success);transform:rotate(45deg)}.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator{border:2px solid var(--pyd-danger);border-radius:999px}.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator::after,.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator::before{content:"";position:absolute;top:.2rem;left:.4rem;width:.15rem;height:.52rem;border-radius:999px;background:var(--pyd-danger)}.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator::before{transform:rotate(45deg)}.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator::after{transform:rotate(-45deg)}@keyframes plan-your-day-spin{to{transform:translateY(-50%) rotate(360deg)}}@media (prefers-reduced-motion:reduce){.plan-your-day__custom-start[data-plan-custom-start-state=checking] .plan-your-day__custom-start-indicator{animation:none}}.plan-your-day__auto-note,.plan-your-day__input-help,.plan-your-day__noscript-note{margin-top:.55rem}.plan-your-day__actions{display:flex;gap:.75rem;flex-wrap:wrap;margin-top:.85rem}.plan-your-day__clear-link{border-color:var(--pyd-border);background:0 0;color:var(--pyd-text)}.plan-your-day__clear-link:active,.plan-your-day__clear-link:hover{transform:translateY(-1px);border-color:var(--pyd-border-emphasis);background:var(--pyd-summary-tile-bg)}.plan-your-day__maps-link.is-disabled,.plan-your-day__maps-link[aria-disabled=true]{opacity:.5;cursor:not-allowed;pointer-events:none}.plan-your-day__messages{display:grid;gap:.7rem;margin-bottom:1rem}.plan-your-day__message{padding:.75rem .85rem;border-radius:.5rem;border:thin solid transparent;font-size:.92rem;line-height:1.45}.plan-your-day__message--note{border-color:var(--pyd-note-border);background:var(--pyd-note-bg);color:var(--pyd-note-text)}.plan-your-day__message--warning{border-color:var(--pyd-warning-border);background:var(--pyd-warning-bg);color:var(--pyd-warning-text)}.plan-your-day__summary{margin-top:1rem;margin-bottom:0;padding:.9rem;border-radius:.5rem;background:var(--pyd-summary-bg);border:thin solid var(--pyd-border-subtle)}.plan-your-day__summary-handoff{display:flex;flex-direction:column;gap:.45rem;margin-top:1rem}.plan-your-day__summary-handoff-label{margin:0;font-size:.84rem;font-weight:700;letter-spacing:0;text-transform:uppercase;color:var(--pyd-accent)}.plan-your-day__maps-link--summary{width:100%;min-height:3.35rem;padding:.9rem 1.2rem;font-size:1.15rem}.plan-your-day__summary-grid div{padding:.7rem .8rem;border-radius:.5rem;background:var(--pyd-summary-tile-bg)}.plan-your-day__summary-grid dt{margin-bottom:.35rem;font-size:.84rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--pyd-accent)}.plan-your-day__summary-grid dd{margin:0;font-size:.95rem;color:var(--pyd-heading)}.plan-your-day__map-wrap{overflow:hidden;border-radius:1rem;border:thin solid var(--pyd-border);background:var(--pyd-map-bg)}.plan-your-day__map-frame{display:block;width:100%;min-height:24rem;border:0;background:var(--pyd-map-bg)}@media (min-width:980px){.plan-your-day__hero{grid-template-columns:minmax(0,1fr) auto;align-items:end}.plan-your-day__summary-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.plan-your-day__result-item,.plan-your-day__trip-item{grid-template-columns:minmax(0,1fr) auto}.plan-your-day__trip-tools{align-self:center;justify-content:end}}@container (min-width:60rem){.plan-your-day__layout{grid-template-columns:minmax(18rem,30rem) minmax(0,1fr)}.plan-your-day__preview-card{position:sticky;top:7.75rem}}@media (max-width:979px){.plan-your-day__card-header,.plan-your-day__summary-top{flex-direction:column;align-items:start}}@media (max-width:639px){.plan-your-day__card,.plan-your-day__surface{border-radius:1rem}.plan-your-day__surface{padding:1rem}.plan-your-day__category-option-body,.plan-your-day__start-option-body{padding-left:2.75rem}.plan-your-day__category-trigger{align-items:start}.plan-your-day__category-trigger-side{gap:.55rem}.plan-your-day__category-results-scroll{max-height:min(22rem,55vh)}.plan-your-day__actions,.plan-your-day__reorder-links,.plan-your-day__result-tools,.plan-your-day__trip-tools{width:100%}.plan-your-day__category-search-button,.plan-your-day__clear-link,.plan-your-day__maps-link,.plan-your-day__reorder-disabled,.plan-your-day__result-add,.plan-your-day__result-link,.plan-your-day__submit,.plan-your-day__trip-tools a,.plan-your-day__trip-tools button{width:100%}.plan-your-day__summary-grid{grid-template-columns:1fr}.plan-your-day__map-frame{min-height:20rem}}@media (min-width:768px){.plan-your-day__category-description{display:block}.plan-your-day__category-search-controls{grid-template-columns:minmax(0,1fr) auto;align-items:center}}@media (max-width:767px){.plan-your-day__category-description{display:none}} \ No newline at end of file diff --git a/plugin/plan-your-day/assets/js/plan.min.js b/plugin/plan-your-day/assets/js/plan.min.js deleted file mode 100644 index 46565b4..0000000 --- a/plugin/plan-your-day/assets/js/plan.min.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{const t="planYourDayEnhanced",e="planYourDayColorMode",a="light",n="dark",r="system",o=[a,n],s=[...o,r],i="checking",l="found",u="not_found",d=[i,l,u],c=t=>String(t??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"),p=(t,e={})=>{let a=String(t||"");return Object.entries(e||{}).forEach((([t,e])=>{a=a.split(`{${t}}`).join(String(e??""))})),a},y=(t,e="")=>Array.isArray(t)?t.map((t=>y(t))):t&&"object"==typeof t?Object.fromEntries(Object.entries(t).map((([t,e])=>{const a=String(t).toLowerCase();return[t,a.includes("token")||a.includes("api_key")||a.includes("authorization")||a.includes("cookie")||a.includes("secret")?"[redacted]":y(e,t)]}))):"string"==typeof t?String(e).toLowerCase().includes("token")?"[redacted]":t.replace(/([?&](?:key|api_key|token)=)[^&]+/gi,"$1[redacted]"):t,g=(t,e,a,n={})=>{if(!t?.debug||"undefined"==typeof console)return;("function"==typeof console[e]?console[e]:console.log).call(console,`[plan-your-day] ${a}`,y(n))},m=t=>Array.isArray(t)?t.map((t=>String(t??""))).filter(Boolean):[],S=(t,e)=>{t&&e&&(t.textContent="",window.requestAnimationFrame((()=>{t.textContent=e})))},f=t=>o.includes(String(t||"")),b=t=>{const e=String(t||"");return s.includes(e)?e:a},h=()=>{try{const t=window.localStorage?.getItem(e);return f(t)?t:""}catch(t){return""}},w=()=>"undefined"==typeof window||"function"!=typeof window.matchMedia?null:window.matchMedia("(prefers-color-scheme: dark)"),v=(t,e=w())=>{const o=h();if(f(o))return o;const s=b(t);return s===r?e?.matches?n:a:s},M=(t,e,r,o)=>{const s=f(r)?r:a;t.setAttribute("data-plan-color-mode",s),((t,e,a)=>{t.colorModeToggle instanceof HTMLButtonElement&&(t.colorModeToggle.hidden=!1,t.colorModeToggle.setAttribute("aria-pressed",String(e===n)),t.colorModeToggle.setAttribute("aria-label",String(a.darkModeLabel||"Dark mode")),t.colorModeToggleLabel instanceof HTMLElement&&(t.colorModeToggleLabel.textContent=String(a.darkModeLabel||"Dark mode")))})(e,s,o)},L=t=>{const e=t.find((t=>t.checked));return e?e.value:""},R=t=>{const e=String(t||"");return d.includes(e)?e:""},E=(t,e)=>{const a=t.form instanceof HTMLFormElement?new FormData(t.form):new FormData;return{category:String(a.get("category")||e.category||""),category_search:String(a.get("category_search")||e.categorySearch||""),waypoints:a.getAll("waypoints[]").map((t=>String(t||""))).filter(Boolean),start_mode:String(a.get("start_mode")||e.startMode||"default"),custom_start:String(a.get("custom_start")||"")}},_=(t,e,a)=>{const n=String(t?.id||""),r=String(t?.label||""),o=String(t?.address||""),s=String(t?.distance_label||""),i=String(t?.maps_uri||""),l=e.includes(n);return`\n
  • \n
    \n

    ${c(r)}

    \n ${s?`

    ${c(s)}

    `:""}\n

    ${c(o)}

    \n
    \n
    \n ${i?`${c(a.viewInGoogleMaps||"")}`:""}\n ${l?`${c(a.inTrip||"")}`:``}\n
    \n
  • \n `},A=(t,e,a=!1)=>{if(!t?.hasMoreResults)return"";const n=String(a?e.loadingMoreResults||e.moreResultsButton||"":e.moreResultsButton||"");return`\n
    \n \n
    \n `},q=(t,e,a,n={})=>`\n
    \n ${((t,e,a)=>{const n=Array.isArray(t?.searchResults)?t.searchResults:[];if(0===n.length){const e=t?.resultsEmptyState||{};return`\n
    \n

    ${c(e.heading||"")}

    \n

    ${c(e.body||"")}

    \n
    \n `}return`\n
      \n ${n.map((t=>_(t,e,a))).join("")}\n
    \n `})(t,e,a)}\n
    \n ${A(t,a,Boolean(n.isLoadingMore))}\n `,T=(t,e)=>{const a=new Set((t=>{const e=[];return(Array.isArray(t?.searchResults)?t.searchResults:[]).forEach((t=>{const a=String(t?.id||"");a&&!e.includes(a)&&e.push(a)})),e})(t));return(Array.isArray(e?.searchResults)?e.searchResults:[]).filter((t=>{const e=String(t?.id||"");return!e||!a.has(e)&&(a.add(e),!0)}))},H=(t,e)=>{t.categoryInput&&(t.categoryInput.value=e.category||""),t.waypointInputs&&(t.waypointInputs.innerHTML=m(e.route?.selectedWaypointIds).map((t=>``)).join(""))},k=(t,e)=>{if(!t.messages)return;const a=Array.isArray(e)?e:[];t.messages.hidden=0===a.length,t.messages.innerHTML=(t=>(Array.isArray(t)?t:[]).map((t=>{const e=String(t?.type||"note"),a=String(t?.text||""),n="warning"===e?"alert":"";return`
  • ${c(a)}
  • `})).join(""))(a)},$=(t,e,a,n)=>{if(!(t instanceof HTMLElement))return;const r=t.querySelector("[data-plan-load-more-wrap]"),o=A(e,a,n);o?r instanceof HTMLElement?r.outerHTML=o:t.insertAdjacentHTML("beforeend",o):r instanceof HTMLElement&&r.remove()},x=(t,e,a,n,r={})=>{if(!(t instanceof HTMLElement))return;const o=Boolean(r.appendResults),s=Array.isArray(r.appendedResults)?r.appendedResults:[];o&&(s.length>0&&((t,e,a,n)=>{if(!(t instanceof HTMLElement&&Array.isArray(e)&&0!==e.length))return!1;const r=t.querySelector("[data-plan-results-list]");if(!(r instanceof HTMLElement))return!1;const o=t.querySelector("[data-plan-results-empty]");return o instanceof HTMLElement&&o.remove(),r.insertAdjacentHTML("beforeend",e.map((t=>_(t,a,n))).join("")),!0})(t,s,a,n)||0===s.length&&(t=>t instanceof HTMLElement&&t.querySelector("[data-plan-results-list], [data-plan-results-empty]")instanceof HTMLElement)(t))?$(t,e,n,Boolean(r.isLoadingMore)):t.innerHTML=q(e,a,n,r)},C=(t,e,a,n={})=>{const r=e.category||"",o=e.expandedCategory||"",s=m(e.route?.selectedWaypointIds),i=e.browse||{},l=Boolean(i.hasSearch)&&!r,u=l||0===t.categoryButtons.length,d=l&&Boolean(e.customResultsExpanded)||!l&&0===t.categoryButtons.length,c={appendResults:Boolean(n.appendResults),appendedResults:Array.isArray(n.appendedResults)?n.appendedResults:[],isLoadingMore:Boolean(n.isLoadingMore)};t.categoryButtons.forEach((t=>{const e=t.getAttribute("data-category-key")||"",a=e===r&&e===o,n=t.closest(".plan-your-day__category-accordion-item");t.setAttribute("aria-expanded",String(a)),n instanceof HTMLElement&&n.classList.toggle("is-expanded",a)})),t.categoryRegions.forEach((t=>{const e=t.getAttribute("data-category-key")||"",n=e===r&&e===o,l=t.querySelector("[data-plan-category-results-panel]");if(t.hidden=!n,l instanceof HTMLElement){if(!n)return void(l.innerHTML="");x(l,i,s,a,c)}})),t.customResults&&(t.customResults.hidden=!u,t.customResults.classList.toggle("is-expanded",d)),t.customResultsButton&&t.customResultsButton.setAttribute("aria-expanded",String(d)),t.customResultsRegion&&(t.customResultsRegion.hidden=!d),t.customResultsHeading&&(t.customResultsHeading.textContent=l?p(a.searchResultsFor||"",{search:i.categoryLabel||""}):String(i?.resultsEmptyState?.heading||"")),t.customResultsDescription&&(t.customResultsDescription.textContent=String(l?a.customSearchResultsDescription||"":i?.resultsEmptyState?.body||"")),t.customResultsPanel&&(d?l?x(t.customResultsPanel,i,s,a,c):t.customResultsPanel.innerHTML=q(i,s,a,{isLoadingMore:!1}):t.customResultsPanel.innerHTML="")},I=(t,e,a)=>{t.tripHeaderActions&&(t.tripHeaderActions.innerHTML=((t,e)=>{const a=m(t?.selectedWaypointIds),n=String(t?.tripCountLabel||"");return`\n ${c(n)}\n ${a.length>0?``:""}\n `})(e.route,a)),t.tripRegion&&(t.tripRegion.innerHTML=((t,e,a)=>{const n=Array.isArray(t?.tripWaypoints)?t.tripWaypoints:[];if(0===n.length){const a=t?.tripEmptyState||{};return`\n
    \n

    ${c(a.heading||e.tripEmptyHeading||"")}

    \n

    ${c(a.body||e.tripEmptyBody||"")}

    \n
    \n `}return`\n
      \n ${n.map(((t,a)=>{const r=String(t?.id||""),o=String(t?.label||""),s=String(t?.address||""),i=a>0,l=a\n
      \n \n
      \n

      ${c(o)}

      \n

      ${c(s)}

      \n
      \n
      \n
      \n \n ${c(e.moveUp||"")}\n \n \n ${c(e.moveDown||"")}\n \n \n
      \n \n `})).join("")}\n
    \n `})(e.route,a,t.tripRegion.getAttribute("data-plan-trip-help-id")||""))},B=(t,e,a)=>{const n=e.route||{},r=String(n.iframeSrc||""),o=n.emptyPreviewState||{},s=String(n.mapsUrl||""),i=m(n.selectedWaypointIds).length>0;k(t,n.messages),t.mapWrap&&(t.mapWrap.hidden=""===r),t.iframe&&(t.iframe.src=r),t.previewEmpty&&(t.previewEmpty.hidden=""!==r),t.previewEmptyHeading&&(t.previewEmptyHeading.textContent=String(o.heading||"")),t.previewEmptyBody&&(t.previewEmptyBody.textContent=String(o.body||"")),t.summaryCount&&(t.summaryCount.textContent=String(n.tripCountLabel||""),t.summaryCount.hidden=i),t.openLinkLabel&&(t.openLinkLabel.textContent=String(n.mapsLinkLabel||"")),t.openLink&&(t.openLink.hidden=!i,t.openLink.classList.toggle("is-disabled",""===s),s?(t.openLink.href=s,t.openLink.removeAttribute("aria-disabled"),t.openLink.removeAttribute("tabindex"),t.openLink.removeAttribute("role")):(t.openLink.removeAttribute("href"),t.openLink.setAttribute("aria-disabled","true"),t.openLink.setAttribute("tabindex","0"),t.openLink.setAttribute("role","button")))},D=t=>t instanceof HTMLElement&&"function"==typeof t.focus&&(t.focus(),document.activeElement===t),F=t=>{if(!(t instanceof HTMLButtonElement))return null;if(t.matches('[data-plan-action="add-waypoint"]'))return{action:"add-waypoint",placeId:t.getAttribute("data-place-id")||t.value||""};if(t.matches('[data-plan-action="remove-waypoint"]'))return{action:"remove-waypoint",placeId:t.getAttribute("data-place-id")||t.value||""};if(t.matches("[data-plan-clear-trip]"))return{action:"clear-trip",placeId:""};if("move_waypoint"===t.name&&t.value){const[e,a]=String(t.value).split(":",2);return{action:"move-waypoint",placeId:e||"",direction:a||""}}return null},P=t=>0===m(t?.selectedWaypointIds).length,U=(t,e,a)=>{const n=R(e);t.customStartWrap instanceof HTMLElement&&t.customStartWrap.setAttribute("data-plan-custom-start-state",n),t.customStartStatus instanceof HTMLElement&&(t.customStartStatus.textContent=((t,e)=>t===i?String(e.customStartChecking||"Checking starting address."):t===l?String(e.customStartFound||"Starting address found. Results are ready."):t===u?String(e.customStartNotFound||"Starting address was not found."):"")(n,a))},j=(t,e,a)=>{t.startModeInputs.forEach((t=>{t.checked=t.value===e.startMode})),t.customStartInput&&(t.customStartInput.value=e.customStart||"");const n="custom"===(L(t.startModeInputs)||e.startMode||"default");t.customStartWrap&&(t.customStartWrap.hidden=!n),t.customStartInput&&(t.customStartInput.disabled=!n),U(t,n?e.customStartStatus:"",a)},W=(t,e)=>{if(!(t.categorySearchInput instanceof HTMLInputElement))return;const a=String(e.categorySearch||"");t.categorySearchInput.value!==a&&(t.categorySearchInput.value=a)},O=(t,e)=>{t.classList.toggle("is-submitting",e),t.setAttribute("aria-busy",String(e))},K=(t,e,a)=>{t.forEach((t=>{if(!(t instanceof HTMLButtonElement||t instanceof HTMLInputElement))return;if(e)return t.hasAttribute(a)||t.setAttribute(a,t.disabled?"true":"false"),void(t.disabled=!0);const n=t.getAttribute(a);null!==n&&(t.disabled="true"===n,t.removeAttribute(a))}))},N=(t,e)=>{K(t.querySelectorAll("[data-plan-route-mutation]"),e,"data-plan-disabled-before-request")},J=(t,e)=>{K(t.querySelectorAll(['[data-plan-form] button[type="submit"]:not([data-plan-route-mutation])',"[data-plan-load-more-button]","[data-plan-start-toggle]","[data-plan-custom-results-button]",'input[name="start_mode"]',"[data-plan-custom-start]","[data-plan-category-search]"].join(",")),e,"data-plan-browse-disabled-before-request")},G=(t,e,a)=>{const n=e.category||"";if(t.categoryPanels.forEach((e=>{const r=e.getAttribute("data-category-key")||"",o=0===t.categoryButtons.length||r===n||!1===t.customResults?.hidden;e.setAttribute("aria-busy",String(a&&o))})),t.customResultsPanel instanceof HTMLElement){const e=!1===t.customResults?.hidden;t.customResultsPanel.setAttribute("aria-busy",String(a&&e))}t.tripRegion instanceof HTMLElement&&t.tripRegion.setAttribute("aria-busy",String(a)),t.previewCard instanceof HTMLElement&&t.previewCard.setAttribute("aria-busy",String(a))},Y=o=>{if(!(o instanceof HTMLElement)||"true"===o.dataset[t])return;o.dataset[t]="true";const s=(t=>{const e=t.querySelector("[data-plan-config]");if(!e)return{};try{return JSON.parse(e.textContent||"{}")}catch(t){return{}}})(o),l=s.strings||{},u={form:o.querySelector("[data-plan-form]"),liveRegion:o.querySelector("[data-plan-live-region]"),categoryInput:o.querySelector("[data-plan-category-input]"),waypointInputs:o.querySelector("[data-plan-waypoint-inputs]"),categoryButtons:Array.from(o.querySelectorAll("[data-plan-category-button]")),categoryItems:Array.from(o.querySelectorAll("[data-plan-category-item]")),categoryRegions:Array.from(o.querySelectorAll("[data-plan-category-region]")),categoryPanels:Array.from(o.querySelectorAll("[data-plan-category-results-panel]")),categorySearchInput:o.querySelector("[data-plan-category-search]"),customResults:o.querySelector("[data-plan-custom-results]"),customResultsButton:o.querySelector("[data-plan-custom-results-button]"),customResultsHeading:o.querySelector("[data-plan-custom-results-heading]"),customResultsDescription:o.querySelector("[data-plan-custom-results-description]"),customResultsRegion:o.querySelector("[data-plan-custom-results-region]"),customResultsPanel:o.querySelector("[data-plan-custom-results-panel]"),resultsHeading:o.querySelector("[data-plan-results-heading]"),startModeInputs:Array.from(o.querySelectorAll('input[name="start_mode"]')),customStartWrap:o.querySelector("[data-plan-custom-start-wrap]"),customStartInput:o.querySelector("[data-plan-custom-start]"),customStartStatus:o.querySelector("[data-plan-custom-start-status]"),startToggle:o.querySelector("[data-plan-start-toggle]"),startToggleLabel:o.querySelector("[data-plan-start-toggle-label]"),startPanel:o.querySelector("[data-plan-start-panel]"),colorModeToggle:o.querySelector("[data-plan-color-mode-toggle]"),colorModeToggleLabel:o.querySelector("[data-plan-color-mode-toggle-label]"),tripHeaderActions:o.querySelector("[data-plan-trip-header-actions]"),tripHeading:o.querySelector("[data-plan-trip-heading]"),tripRegion:o.querySelector("[data-plan-trip-region]"),messages:o.querySelector("[data-plan-messages]"),previewCard:o.querySelector("[data-plan-preview-card]"),mapWrap:o.querySelector("[data-plan-map-wrap]"),iframe:o.querySelector("[data-plan-iframe]"),previewEmpty:o.querySelector("[data-plan-preview-empty]"),previewEmptyHeading:o.querySelector("[data-plan-preview-empty-heading]"),previewEmptyBody:o.querySelector("[data-plan-preview-empty-body]"),summaryCount:o.querySelector("[data-plan-summary-count]"),openLink:o.querySelector("[data-plan-open-link]"),openLinkLabel:o.querySelector("[data-plan-open-link-label]")},d={category:String(s.initialState?.category||""),categorySearch:String(s.initialState?.categorySearch||""),startMode:String(s.initialState?.startMode||"default"),customStart:String(s.initialState?.customStart||""),customStartStatus:R(s.initialData?.browse?.customStartStatus||""),expandedCategory:String(s.initialState?.category||""),customResultsExpanded:Boolean(s.initialData?.browse?.isCustomSearch),isLoadingMore:!1,browse:s.initialData?.browse||{},route:s.initialData?.route||{}},c="string"==typeof s.rest?.bootstrapUrl&&""!==s.rest.bootstrapUrl;let y="string"==typeof s.rest?.endpointToken?s.rest.endpointToken:"",m=!1,_=null;const A=u.form instanceof HTMLFormElement&&"string"==typeof s.rest?.browseUrl&&""!==s.rest.browseUrl&&"string"==typeof s.rest?.routeUrl&&""!==s.rest.routeUrl&&(""!==y||c),q=Boolean(s.hydration?.shouldHydrateOnLoad),$=b(s.colorModeDefault||o.getAttribute("data-plan-color-mode-default")),x=w();let U=!0,K=null,Y="",z=0,Q=null;const V="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches;let X=0;M(o,u,v($,x),l),$===r&&x&&x.addEventListener("change",(()=>{h()||M(o,u,v($,x),l)}));const Z=()=>{C(u,d,l,{isLoadingMore:d.isLoadingMore}),I(u,d,l),B(u,d),H(u,d),j(u,d,l),W(u,d),N(o,!1)},tt=(t,e="")=>{k(u,[{type:"warning",text:t||l.requestFailed||""}]),S(u.liveRegion,e||t||l.requestFailed||"")},et=(t={})=>{if(!u.startToggle||!u.startPanel)return;const e=!1!==t.syncHidden;u.startToggle.hidden=!1,u.startToggle.setAttribute("aria-expanded",String(U)),u.startToggle.classList.toggle("is-collapsed",!U),e&&(u.startPanel.hidden=!U),u.startToggleLabel&&(u.startToggleLabel.textContent=String(U?l.hideStartOptions||"Hide options":l.showStartOptions||"Show options"))},at=t=>{if(!(u.startPanel instanceof HTMLElement&&u.startToggle))return U=t,void et();const e=u.startPanel,a=V?0:480;X&&(window.cancelAnimationFrame(X),X=0);const n=e.hidden?0:e.getBoundingClientRect().height;e.hidden=!1,t&&(e.style.height="");const r=t?e.scrollHeight:0,o=t&&r>0?Math.max(n/r,0):1,s=t?1:0;if(U=t,e.style.overflow="hidden",e.style.pointerEvents="none",e.style.height=`${n}px`,e.style.opacity=String(o),et({syncHidden:!1}),a<=0||n===r)return e.style.height="",e.style.overflow="",e.style.pointerEvents="",e.style.opacity="",e.hidden=!t,void et();const i=performance.now(),l=u=>{const d=Math.min((u-i)/a,1),c=(p=d)<.5?4*p*p*p:1-Math.pow(-2*p+2,3)/2;var p;const y=n+(r-n)*c,g=o+(s-o)*c;e.style.height=`${Math.max(y,0)}px`,e.style.opacity=String(Math.max(Math.min(g,1),0)),d<1?X=window.requestAnimationFrame(l):(e.style.height="",e.style.overflow="",e.style.pointerEvents="",e.style.opacity="",e.hidden=!t,et(),X=0)};X=window.requestAnimationFrame(l)},nt=async(t,e,a={})=>{if(!A)return"unsupported";const n=Boolean(a.appendBrowseResults),r=String(a.announcementMessage||""),f=String(a.errorMessage||""),b=String(a.searchContextKey||""),h="browse"===t&&!1!==a.refreshRoute,w="route"===t?a.routeFocusRequest??null:null,v=String(e.start_mode||""),M=String(e.custom_start||""),L="browse"===t&&!n&&"custom"===v&&""!==M.trim(),E="route"===t&&(d.customStartStatus===i||"custom"!==v||""===M.trim()||M!==String(d.customStart||""));if(K instanceof AbortController){if("route"===Y)return g(s,"info","request:blocked",{endpointKey:t,blockedBy:Y}),"busy";K.abort()}z+=1;const q=z;"route"===t&&(Q=w?{requestId:q,focusRequest:w}:null),K=new AbortController,Y=t,O(o,!0),G(u,d,!0),N(o,"route"===t),J(o,"route"===t),L?(d.customStartStatus=i,j(u,d,l)):("browse"===t&&!n||E)&&(d.customStartStatus="",j(u,d,l)),g(s,"info","request:start",{endpointKey:t,payload:e});try{const a=await(async()=>c?m&&""!==y?y:_||(_=fetch(s.rest.bootstrapUrl,{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({})}).then((async t=>{const e=await t.json().catch((()=>({})));if(g(s,t.ok?"info":"warn","request:bootstrap",{status:t.status,ok:t.ok,body:e}),!t.ok)throw new Error(e?.message||l.requestFailed||"");const a=String(e?.endpointToken||"");if(""===a)throw new Error(l.requestFailed||"");return y=a,m=!0,y})).finally((()=>{_=null})),_):y)();if(""===a)throw new Error(l.requestFailed||"");const i={...e,endpoint_token:a};"browse"===t&&(i.refresh_route=h,""!==b&&(i.search_context_key=b));const w=await fetch(s.rest["browse"===t?"browseUrl":"routeUrl"],{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(i),signal:K.signal}),v=await w.json().catch((()=>({})));if(g(s,w.ok?"info":"warn","request:response",{endpointKey:t,status:w.status,ok:w.ok,body:v}),!w.ok)throw new Error(v?.message||l.requestFailed||"");if(q!==z)return!0;if("browse"===t){const t=d.category||"",a=d.expandedCategory||"",r=d.categorySearch||"",s=v?.browse||{};if(n&&""!==String(s.searchResultsError||""))return d.isLoadingMore=!1,C(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!1}),tt(f||l.requestFailed||"",f||l.requestFailed||""),"failed";const i=n&&""!==b&&b===String(d.browse?.searchContextKey||"")&&b===String(s.searchContextKey||""),c=i?T(d.browse,s):[];if(d.browse=i?{...(k=d.browse)||{},...($=s)||{},searchResults:[...Array.isArray(k?.searchResults)?k.searchResults:[],...T(k,$)]}:s,d.route=v?.route||d.route||{},d.category=String(d.browse.categoryKey||e.category||""),d.categorySearch=String(d.browse.categorySearch||e.category_search||""),d.customStartStatus=R(d.browse.customStartStatus||""),d.isLoadingMore=!1,d.category?(d.expandedCategory=d.category===t?a:d.category,d.customResultsExpanded=!1):d.browse.hasSearch?String(e.category_search||"")!==r&&(d.expandedCategory="",d.customResultsExpanded=!0):(d.expandedCategory="",d.customResultsExpanded=!1),i)return C(u,d,l,{appendResults:!0,appendedResults:c,isLoadingMore:!1}),I(u,d,l),B(u,d),H(u,d),j(u,d,l),W(u,d),N(o,!1),S(u.liveRegion,d.browse?.searchResultsError?f||l.requestFailed||"":((t,e,a)=>t>0?p(a.loadedMoreResults||"",{count:t}):String(e?.hasMoreResults?a.resultsUpdated||"":a.noMoreResults||""))(c.length,d.browse,l)),"success"}else d.route=v?.route||d.route||{},d.category=String(d.route.categoryKey||d.category||""),d.categorySearch=String(d.route.categorySearch||e.category_search||"");return d.startMode=String(e.start_mode||d.startMode||"default"),d.customStart=String(e.custom_start||""),Z(),"route"===t&&Q&&Q.requestId===q&&(((t,e)=>{if(!e)return;const a=String(e.placeId||""),n=t.tripHeaderActions?.querySelector("button:not([disabled]):not([hidden])");let r=null;if(a&&"add-waypoint"===e.action)r=t.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="remove_waypoint"]`);else if(a&&"move-waypoint"===e.action){const n=String(e.direction||"");r=t.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="move_waypoint"][value="${a}:${n}"]`)||t.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="remove_waypoint"]`)}else"remove-waypoint"===e.action?r=t.tripRegion?.querySelector('[data-plan-trip-list] button[name="remove_waypoint"]')||n:"clear-trip"===e.action&&(r=n||t.tripHeading);D(r)||D(t.tripHeading)})(u,Q.focusRequest),Q=null),S(u.liveRegion,r||""),"success"}catch(a){return"AbortError"===a?.name?(g(s,"info","request:aborted",{endpointKey:t}),q===z&&L&&(d.customStartStatus="",j(u,d,l)),"aborted"):q!==z?"stale":(g(s,"error","request:failed",{endpointKey:t,error:a instanceof Error?a.message:String(a||""),payload:e}),n&&(d.isLoadingMore=!1,C(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!1})),tt(n?f||l.requestFailed||"":a instanceof Error?a.message:l.requestFailed||"",n&&(f||l.requestFailed)||""),L&&(d.customStartStatus="",j(u,d,l)),"failed")}finally{q===z&&(n&&d.isLoadingMore&&(d.isLoadingMore=!1),O(o,!1),G(u,d,!1),N(o,!1),"route"===t&&Q&&Q.requestId===q&&(Q=null),J(o,!1),K=null,Y="")}var k,$};if(u.startModeInputs.forEach((t=>{t.addEventListener("change",(()=>{d.startMode=L(u.startModeInputs)||d.startMode||"default",j(u,d,l),A?nt("browse",E(u,d),{announcementMessage:l.startingPointUpdated||"",refreshRoute:!0}):S(u.liveRegion,l.startingPointUpdated||"")}))})),u.customStartInput instanceof HTMLInputElement&&u.customStartInput.addEventListener("change",(()=>{d.customStart=u.customStartInput.value||"",j(u,d,l),A?nt("browse",E(u,d),{announcementMessage:l.startingPointUpdated||"",refreshRoute:!0}):S(u.liveRegion,l.startingPointUpdated||"")})),u.categorySearchInput instanceof HTMLInputElement&&(u.categorySearchInput.addEventListener("input",(()=>{d.categorySearch=u.categorySearchInput.value||""})),u.categorySearchInput.addEventListener("keydown",(t=>{if("Enter"!==t.key||!A)return;t.preventDefault();const e=E(u,d);e.category="",e.category_search=u.categorySearchInput.value||"",d.expandedCategory="",d.customResultsExpanded=!0,nt("browse",e,{announcementMessage:l.resultsUpdated||"",refreshRoute:P(d.route)})}))),u.form instanceof HTMLFormElement&&u.form.addEventListener("submit",(t=>{const e=t.submitter;if(!(e instanceof HTMLButtonElement&&A))return;let a="browse",n=l.resultsUpdated||"";const r=E(u,d);if(e.matches("[data-plan-category-button]")){const a=e.getAttribute("data-category-key")||"";if(a===d.category){const n=e.querySelector(".plan-your-day__category-title")?.textContent?.trim()||a;return t.preventDefault(),d.expandedCategory=d.expandedCategory===a?"":a,d.customResultsExpanded=!1,C(u,d,l),void S(u.liveRegion,d.expandedCategory===a?p(l.categoryResultsExpanded||"",{category:n}):p(l.categoryResultsCollapsed||"",{category:n}))}r.category=a,r.category_search="",d.expandedCategory=a,d.customResultsExpanded=!1}else e.matches('[data-plan-action="search-category-query"]')?(r.category="",r.category_search=u.categorySearchInput instanceof HTMLInputElement&&u.categorySearchInput.value||"",d.expandedCategory="",d.customResultsExpanded=!0):e.matches('[data-plan-action="add-waypoint"]')?(r.waypoints=[...r.waypoints,e.getAttribute("data-place-id")||e.value||""],a="route",n=l.tripUpdated||""):e.matches('[data-plan-action="remove-waypoint"]')?(r.remove_waypoint=e.getAttribute("data-place-id")||e.value||"",a="route",n=l.tripUpdated||""):e.matches("[data-plan-clear-trip]")?(r.clear_trip=!0,a="route",n=l.tripUpdated||""):"move_waypoint"===e.name&&e.value&&(r.move_waypoint=e.value,a="route",n=l.tripUpdated||"");t.preventDefault(),nt(a,r,{announcementMessage:n,refreshRoute:"browse"===a?P(d.route):void 0,routeFocusRequest:"route"===a?F(e):null})})),o.addEventListener("click",(t=>{const r=t.target;if(!(r instanceof HTMLElement))return;if(r.closest('[data-plan-open-link][aria-disabled="true"]')instanceof HTMLElement)return t.preventDefault(),void S(u.liveRegion,l.openMapsDisabled||"");if(r.closest("[data-plan-start-toggle]")instanceof HTMLButtonElement)return t.preventDefault(),U?U&&at(!1):U||at(!0),void S(u.liveRegion,U?l.startOptionsExpanded||"":l.startOptionsCollapsed||"");if(r.closest("[data-plan-color-mode-toggle]")instanceof HTMLButtonElement){t.preventDefault();const r=(o.getAttribute("data-plan-color-mode")===n?n:a)===n?a:n;return(t=>{if(f(t))try{window.localStorage?.setItem(e,t)}catch(t){}})(r),void M(o,u,r,l)}if(r.closest("[data-plan-custom-results-button]")instanceof HTMLButtonElement){if(t.preventDefault(),!d.browse?.hasSearch||d.category)return;return d.expandedCategory="",d.customResultsExpanded=!d.customResultsExpanded,C(u,d,l),void S(u.liveRegion,d.customResultsExpanded?String(l.customResultsExpanded||""):String(l.customResultsCollapsed||""))}const s=r.closest("[data-plan-load-more-button]");if(s instanceof HTMLButtonElement){if(t.preventDefault(),!A||d.isLoadingMore||s.disabled)return;const e=String(d.browse?.nextPageToken||"");if(!e)return void S(u.liveRegion,l.noMoreResults||"");d.isLoadingMore=!0,C(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!0}),S(u.liveRegion,l.loadingMoreResults||""),nt("browse",{...E(u,d),page_token:e,append_results:!0},{appendBrowseResults:!0,errorMessage:l.loadMoreError||"",refreshRoute:!1,searchContextKey:String(d.browse?.searchContextKey||"")})}})),o.addEventListener("keydown",(t=>{const e=t.target;if(!(e instanceof HTMLElement))return;e.closest('[data-plan-open-link][aria-disabled="true"]')instanceof HTMLElement&&("Enter"!==t.key&&" "!==t.key||(t.preventDefault(),S(u.liveRegion,l.openMapsDisabled||"")))})),Z(),et(),o.classList.add("is-enhanced"),q){if(!A)return void tt(l.requestFailed||"");nt("browse",E(u,d),{refreshRoute:!0})}},z=()=>{document.querySelectorAll("[data-plan-root]").forEach(Y)};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",z,{once:!0}):z()})(); \ No newline at end of file diff --git a/plugin/plan-your-day/tests/PluginDisplayNameTest.php b/plugin/plan-your-day/tests/PluginDisplayNameTest.php deleted file mode 100644 index 15e27b4..0000000 --- a/plugin/plan-your-day/tests/PluginDisplayNameTest.php +++ /dev/null @@ -1,45 +0,0 @@ -fixture( 'plan-your-day.php' ) ); - - $release = json_decode( $this->fixture( 'release.json' ), true ); - self::assertIsArray( $release ); - self::assertSame( 'Waypoints', $release['name'] ?? null ); - - $block = json_decode( $this->fixture( 'blocks/planner/block.json' ), true ); - self::assertIsArray( $block ); - self::assertSame( 'Waypoints', $block['title'] ?? null ); - self::assertSame( 'Render the Waypoints planner through the block editor.', $block['description'] ?? null ); - } - - public function test_public_readme_uses_waypoints_display_name(): void { - $readme = $this->fixture( 'readme.txt' ); - - self::assertStringContainsString( '=== Waypoints ===', $readme ); - self::assertStringContainsString( 'Waypoints is a configurable day planning plugin for WordPress.', $readme ); - self::assertStringContainsString( 'Open Settings > Waypoints', $readme ); - self::assertStringNotContainsString( 'Plan Your Day', $readme ); - } - - public function test_block_editor_placeholder_uses_waypoints_display_name(): void { - $script = $this->fixture( 'assets/js/plan-block.js' ); - - self::assertStringContainsString( "__( 'Waypoints', 'plan-your-day' )", $script ); - self::assertStringNotContainsString( "__( 'Plan Your Day', 'plan-your-day' )", $script ); - } - - private function fixture( string $path ): string { - $content = file_get_contents( dirname( __DIR__ ) . '/' . $path ); - - self::assertIsString( $content ); - - return $content; - } -} diff --git a/plugin/plan-your-day/.distignore b/plugin/waypoints/.distignore similarity index 100% rename from plugin/plan-your-day/.distignore rename to plugin/waypoints/.distignore diff --git a/plugin/plan-your-day/DECISIONS.md b/plugin/waypoints/DECISIONS.md similarity index 100% rename from plugin/plan-your-day/DECISIONS.md rename to plugin/waypoints/DECISIONS.md diff --git a/plugin/plan-your-day/assets/css/admin-settings.css b/plugin/waypoints/assets/css/admin-settings.css similarity index 100% rename from plugin/plan-your-day/assets/css/admin-settings.css rename to plugin/waypoints/assets/css/admin-settings.css diff --git a/plugin/plan-your-day/assets/css/plan.css b/plugin/waypoints/assets/css/plan.css similarity index 94% rename from plugin/plan-your-day/assets/css/plan.css rename to plugin/waypoints/assets/css/plan.css index baab8fe..bbf8051 100644 --- a/plugin/plan-your-day/assets/css/plan.css +++ b/plugin/waypoints/assets/css/plan.css @@ -258,6 +258,7 @@ .plan-your-day { width: min(100% - 2rem, 1180px); margin: clamp(1rem, 4vw, 3rem) auto; + padding-bottom: calc(4.75rem + env(safe-area-inset-bottom)); color: var(--pyd-text); container-type: inline-size; } @@ -272,6 +273,7 @@ border-radius: 1.5rem; background: var(--pyd-surface-bg); box-shadow: var(--pyd-surface-shadow); + position: relative; } .plan-your-day__hero { @@ -400,6 +402,86 @@ white-space: nowrap; } +.plan-your-day__waypoint-status-wrap { + position: sticky; + z-index: 50; + right:0; + left: 0; + bottom:.5dvh; + padding-top: 10px; + pointer-events: none; +} + +.plan-your-day__waypoint-status { + display: inline-flex; + align-items: center; + justify-content: center; + width: 100%; + min-height: 3.25rem; + padding: 0.8rem 1rem; + border: thin solid var(--pyd-accent-border); + border-radius: 0.9rem; + background: var(--pyd-primary-bg); + color: var(--pyd-primary-text); + box-shadow: 0 0.85rem 1.8rem rgba(23, 48, 56, 0.2); + font: inherit; + font-size: 0.98rem; + font-weight: 700; + text-align: center; + pointer-events: auto; + transition: + background-color 0.2s ease, + box-shadow 0.2s ease, + transform 0.2s ease; +} + +.plan-your-day__waypoint-status:hover, +.plan-your-day__waypoint-status:active { + background: var(--pyd-primary-hover-bg); + box-shadow: 0 1rem 2rem rgba(23, 48, 56, 0.24); +} + +.plan-your-day__waypoint-status:active { + transform: translateY(1px); +} + +.plan-your-day__api-call-counter { + position: fixed; + z-index: 60; + top: 50%; + right: max(0.75rem, env(safe-area-inset-right)); + display: grid; + gap: 0.1rem; + min-width: 7.75rem; + padding: 0.55rem 0.7rem; + border: thin solid var(--pyd-accent-border); + border-radius: 0.7rem; + background: var(--pyd-primary-bg); + color: var(--pyd-primary-text); + box-shadow: 0 0.85rem 1.8rem rgba(23, 48, 56, 0.2); + font-size: 0.78rem; + font-weight: 700; + line-height: 1.2; + pointer-events: none; + transform: translateY(-50%); +} + +.plan-your-day__api-call-label, +.plan-your-day__api-call-count { + display: inline; +} + +.plan-your-day__api-call-count { + font-size: 1.35rem; + line-height: 1; +} + +.plan-your-day__api-call-breakdown { + color: var(--pyd-primary-soft-text); + font-size: 0.68rem; + font-weight: 400; +} + .plan-your-day__category-grid, .plan-your-day__start-options, .plan-your-day__results-list, @@ -489,6 +571,7 @@ .plan-your-day__clear-link:focus-visible, .plan-your-day__result-add:focus-visible, .plan-your-day__result-link:focus-visible, +.plan-your-day__waypoint-status:focus-visible, .plan-your-day__trip-tools a:focus-visible, .plan-your-day__trip-tools button:focus-visible { outline: 3px solid var(--focus-ring-color); @@ -1334,6 +1417,13 @@ flex-direction: column; align-items: start; } + + .plan-your-day__api-call-counter { + top: auto; + right: max(0.75rem, env(safe-area-inset-right)); + bottom: calc(4.25rem + env(safe-area-inset-bottom)); + transform: none; + } } @media (max-width: 639px) { diff --git a/plugin/waypoints/assets/css/plan.min.css b/plugin/waypoints/assets/css/plan.min.css new file mode 100644 index 0000000..0135fbf --- /dev/null +++ b/plugin/waypoints/assets/css/plan.min.css @@ -0,0 +1 @@ +@font-face{font-family:"Noto Sans";src:url("../fonts/noto-sans-v42-latin-ext-300.woff2") format("woff2");font-style:normal;font-weight:300;font-display:swap}@font-face{font-family:"Noto Sans";src:url("../fonts/noto-sans-v42-latin-ext-regular.woff2") format("woff2");font-style:normal;font-weight:400;font-display:swap}@font-face{font-family:"Noto Sans";src:url("../fonts/noto-sans-v42-latin-ext-700.woff2") format("woff2");font-style:normal;font-weight:700;font-display:swap}.plan-your-day,.plan-your-day *,.plan-your-day ::after,.plan-your-day ::before{box-sizing:border-box}.plan-your-day{--focus-ring-color:#1168d8;--focus-ring-offset-color:#ffffff;--pyd-font-family:"Noto Sans",system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;--pyd-text:#173038;--pyd-heading:#102027;--pyd-hero-heading:#112127;--pyd-muted:#35545d;--pyd-accent:#8a4f0f;--pyd-accent-soft:rgba(138, 79, 15, 0.2);--pyd-accent-border:rgba(138, 79, 15, 0.42);--pyd-border-subtle:rgba(23, 48, 56, 0.08);--pyd-border:rgba(23, 48, 56, 0.12);--pyd-border-medium:rgba(23, 48, 56, 0.14);--pyd-border-strong:rgba(23, 48, 56, 0.16);--pyd-border-emphasis:rgba(23, 48, 56, 0.22);--pyd-control-mark-border:rgba(23, 48, 56, 0.35);--pyd-surface-bg:radial-gradient(circle at top right, rgba(241, 181, 67, 0.18), transparent 26%),linear-gradient(180deg, #fffdf8 0%, #f4ecdb 100%);--pyd-surface-shadow:0 0.85rem 2rem rgba(23, 48, 56, 0.12);--pyd-card-bg:rgba(255, 255, 255, 0.82);--pyd-card-shadow:0 0.65rem 1.45rem rgba(23, 48, 56, 0.06);--pyd-panel-bg:#fffefb;--pyd-option-bg:#fffdf9;--pyd-option-selected-bg:linear-gradient(180deg, #fffaf0 0%, #f7ecd6 100%);--pyd-option-selected-shadow:0 0.85rem 1.8rem rgba(138, 79, 15, 0.12);--pyd-radio-bg:#ffffff;--pyd-radio-checked-bg:radial-gradient(circle at center, var(--pyd-accent) 0 45%, transparent 48% 100%),#fff7e9;--pyd-chip-bg:rgba(23, 48, 56, 0.08);--pyd-hero-note-bg:rgba(255, 255, 255, 0.72);--pyd-primary-bg:#173038;--pyd-primary-hover-bg:#0f2228;--pyd-primary-text:#fffdf7;--pyd-primary-soft-text:#fffaf0;--pyd-secondary-hover-bg:#f6eddb;--pyd-interactive-hover-bg:rgba(255, 255, 255, 0.38);--pyd-item-bg:linear-gradient(180deg, #fffdf9 0%, #f7f0e0 100%);--pyd-item-hover-shadow:0 0.85rem 1.8rem rgba(23, 48, 56, 0.08);--pyd-item-drag-shadow:0 1.1rem 2rem rgba(23, 48, 56, 0.14);--pyd-feedback-border:rgba(255, 255, 255, 0.55);--pyd-feedback-shadow:0 0.5rem 1rem rgba(23, 48, 56, 0.18);--pyd-disabled-bg:rgba(255, 255, 255, 0.65);--pyd-disabled-text:#5c737a;--pyd-empty-border:rgba(23, 48, 56, 0.26);--pyd-empty-bg:rgba(255, 255, 255, 0.62);--pyd-summary-bg:linear-gradient(180deg, #fffdf7 0%, #f8f1e0 100%);--pyd-summary-tile-bg:rgba(255, 255, 255, 0.7);--pyd-note-border:rgba(23, 48, 56, 0.12);--pyd-note-bg:rgba(223, 236, 241, 0.75);--pyd-note-text:#163038;--pyd-warning-border:rgba(138, 79, 15, 0.2);--pyd-warning-bg:rgba(255, 243, 225, 0.9);--pyd-warning-text:#6e410d;--pyd-success:#167c3b;--pyd-danger:#a73525;--pyd-map-bg:#f3ede0;--pyd-scrollbar-track:rgba(23, 48, 56, 0.08);--pyd-scrollbar-thumb:rgba(138, 79, 15, 0.55);--pyd-scrollbar-thumb-border:rgba(255, 255, 255, 0.65);color-scheme:light;font-family:var(--pyd-font-family);line-height:1.5;padding:0}.plan-your-day[data-plan-color-mode=dark]{--focus-ring-color:#9fc5ff;--focus-ring-offset-color:#232323;--pyd-text:#f3f1ea;--pyd-heading:#fffaf0;--pyd-hero-heading:#fffdf7;--pyd-muted:#c5d0d1;--pyd-accent:#f1b543;--pyd-accent-soft:rgba(241, 181, 67, 0.24);--pyd-accent-border:rgba(241, 181, 67, 0.55);--pyd-border-subtle:rgba(255, 255, 255, 0.08);--pyd-border:rgba(255, 255, 255, 0.12);--pyd-border-medium:rgba(255, 255, 255, 0.14);--pyd-border-strong:rgba(255, 255, 255, 0.18);--pyd-border-emphasis:rgba(255, 255, 255, 0.26);--pyd-control-mark-border:rgba(255, 255, 255, 0.42);--pyd-surface-bg:radial-gradient(circle at top right, rgba(241, 181, 67, 0.14), transparent 28%),linear-gradient(180deg, #2e2e2e 0%, #232323 100%);--pyd-surface-shadow:0 0.85rem 2rem rgba(0, 0, 0, 0.34);--pyd-card-bg:rgba(48, 48, 48, 0.92);--pyd-card-shadow:0 0.65rem 1.45rem rgba(0, 0, 0, 0.24);--pyd-panel-bg:#2b2b2b;--pyd-option-bg:#292929;--pyd-option-selected-bg:linear-gradient(180deg, #3a3427 0%, #2b2925 100%);--pyd-option-selected-shadow:0 0.85rem 1.8rem rgba(0, 0, 0, 0.28);--pyd-radio-bg:#232323;--pyd-radio-checked-bg:radial-gradient(circle at center, var(--pyd-accent) 0 45%, transparent 48% 100%),#302818;--pyd-chip-bg:rgba(255, 255, 255, 0.1);--pyd-hero-note-bg:rgba(255, 255, 255, 0.08);--pyd-primary-bg:#f1b543;--pyd-primary-hover-bg:#ffd36b;--pyd-primary-text:#232323;--pyd-primary-soft-text:#232323;--pyd-secondary-hover-bg:rgba(241, 181, 67, 0.16);--pyd-interactive-hover-bg:rgba(255, 255, 255, 0.08);--pyd-item-bg:linear-gradient(180deg, #303030 0%, #282828 100%);--pyd-item-hover-shadow:0 0.85rem 1.8rem rgba(0, 0, 0, 0.26);--pyd-item-drag-shadow:0 1.1rem 2rem rgba(0, 0, 0, 0.36);--pyd-feedback-border:rgba(241, 181, 67, 0.36);--pyd-feedback-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.28);--pyd-disabled-bg:rgba(255, 255, 255, 0.08);--pyd-disabled-text:#9aa6a8;--pyd-empty-border:rgba(255, 255, 255, 0.24);--pyd-empty-bg:rgba(255, 255, 255, 0.07);--pyd-summary-bg:linear-gradient(180deg, #303030 0%, #292929 100%);--pyd-summary-tile-bg:rgba(255, 255, 255, 0.08);--pyd-note-border:rgba(145, 198, 217, 0.28);--pyd-note-bg:rgba(62, 91, 101, 0.5);--pyd-note-text:#e9f6f9;--pyd-warning-border:rgba(241, 181, 67, 0.34);--pyd-warning-bg:rgba(78, 57, 24, 0.68);--pyd-warning-text:#ffe1a3;--pyd-success:#78d690;--pyd-danger:#ff9a8a;--pyd-map-bg:#303030;--pyd-scrollbar-track:rgba(255, 255, 255, 0.08);--pyd-scrollbar-thumb:rgba(241, 181, 67, 0.62);--pyd-scrollbar-thumb-border:rgba(35, 35, 35, 0.9);color-scheme:dark}@media (prefers-color-scheme:dark){.plan-your-day[data-plan-color-mode-default=system]:not([data-plan-color-mode]){--focus-ring-color:#9fc5ff;--focus-ring-offset-color:#232323;--pyd-text:#f3f1ea;--pyd-heading:#fffaf0;--pyd-hero-heading:#fffdf7;--pyd-muted:#c5d0d1;--pyd-accent:#f1b543;--pyd-accent-soft:rgba(241, 181, 67, 0.24);--pyd-accent-border:rgba(241, 181, 67, 0.55);--pyd-border-subtle:rgba(255, 255, 255, 0.08);--pyd-border:rgba(255, 255, 255, 0.12);--pyd-border-medium:rgba(255, 255, 255, 0.14);--pyd-border-strong:rgba(255, 255, 255, 0.18);--pyd-border-emphasis:rgba(255, 255, 255, 0.26);--pyd-control-mark-border:rgba(255, 255, 255, 0.42);--pyd-surface-bg:radial-gradient(circle at top right, rgba(241, 181, 67, 0.14), transparent 28%),linear-gradient(180deg, #2e2e2e 0%, #232323 100%);--pyd-surface-shadow:0 0.85rem 2rem rgba(0, 0, 0, 0.34);--pyd-card-bg:rgba(48, 48, 48, 0.92);--pyd-card-shadow:0 0.65rem 1.45rem rgba(0, 0, 0, 0.24);--pyd-panel-bg:#2b2b2b;--pyd-option-bg:#292929;--pyd-option-selected-bg:linear-gradient(180deg, #3a3427 0%, #2b2925 100%);--pyd-option-selected-shadow:0 0.85rem 1.8rem rgba(0, 0, 0, 0.28);--pyd-radio-bg:#232323;--pyd-radio-checked-bg:radial-gradient(circle at center, var(--pyd-accent) 0 45%, transparent 48% 100%),#302818;--pyd-chip-bg:rgba(255, 255, 255, 0.1);--pyd-hero-note-bg:rgba(255, 255, 255, 0.08);--pyd-primary-bg:#f1b543;--pyd-primary-hover-bg:#ffd36b;--pyd-primary-text:#232323;--pyd-primary-soft-text:#232323;--pyd-secondary-hover-bg:rgba(241, 181, 67, 0.16);--pyd-interactive-hover-bg:rgba(255, 255, 255, 0.08);--pyd-item-bg:linear-gradient(180deg, #303030 0%, #282828 100%);--pyd-item-hover-shadow:0 0.85rem 1.8rem rgba(0, 0, 0, 0.26);--pyd-item-drag-shadow:0 1.1rem 2rem rgba(0, 0, 0, 0.36);--pyd-feedback-border:rgba(241, 181, 67, 0.36);--pyd-feedback-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.28);--pyd-disabled-bg:rgba(255, 255, 255, 0.08);--pyd-disabled-text:#9aa6a8;--pyd-empty-border:rgba(255, 255, 255, 0.24);--pyd-empty-bg:rgba(255, 255, 255, 0.07);--pyd-summary-bg:linear-gradient(180deg, #303030 0%, #292929 100%);--pyd-summary-tile-bg:rgba(255, 255, 255, 0.08);--pyd-note-border:rgba(145, 198, 217, 0.28);--pyd-note-bg:rgba(62, 91, 101, 0.5);--pyd-note-text:#e9f6f9;--pyd-warning-border:rgba(241, 181, 67, 0.34);--pyd-warning-bg:rgba(78, 57, 24, 0.68);--pyd-warning-text:#ffe1a3;--pyd-success:#78d690;--pyd-danger:#ff9a8a;--pyd-map-bg:#303030;--pyd-scrollbar-track:rgba(255, 255, 255, 0.08);--pyd-scrollbar-thumb:rgba(241, 181, 67, 0.62);--pyd-scrollbar-thumb-border:rgba(35, 35, 35, 0.9);color-scheme:dark}}.plan-your-day img{display:block;max-width:100%}.plan-your-day button,.plan-your-day input,.plan-your-day select,.plan-your-day textarea{touch-action:manipulation}.plan-your-day button{cursor:pointer}.plan-your-day a{color:inherit}.plan-your-day .screen-reader-text,.plan-your-day .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.plan-your-day{width:min(100% - 2rem,1180px);margin:clamp(1rem,4vw,3rem) auto;padding-bottom:calc(4.75rem + env(safe-area-inset-bottom));color:var(--pyd-text);container-type:inline-size}.plan-your-day [hidden]{display:none!important}.plan-your-day__surface{padding:clamp(1rem,1.6vw,1.5rem);border:thin solid var(--pyd-border);border-radius:1.5rem;background:var(--pyd-surface-bg);box-shadow:var(--pyd-surface-shadow);position:relative}.plan-your-day__hero{display:grid;gap:.8rem;margin-bottom:1.2rem;padding-bottom:1rem;border-bottom:thin solid var(--pyd-border)}.plan-your-day__eyebrow,.plan-your-day__summary-eyebrow,.plan-your-day__trip-kicker{margin:0;font-size:.78rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--pyd-accent)}.plan-your-day__hero h2{font-size:clamp(1.8rem, 3.3vw, 2.45rem);line-height:1;color:var(--pyd-hero-heading);font-weight:700}.plan-your-day__auto-note,.plan-your-day__card-header p,.plan-your-day__category-description,.plan-your-day__hero-note,.plan-your-day__input-help,.plan-your-day__intro,.plan-your-day__noscript-note,.plan-your-day__preview-empty p,.plan-your-day__result-meta,.plan-your-day__results-empty p,.plan-your-day__start-description,.plan-your-day__summary-overview,.plan-your-day__trip-empty p,.plan-your-day__trip-meta{margin:0;font-size:.95rem;line-height:1.5;color:var(--pyd-muted)}.plan-your-day__hero-note{max-width:33rem;padding:.75rem .9rem;border-radius:1rem;background:var(--pyd-hero-note-bg);border:thin solid var(--pyd-border-subtle)}.plan-your-day__layout{display:grid;gap:1.2rem;align-items:start}.plan-your-day__controls,.plan-your-day__preview-panel{display:grid;gap:.85rem;min-width:0}.plan-your-day__card{min-width:0;padding:1rem;border:thin solid var(--pyd-border);border-radius:1.2rem;background:var(--pyd-card-bg);box-shadow:var(--pyd-card-shadow)}.plan-your-day__card-header,.plan-your-day__summary-top{display:flex;align-items:start;justify-content:space-between;gap:1rem;margin-bottom:.8rem}.plan-your-day__trip-header-actions{display:inline-flex;align-items:center;justify-content:flex-end;gap:.6rem;flex-wrap:wrap}.plan-your-day__card h3,.plan-your-day__card h4{color:var(--pyd-heading);font-weight:700;margin-top:0}.plan-your-day__card h3{font-size:1.2rem;margin-bottom:.25rem}.plan-your-day__fieldset{border:0;padding:0;margin:0}.plan-your-day__count-pill,.plan-your-day__summary-count{display:inline-flex;align-items:center;justify-content:center;min-height:1.8rem;padding:.3rem .7rem;border-radius:999px;background:var(--pyd-primary-bg);color:var(--pyd-primary-soft-text);font-size:.85rem;font-weight:700;white-space:nowrap}.plan-your-day__waypoint-status-wrap{position:sticky;z-index:50;right:0;left:0;bottom:.5dvh;padding-top:10px;pointer-events:none}.plan-your-day__waypoint-status{display:inline-flex;align-items:center;justify-content:center;width:100%;min-height:3.25rem;padding:.8rem 1rem;border:thin solid var(--pyd-accent-border);border-radius:.9rem;background:var(--pyd-primary-bg);color:var(--pyd-primary-text);box-shadow:0 .85rem 1.8rem rgba(23,48,56,.2);font:inherit;font-size:.98rem;font-weight:700;text-align:center;pointer-events:auto;transition:background-color .2s ease,box-shadow .2s ease,transform .2s ease}.plan-your-day__waypoint-status:active,.plan-your-day__waypoint-status:hover{background:var(--pyd-primary-hover-bg);box-shadow:0 1rem 2rem rgba(23,48,56,.24)}.plan-your-day__waypoint-status:active{transform:translateY(1px)}.plan-your-day__api-call-counter{position:fixed;z-index:60;top:50%;right:max(.75rem,env(safe-area-inset-right));display:grid;gap:.1rem;min-width:7.75rem;padding:.55rem .7rem;border:thin solid var(--pyd-accent-border);border-radius:.7rem;background:var(--pyd-primary-bg);color:var(--pyd-primary-text);box-shadow:0 .85rem 1.8rem rgba(23,48,56,.2);font-size:.78rem;font-weight:700;line-height:1.2;pointer-events:none;transform:translateY(-50%)}.plan-your-day__api-call-count,.plan-your-day__api-call-label{display:inline}.plan-your-day__api-call-count{font-size:1.35rem;line-height:1}.plan-your-day__api-call-breakdown{color:var(--pyd-primary-soft-text);font-size:.68rem;font-weight:400}.plan-your-day__category-grid,.plan-your-day__results-list,.plan-your-day__start-options,.plan-your-day__summary-grid,.plan-your-day__trip-list{display:grid;gap:.7rem}.plan-your-day__category-option,.plan-your-day__start-option{display:block;cursor:pointer}.plan-your-day__category-option input,.plan-your-day__start-option input{position:absolute;opacity:0;pointer-events:none}.plan-your-day__category-option-body,.plan-your-day__start-option-body{display:grid;gap:.35rem;min-height:100%;padding:.85rem .9rem .85rem 2.7rem;border:thin solid var(--pyd-border-strong);border-radius:1rem;background:var(--pyd-option-bg);position:relative;transition:border-color .2s ease,transform .2s ease,box-shadow .2s ease,background-color .2s ease}.plan-your-day__category-option-body::before,.plan-your-day__start-option-body::before{content:"";position:absolute;top:.92rem;left:.9rem;width:1.05rem;height:1.05rem;border:2px solid var(--pyd-control-mark-border);border-radius:999px;background:var(--pyd-radio-bg)}.plan-your-day__start-option:active .plan-your-day__start-option-body,.plan-your-day__start-option:hover .plan-your-day__start-option-body{border-color:var(--pyd-accent-border);background:var(--pyd-secondary-hover-bg);box-shadow:var(--pyd-item-hover-shadow);transform:translateY(-1px)}.plan-your-day__category-option input:checked+.plan-your-day__category-option-body,.plan-your-day__start-option input:checked+.plan-your-day__start-option-body{border-color:var(--pyd-accent-border);background:var(--pyd-option-selected-bg);box-shadow:var(--pyd-option-selected-shadow);transform:translateY(-1px)}.plan-your-day__category-option input:checked+.plan-your-day__category-option-body::before,.plan-your-day__start-option input:checked+.plan-your-day__start-option-body::before{border-color:var(--pyd-accent);background:var(--pyd-radio-checked-bg)}.plan-your-day__category-radio:focus-visible+.plan-your-day__category-option-body,.plan-your-day__category-search input:focus-visible,.plan-your-day__category-search-button:focus-visible,.plan-your-day__category-trigger:focus-visible,.plan-your-day__clear-link:focus-visible,.plan-your-day__color-mode-toggle:focus-visible,.plan-your-day__custom-start input:focus-visible,.plan-your-day__load-more-button:focus-visible,.plan-your-day__maps-link:focus-visible,.plan-your-day__result-add:focus-visible,.plan-your-day__result-link:focus-visible,.plan-your-day__start-option input:focus-visible+.plan-your-day__start-option-body,.plan-your-day__start-toggle:focus-visible,.plan-your-day__submit:focus-visible,.plan-your-day__trip-tools a:focus-visible,.plan-your-day__trip-tools button:focus-visible,.plan-your-day__waypoint-status:focus-visible{outline:3px solid var(--focus-ring-color);outline-offset:3px;box-shadow:0 0 0 3px var(--focus-ring-offset-color)}.plan-your-day__category-title,.plan-your-day__start-title{display:block;color:var(--pyd-heading);font-size:1rem;font-weight:700}.plan-your-day__category-accordion{display:grid;gap:.7rem}.plan-your-day__category-search{margin-bottom:.85rem}.plan-your-day__category-search-controls{display:grid;gap:.65rem}.plan-your-day__category-search input{width:100%;min-height:2.85rem;padding:.75rem .9rem;border:thin solid var(--pyd-border-strong);border-radius:.5rem;background:var(--pyd-panel-bg);color:var(--pyd-heading)}.plan-your-day__category-search-button{justify-self:start}.plan-your-day__custom-search-results{margin-bottom:.85rem}.plan-your-day__category-accordion-item{border:thin solid var(--pyd-border-medium);border-radius:1rem;background:var(--pyd-item-bg);transition:border-color .18s ease,box-shadow .18s ease,background-color .18s ease}.plan-your-day__category-accordion-item.is-expanded{border-color:var(--pyd-accent-border);box-shadow:var(--pyd-item-hover-shadow)}.plan-your-day__category-accordion-item.is-expanded>h4 button{box-shadow:0 4px 5px -5px #333333d7}.plan-your-day__category-accordion-heading{margin:0}.plan-your-day__category-trigger{appearance:none;display:flex;align-items:center;justify-content:space-between;gap:1rem;width:100%;padding:.95rem 1rem;border:0;border-radius:1rem;background:0 0;color:inherit;text-align:left;cursor:pointer;transition:transform .18s ease,background-color .18s ease,color .18s ease}.plan-your-day__category-trigger:active,.plan-your-day__category-trigger:hover{transform:translateY(-1px);background:var(--pyd-interactive-hover-bg)}.plan-your-day__category-trigger-copy{display:grid;gap:.18rem;min-width:0;flex:1 1 auto}.plan-your-day__category-trigger-side{display:inline-flex;align-items:center;gap:.7rem;flex-shrink:0}.plan-your-day__category-trigger-chevron{width:.72rem;height:.72rem;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .18s ease}.plan-your-day__category-trigger[aria-expanded=true] .plan-your-day__category-trigger-chevron{transform:rotate(-135deg)}.plan-your-day__category-panel{padding:.66rem 1rem 1rem}.plan-your-day__custom-search-header{margin:0}.plan-your-day__custom-search-header h4{margin:0;font-weight:700}.plan-your-day__category-results-scroll{max-height:min(28rem,65vh);overflow-y:auto;padding-right:.35rem;scrollbar-width:thin;scrollbar-color:var(--pyd-scrollbar-thumb) var(--pyd-scrollbar-track);transition:opacity .18s ease,filter .18s ease}.plan-your-day__category-results-scroll::-webkit-scrollbar{width:.65rem}.plan-your-day__category-results-scroll::-webkit-scrollbar-track{background:var(--pyd-scrollbar-track);border-radius:999px}.plan-your-day__category-results-scroll::-webkit-scrollbar-thumb{background:var(--pyd-scrollbar-thumb);border-radius:999px;border:2px solid var(--pyd-scrollbar-thumb-border)}.plan-your-day__start-toggle{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:.55rem;min-height:2.5rem;padding:.55rem .9rem;border:thin solid var(--pyd-border-medium);border-radius:999px;background:var(--pyd-panel-bg);color:var(--pyd-text);font-size:.88rem;font-weight:700;line-height:1;cursor:pointer;transition:transform .18s ease,border-color .18s ease,background-color .18s ease,color .18s ease}.plan-your-day__start-toggle:active,.plan-your-day__start-toggle:hover{transform:translateY(-1px);border-color:var(--pyd-accent-border);background:var(--pyd-secondary-hover-bg)}.plan-your-day__hero-tools{display:flex;align-items:center;justify-content:flex-start}.plan-your-day__color-mode-toggle{appearance:none;display:inline-flex;position:relative;align-items:center;gap:.55rem;min-height:2.5rem;padding:.45rem .75rem .45rem .5rem;border:thin solid var(--pyd-border-medium);border-radius:999px;background:var(--pyd-panel-bg);color:var(--pyd-text);cursor:pointer;font-family:inherit;font-size:.88rem;font-weight:700;line-height:1;transition:transform .18s ease,border-color .18s ease,background-color .18s ease,color .18s ease}.plan-your-day__color-mode-toggle::before{content:"";width:2.2rem;height:1.25rem;border-radius:999px;background:var(--pyd-chip-bg);box-shadow:inset 0 0 0 thin var(--pyd-border)}.plan-your-day__color-mode-toggle::after{content:"";position:absolute;width:.85rem;height:.85rem;margin-left:.2rem;border-radius:999px;background:var(--pyd-primary-bg);transform:translateX(0);transition:background-color .18s ease,transform .18s ease}.plan-your-day__color-mode-toggle[aria-pressed=true]::after{transform:translateX(.95rem)}.plan-your-day__color-mode-toggle:active,.plan-your-day__color-mode-toggle:hover{transform:translateY(-1px);border-color:var(--pyd-accent-border);background:var(--pyd-secondary-hover-bg)}.plan-your-day__start-toggle-chevron{width:.6rem;height:.6rem;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-135deg);transition:transform .18s ease}.plan-your-day__start-toggle.is-collapsed .plan-your-day__start-toggle-chevron{transform:rotate(45deg)}.plan-your-day__start-panel{will-change:height}.plan-your-day [data-plan-preview-card][aria-busy=true] .plan-your-day__map-wrap,.plan-your-day [data-plan-preview-card][aria-busy=true] .plan-your-day__summary,.plan-your-day [data-plan-trip-region][aria-busy=true],.plan-your-day__category-results-scroll[aria-busy=true]{opacity:.72;filter:saturate(.88)}.plan-your-day [data-plan-preview-card][aria-busy=true],.plan-your-day [data-plan-trip-region][aria-busy=true],.plan-your-day__category-results-scroll[aria-busy=true]{cursor:progress}.plan-your-day__result-distance{margin:0;font-size:.88rem;font-weight:700;color:var(--pyd-accent)}.plan-your-day__results-list,.plan-your-day__trip-list{margin:0;padding:0;list-style:none}.plan-your-day__load-more{display:flex;justify-content:center;margin-top:.85rem}.plan-your-day__result-item,.plan-your-day__trip-item{display:flex;flex-wrap:wrap;gap:.8rem;position:relative;padding:.85rem;border:thin solid var(--pyd-border-medium);border-radius:.5rem;background:var(--pyd-item-bg);transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease}.plan-your-day__result-copy,.plan-your-day__trip-copy{display:grid;gap:.15rem;min-width:0}.plan-your-day__result-copy h4,.plan-your-day__trip-copy h4{font-size:1rem;margin:0;font-weight:700}.plan-your-day__result-tools,.plan-your-day__trip-tools{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.plan-your-day__category-search-button,.plan-your-day__clear-link,.plan-your-day__load-more-button,.plan-your-day__maps-link,.plan-your-day__reorder-disabled,.plan-your-day__result-add,.plan-your-day__result-link,.plan-your-day__submit,.plan-your-day__trip-tools a,.plan-your-day__trip-tools button{display:inline-flex;align-items:center;justify-content:center;min-height:2.625rem;height:42px;padding:.65rem .95rem;border:thin solid transparent;border-radius:999px;appearance:none;cursor:pointer;font-family:inherit;font-size:.9rem;font-weight:700;line-height:1;text-decoration:none;transition:transform .18s ease,background-color .18s ease,color .18s ease,border-color .18s ease,box-shadow .18s ease}.plan-your-day__load-more-button,.plan-your-day__maps-link,.plan-your-day__result-link,.plan-your-day__trip-tools a,.plan-your-day__trip-tools button{border-color:var(--pyd-border-strong);background:var(--pyd-panel-bg);color:var(--pyd-text)}.plan-your-day__load-more-button:active:not(:disabled),.plan-your-day__load-more-button:hover:not(:disabled),.plan-your-day__maps-link:active:not(.is-disabled),.plan-your-day__maps-link:hover:not(.is-disabled),.plan-your-day__result-link:active,.plan-your-day__result-link:hover,.plan-your-day__trip-tools a:active,.plan-your-day__trip-tools a:hover,.plan-your-day__trip-tools button:active:not(:disabled),.plan-your-day__trip-tools button:hover:not(:disabled){transform:translateY(-1px);border-color:var(--pyd-accent-border);background:var(--pyd-secondary-hover-bg)}.plan-your-day__category-search-button,.plan-your-day__load-more-button,.plan-your-day__result-add,.plan-your-day__submit{background:var(--pyd-primary-bg);color:var(--pyd-primary-text)}.plan-your-day__category-search-button:active,.plan-your-day__category-search-button:hover,.plan-your-day__load-more-button:active:not(:disabled),.plan-your-day__load-more-button:hover:not(:disabled),.plan-your-day__result-add:active,.plan-your-day__result-add:hover,.plan-your-day__submit:active,.plan-your-day__submit:hover{transform:translateY(-1px);background:var(--pyd-primary-hover-bg)}.plan-your-day__load-more-button{min-width:11rem}.plan-your-day__load-more-button.is-loading,.plan-your-day__load-more-button:disabled{cursor:progress;opacity:.82;transform:none}.plan-your-day__result-added{display:inline-flex;align-items:center;justify-content:center;min-height:2.625rem;padding:.65rem .95rem;border-radius:999px;background:var(--pyd-chip-bg);color:var(--pyd-text);font-size:.9rem;font-weight:700}.plan-your-day__load-more{display:flex;justify-content:flex-start;margin-top:.9rem}.plan-your-day__load-more-button.is-loading,.plan-your-day__load-more-button:disabled{opacity:.72;cursor:progress;transform:none}.plan-your-day__waypoint-feedback{position:absolute;top:.65rem;right:.65rem;z-index:2;max-width:calc(100% - 1.3rem);padding:.45rem .7rem;border:thin solid var(--pyd-feedback-border);border-radius:.5rem;background:var(--pyd-primary-bg);box-shadow:var(--pyd-feedback-shadow);color:var(--pyd-primary-text);font-size:.85rem;font-weight:700;line-height:1.2;pointer-events:none}.plan-your-day.is-enhanced .plan-your-day__trip-item:hover{border-color:var(--pyd-accent-border);box-shadow:var(--pyd-item-hover-shadow)}.plan-your-day__trip-item.is-dragging{opacity:.7;transform:scale(.985);box-shadow:var(--pyd-item-drag-shadow)}.plan-your-day__trip-item.is-drop-before{box-shadow:inset 0 4px 0 var(--pyd-accent)}.plan-your-day__trip-item.is-drop-after{box-shadow:inset 0 -4px 0 var(--pyd-accent)}.plan-your-day__trip-main{display:grid;grid-template-columns:auto 1fr;gap:.75rem}.plan-your-day__trip-number{display:inline-flex;align-items:center;justify-content:center;width:2.1rem;height:2.1rem;border-radius:999px;background:var(--pyd-primary-bg);color:var(--pyd-primary-text);font-size:.95rem;font-weight:700}.plan-your-day__drag-handle{display:none;align-items:center;justify-content:center;min-width:4rem;padding:.6rem 16px .4rem 32px;border-radius:1000px;background:var(--pyd-primary-bg);color:var(--pyd-primary-text);font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;position:relative;height:42px}.plan-your-day__drag-handle:before{content:'';display:inline-block;width:10px;height:18px;background-image:repeating-radial-gradient(circle,currentColor 0 2px,transparent 2.5px 100%);background-size:5px 6px;background-position:center;position:absolute;left:8px;top:5px;bottom:5px;margin:auto 0}.plan-your-day.is-enhanced .plan-your-day__drag-handle{display:inline-flex}.plan-your-day__reorder-links{display:inline-flex;gap:.4rem;flex-wrap:wrap}.plan-your-day__reorder-disabled{opacity:.45;cursor:not-allowed;border-color:var(--pyd-border);background:var(--pyd-disabled-bg);color:var(--pyd-disabled-text)}.plan-your-day__preview-empty,.plan-your-day__results-empty,.plan-your-day__trip-empty{display:grid;gap:.55rem;place-items:start;padding:1.1rem;border:thin dashed var(--pyd-empty-border);border-radius:.5rem;background:var(--pyd-empty-bg)}.plan-your-day__preview-empty h4,.plan-your-day__results-empty h4,.plan-your-day__trip-empty h4{font-size:1.15rem;font-weight:700}.plan-your-day__custom-start{margin-top:.85rem}.plan-your-day__custom-start label{display:inline-block;margin-bottom:.45rem;font-weight:700;color:var(--pyd-heading)}.plan-your-day__custom-start-field{position:relative}.plan-your-day__custom-start input{width:100%;min-height:2.85rem;padding:.75rem 2.85rem .75rem .9rem;border:thin solid var(--pyd-border-strong);border-radius:.5rem;background:var(--pyd-panel-bg);color:var(--pyd-heading)}.plan-your-day__custom-start-indicator{position:absolute;top:50%;right:.9rem;display:none;width:1rem;height:1rem;pointer-events:none;transform:translateY(-50%)}.plan-your-day__custom-start[data-plan-custom-start-state=checking] .plan-your-day__custom-start-indicator,.plan-your-day__custom-start[data-plan-custom-start-state=found] .plan-your-day__custom-start-indicator,.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator{display:block}.plan-your-day__custom-start[data-plan-custom-start-state=checking] .plan-your-day__custom-start-indicator{border:2px solid var(--pyd-border-emphasis);border-top-color:var(--pyd-accent);border-radius:999px;animation:plan-your-day-spin .8s linear infinite}.plan-your-day__custom-start[data-plan-custom-start-state=found] .plan-your-day__custom-start-indicator::before{content:"";position:absolute;top:.05rem;left:.3rem;width:.38rem;height:.72rem;border-right:.18rem solid var(--pyd-success);border-bottom:.18rem solid var(--pyd-success);transform:rotate(45deg)}.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator{border:2px solid var(--pyd-danger);border-radius:999px}.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator::after,.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator::before{content:"";position:absolute;top:.2rem;left:.4rem;width:.15rem;height:.52rem;border-radius:999px;background:var(--pyd-danger)}.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator::before{transform:rotate(45deg)}.plan-your-day__custom-start[data-plan-custom-start-state=not_found] .plan-your-day__custom-start-indicator::after{transform:rotate(-45deg)}@keyframes plan-your-day-spin{to{transform:translateY(-50%) rotate(360deg)}}@media (prefers-reduced-motion:reduce){.plan-your-day__custom-start[data-plan-custom-start-state=checking] .plan-your-day__custom-start-indicator{animation:none}}.plan-your-day__auto-note,.plan-your-day__input-help,.plan-your-day__noscript-note{margin-top:.55rem}.plan-your-day__actions{display:flex;gap:.75rem;flex-wrap:wrap;margin-top:.85rem}.plan-your-day__clear-link{border-color:var(--pyd-border);background:0 0;color:var(--pyd-text)}.plan-your-day__clear-link:active,.plan-your-day__clear-link:hover{transform:translateY(-1px);border-color:var(--pyd-border-emphasis);background:var(--pyd-summary-tile-bg)}.plan-your-day__maps-link.is-disabled,.plan-your-day__maps-link[aria-disabled=true]{opacity:.5;cursor:not-allowed;pointer-events:none}.plan-your-day__messages{display:grid;gap:.7rem;margin-bottom:1rem}.plan-your-day__message{padding:.75rem .85rem;border-radius:.5rem;border:thin solid transparent;font-size:.92rem;line-height:1.45}.plan-your-day__message--note{border-color:var(--pyd-note-border);background:var(--pyd-note-bg);color:var(--pyd-note-text)}.plan-your-day__message--warning{border-color:var(--pyd-warning-border);background:var(--pyd-warning-bg);color:var(--pyd-warning-text)}.plan-your-day__summary{margin-top:1rem;margin-bottom:0;padding:.9rem;border-radius:.5rem;background:var(--pyd-summary-bg);border:thin solid var(--pyd-border-subtle)}.plan-your-day__summary-handoff{display:flex;flex-direction:column;gap:.45rem;margin-top:1rem}.plan-your-day__summary-handoff-label{margin:0;font-size:.84rem;font-weight:700;letter-spacing:0;text-transform:uppercase;color:var(--pyd-accent)}.plan-your-day__maps-link--summary{width:100%;min-height:3.35rem;padding:.9rem 1.2rem;font-size:1.15rem}.plan-your-day__summary-grid div{padding:.7rem .8rem;border-radius:.5rem;background:var(--pyd-summary-tile-bg)}.plan-your-day__summary-grid dt{margin-bottom:.35rem;font-size:.84rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--pyd-accent)}.plan-your-day__summary-grid dd{margin:0;font-size:.95rem;color:var(--pyd-heading)}.plan-your-day__map-wrap{overflow:hidden;border-radius:1rem;border:thin solid var(--pyd-border);background:var(--pyd-map-bg)}.plan-your-day__map-frame{display:block;width:100%;min-height:24rem;border:0;background:var(--pyd-map-bg)}@media (min-width:980px){.plan-your-day__hero{grid-template-columns:minmax(0,1fr) auto;align-items:end}.plan-your-day__summary-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.plan-your-day__result-item,.plan-your-day__trip-item{grid-template-columns:minmax(0,1fr) auto}.plan-your-day__trip-tools{align-self:center;justify-content:end}}@container (min-width:60rem){.plan-your-day__layout{grid-template-columns:minmax(18rem,30rem) minmax(0,1fr)}.plan-your-day__preview-card{position:sticky;top:7.75rem}}@media (max-width:979px){.plan-your-day__card-header,.plan-your-day__summary-top{flex-direction:column;align-items:start}.plan-your-day__api-call-counter{top:auto;right:max(.75rem,env(safe-area-inset-right));bottom:calc(4.25rem + env(safe-area-inset-bottom));transform:none}}@media (max-width:639px){.plan-your-day__card,.plan-your-day__surface{border-radius:1rem}.plan-your-day__surface{padding:1rem}.plan-your-day__category-option-body,.plan-your-day__start-option-body{padding-left:2.75rem}.plan-your-day__category-trigger{align-items:start}.plan-your-day__category-trigger-side{gap:.55rem}.plan-your-day__category-results-scroll{max-height:min(22rem,55vh)}.plan-your-day__actions,.plan-your-day__reorder-links,.plan-your-day__result-tools,.plan-your-day__trip-tools{width:100%}.plan-your-day__category-search-button,.plan-your-day__clear-link,.plan-your-day__maps-link,.plan-your-day__reorder-disabled,.plan-your-day__result-add,.plan-your-day__result-link,.plan-your-day__submit,.plan-your-day__trip-tools a,.plan-your-day__trip-tools button{width:100%}.plan-your-day__summary-grid{grid-template-columns:1fr}.plan-your-day__map-frame{min-height:20rem}}@media (min-width:768px){.plan-your-day__category-description{display:block}.plan-your-day__category-search-controls{grid-template-columns:minmax(0,1fr) auto;align-items:center}}@media (max-width:767px){.plan-your-day__category-description{display:none}} \ No newline at end of file diff --git a/plugin/plan-your-day/assets/fonts/OFL.txt b/plugin/waypoints/assets/fonts/OFL.txt similarity index 100% rename from plugin/plan-your-day/assets/fonts/OFL.txt rename to plugin/waypoints/assets/fonts/OFL.txt diff --git a/plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-300.woff2 b/plugin/waypoints/assets/fonts/noto-sans-v42-latin-ext-300.woff2 similarity index 100% rename from plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-300.woff2 rename to plugin/waypoints/assets/fonts/noto-sans-v42-latin-ext-300.woff2 diff --git a/plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-700.woff2 b/plugin/waypoints/assets/fonts/noto-sans-v42-latin-ext-700.woff2 similarity index 100% rename from plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-700.woff2 rename to plugin/waypoints/assets/fonts/noto-sans-v42-latin-ext-700.woff2 diff --git a/plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-regular.woff2 b/plugin/waypoints/assets/fonts/noto-sans-v42-latin-ext-regular.woff2 similarity index 100% rename from plugin/plan-your-day/assets/fonts/noto-sans-v42-latin-ext-regular.woff2 rename to plugin/waypoints/assets/fonts/noto-sans-v42-latin-ext-regular.woff2 diff --git a/plugin/plan-your-day/assets/js/admin-settings.js b/plugin/waypoints/assets/js/admin-settings.js similarity index 100% rename from plugin/plan-your-day/assets/js/admin-settings.js rename to plugin/waypoints/assets/js/admin-settings.js diff --git a/plugin/plan-your-day/assets/js/plan-block.js b/plugin/waypoints/assets/js/plan-block.js similarity index 79% rename from plugin/plan-your-day/assets/js/plan-block.js rename to plugin/waypoints/assets/js/plan-block.js index ece62ed..84841c3 100644 --- a/plugin/plan-your-day/assets/js/plan-block.js +++ b/plugin/waypoints/assets/js/plan-block.js @@ -10,7 +10,7 @@ var Placeholder = components.Placeholder; var TextControl = components.TextControl; - blocks.registerBlockType( 'plan-your-day/planner', { + blocks.registerBlockType( 'waypoints/planner', { edit: function( props ) { var attributes = props.attributes || {}; var blockProps = useBlockProps(); @@ -24,12 +24,12 @@ el( PanelBody, { - title: __( 'Planner settings', 'plan-your-day' ), + title: __( 'Planner settings', 'waypoints' ), initialOpen: true, }, el( TextControl, { - label: __( 'Action URL', 'plan-your-day' ), - help: __( 'Optional. Submit planner updates to a specific page URL instead of the current page.', 'plan-your-day' ), + label: __( 'Action URL', 'waypoints' ), + help: __( 'Optional. Submit planner updates to a specific page URL instead of the current page.', 'waypoints' ), value: attributes.actionUrl || '', onChange: function( value ) { props.setAttributes( { actionUrl: value } ); @@ -43,13 +43,13 @@ el( Placeholder, { - label: __( 'Waypoints', 'plan-your-day' ), - instructions: __( 'The frontend uses the same planner renderer as the shortcode. Configure an optional action URL in the block settings.', 'plan-your-day' ), + label: __( 'Waypoints', 'waypoints' ), + instructions: __( 'The frontend uses the same planner renderer as the shortcode. Configure an optional action URL in the block settings.', 'waypoints' ), }, el( 'p', null, - __( 'Planner styles and interactions load only when this block is rendered on the page.', 'plan-your-day' ) + __( 'Planner styles and interactions load only when this block is rendered on the page.', 'waypoints' ) ) ) ) diff --git a/plugin/plan-your-day/assets/js/plan.js b/plugin/waypoints/assets/js/plan.js similarity index 87% rename from plugin/plan-your-day/assets/js/plan.js rename to plugin/waypoints/assets/js/plan.js index be56978..fff0718 100644 --- a/plugin/plan-your-day/assets/js/plan.js +++ b/plugin/waypoints/assets/js/plan.js @@ -399,6 +399,21 @@ `; }; + const getWaypointStatusLabel = (routeData, strings) => { + const waypointCount = toStringArray(routeData?.selectedWaypointIds).length; + + if (waypointCount <= 0) { + return String(strings.waypointStatusEmpty || 'Add some waypoints!'); + } + + return formatTemplate( + waypointCount === 1 + ? String(strings.waypointStatusSingle || '{count} waypoint added') + : String(strings.waypointStatusPlural || '{count} waypoints added'), + { count: waypointCount } + ); + }; + const renderTripMarkup = (routeData, strings, tripHelpId) => { const tripWaypoints = Array.isArray(routeData?.tripWaypoints) ? routeData.tripWaypoints : []; @@ -724,6 +739,14 @@ } }; + const syncWaypointStatus = (refs, state, strings) => { + if (!(refs.waypointStatus instanceof HTMLButtonElement)) { + return; + } + + refs.waypointStatus.textContent = getWaypointStatusLabel(state.route, strings); + }; + const focusElement = (element) => { if (!(element instanceof HTMLElement) || typeof element.focus !== 'function') { return false; @@ -773,21 +796,168 @@ return null; }; + const parseWaypointMoveValue = (moveValue) => { + const [placeId, direction] = String(moveValue || '').split(':', 2); + + if (!placeId || (direction !== 'up' && direction !== 'down')) { + return null; + } + + return { + placeId, + direction, + }; + }; + + const moveWaypointIds = (selectedWaypointIds, moveValue) => { + const move = parseWaypointMoveValue(moveValue); + const nextWaypointIds = toStringArray(selectedWaypointIds); + + if (!move) { + return nextWaypointIds; + } + + const currentIndex = nextWaypointIds.indexOf(move.placeId); + const targetIndex = move.direction === 'up' ? currentIndex - 1 : currentIndex + 1; + + if ( + currentIndex < 0 || + targetIndex < 0 || + targetIndex >= nextWaypointIds.length + ) { + return nextWaypointIds; + } + + [nextWaypointIds[currentIndex], nextWaypointIds[targetIndex]] = [ + nextWaypointIds[targetIndex], + nextWaypointIds[currentIndex], + ]; + + return nextWaypointIds; + }; + + const sortTripWaypointsByIds = (tripWaypoints, waypointIds) => { + const waypointById = new Map(); + + (Array.isArray(tripWaypoints) ? tripWaypoints : []).forEach((waypoint) => { + const placeId = String(waypoint?.id || ''); + + if (placeId) { + waypointById.set(placeId, waypoint); + } + }); + + return toStringArray(waypointIds) + .map((placeId) => waypointById.get(placeId)) + .filter(Boolean); + }; + + const getValidLocalTripWaypoints = (tripWaypoints) => + (Array.isArray(tripWaypoints) ? tripWaypoints : []) + .map((waypoint) => ({ + id: String(waypoint?.id || '').trim(), + label: String(waypoint?.label || '').trim(), + address: String(waypoint?.address || '').trim(), + })) + .filter((waypoint) => waypoint.id && (waypoint.label || waypoint.address)); + + const getWaypointDirectionsLabel = (waypoint) => String(waypoint?.address || waypoint?.label || '').trim(); + + const buildLocalEmbedDirectionsUrl = (currentIframeSrc, tripWaypoints) => { + const waypoints = getValidLocalTripWaypoints(tripWaypoints); + + if (waypoints.length === 0) { + return ''; + } + + const currentSrc = String(currentIframeSrc || ''); + + if (!currentSrc) { + return ''; + } + + try { + const url = new URL(currentSrc); + const apiKey = url.searchParams.get('key') || ''; + const origin = url.searchParams.get('origin') || ''; + + if (!apiKey || !origin) { + return currentSrc; + } + + const destination = waypoints[waypoints.length - 1]; + const intermediates = waypoints.slice(0, -1); + + url.searchParams.set('key', apiKey); + url.searchParams.set('origin', origin); + url.searchParams.set('destination', `place_id:${destination.id}`); + url.searchParams.set('mode', 'walking'); + + if (intermediates.length > 0) { + url.searchParams.set('waypoints', intermediates.map((waypoint) => `place_id:${waypoint.id}`).join('|')); + } else { + url.searchParams.delete('waypoints'); + } + + return url.toString(); + } catch (error) { + return currentSrc; + } + }; + + const buildLocalDirectionsHandoffUrl = (currentMapsUrl, tripWaypoints) => { + const waypoints = getValidLocalTripWaypoints(tripWaypoints); + + if (waypoints.length === 0) { + return ''; + } + + try { + const url = new URL(String(currentMapsUrl || 'https://www.google.com/maps/dir/')); + const origin = url.searchParams.get('origin') || ''; + const destination = waypoints[waypoints.length - 1]; + const intermediates = waypoints.slice(0, -1); + + url.searchParams.set('api', '1'); + url.searchParams.set('destination', getWaypointDirectionsLabel(destination)); + url.searchParams.set('destination_place_id', destination.id); + url.searchParams.set('travelmode', 'walking'); + + if (origin) { + url.searchParams.set('origin', origin); + } else { + url.searchParams.delete('origin'); + } + + if (intermediates.length > 0) { + url.searchParams.set('waypoints', intermediates.map(getWaypointDirectionsLabel).join('|')); + url.searchParams.set('waypoint_place_ids', intermediates.map((waypoint) => waypoint.id).join('|')); + } else { + url.searchParams.delete('waypoints'); + url.searchParams.delete('waypoint_place_ids'); + } + + return url.toString(); + } catch (error) { + return String(currentMapsUrl || ''); + } + }; + const restoreRouteActionFocus = (refs, focusRequest) => { if (!focusRequest) { return; } + if (focusRequest.action === 'add-waypoint') { + return; + } + const placeId = String(focusRequest.placeId || ''); const tripHeaderActionButton = refs.tripHeaderActions?.querySelector('button:not([disabled]):not([hidden])'); let target = null; - if (placeId && focusRequest.action === 'add-waypoint') { - target = refs.tripRegion?.querySelector( - `[data-waypoint-id="${placeId}"] button[name="remove_waypoint"]` - ); - } else if (placeId && focusRequest.action === 'move-waypoint') { + if (placeId && focusRequest.action === 'move-waypoint') { const direction = String(focusRequest.direction || ''); target = @@ -994,6 +1164,10 @@ summaryCount: root.querySelector('[data-plan-summary-count]'), openLink: root.querySelector('[data-plan-open-link]'), openLinkLabel: root.querySelector('[data-plan-open-link-label]'), + waypointStatus: root.querySelector('[data-plan-waypoint-status]'), + apiCallCounter: root.querySelector('[data-plan-api-call-counter]'), + apiCallCount: root.querySelector('[data-plan-api-call-count]'), + apiCallBreakdown: root.querySelector('[data-plan-api-call-breakdown]'), }; const state = { @@ -1037,8 +1211,43 @@ typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches; let startPanelAnimationFrame = 0; + const apiCallCounts = { + bootstrap: 0, + browse: 0, + route: 0, + }; + + const syncApiCallCounter = () => { + if (!(refs.apiCallCounter instanceof HTMLElement)) { + return; + } + + const totalCalls = apiCallCounts.bootstrap + apiCallCounts.browse + apiCallCounts.route; + const breakdown = `Bootstrap ${apiCallCounts.bootstrap}, Browse ${apiCallCounts.browse}, Route ${apiCallCounts.route}`; + + if (refs.apiCallCount instanceof HTMLElement) { + refs.apiCallCount.textContent = String(totalCalls); + } + + if (refs.apiCallBreakdown instanceof HTMLElement) { + refs.apiCallBreakdown.textContent = breakdown; + } + + refs.apiCallCounter.setAttribute('title', breakdown); + refs.apiCallCounter.setAttribute('aria-label', `API calls: ${totalCalls}. ${breakdown}.`); + }; + + const recordApiCall = (apiCallType) => { + if (!Object.prototype.hasOwnProperty.call(apiCallCounts, apiCallType)) { + return; + } + + apiCallCounts[apiCallType] += 1; + syncApiCallCounter(); + }; applyColorMode(root, refs, resolveColorMode(colorModeDefault, colorModeMediaQuery), strings); + syncApiCallCounter(); if (colorModeDefault === COLOR_MODE_SYSTEM && colorModeMediaQuery) { colorModeMediaQuery.addEventListener('change', () => { @@ -1056,12 +1265,53 @@ }); renderTrip(refs, state, strings); renderPreview(refs, state, strings); + syncWaypointStatus(refs, state, strings); syncHiddenInputs(refs, state); syncStartUi(refs, state, strings); syncCategorySearchUi(refs, state); setRouteMutationBusyState(root, false); }; + const applyLocalWaypointMove = (moveValue, focusRequest) => { + const currentWaypointIds = toStringArray(state.route?.selectedWaypointIds); + const nextWaypointIds = moveWaypointIds(currentWaypointIds, moveValue); + const didMove = + currentWaypointIds.length !== nextWaypointIds.length || + currentWaypointIds.some((waypointId, index) => waypointId !== nextWaypointIds[index]); + + if (!didMove) { + return true; + } + + const nextTripWaypoints = sortTripWaypointsByIds(state.route?.tripWaypoints, nextWaypointIds); + + if (nextTripWaypoints.length !== nextWaypointIds.length) { + return false; + } + + state.route = { + ...(state.route || {}), + selectedWaypointIds: nextWaypointIds, + tripWaypoints: nextTripWaypoints, + hasTrip: nextWaypointIds.length > 0, + iframeSrc: buildLocalEmbedDirectionsUrl(state.route?.iframeSrc || '', nextTripWaypoints), + mapsUrl: buildLocalDirectionsHandoffUrl(state.route?.mapsUrl || '', nextTripWaypoints), + }; + + renderTrip(refs, state, strings); + renderPreview(refs, state, strings); + syncWaypointStatus(refs, state, strings); + syncHiddenInputs(refs, state); + restoreRouteActionFocus(refs, focusRequest); + announce(refs.liveRegion, strings.tripUpdated || ''); + debugLog(config, 'info', 'route:move-local', { + moveWaypoint: moveValue, + selectedWaypointIds: nextWaypointIds, + }); + + return true; + }; + const showRequestError = (message, announcementMessage = '') => { renderMessages(refs, [ { @@ -1208,6 +1458,7 @@ return endpointTokenRequest; } + recordApiCall('bootstrap'); endpointTokenRequest = fetch(config.rest.bootstrapUrl, { method: 'POST', credentials: 'same-origin', @@ -1335,6 +1586,7 @@ } } + recordApiCall(endpointKey); const response = await fetch(config.rest[endpointKey === 'browse' ? 'browseUrl' : 'routeUrl'], { method: 'POST', credentials: 'same-origin', @@ -1416,6 +1668,7 @@ }); renderTrip(refs, state, strings); renderPreview(refs, state, strings); + syncWaypointStatus(refs, state, strings); syncHiddenInputs(refs, state); syncStartUi(refs, state, strings); syncCategorySearchUi(refs, state); @@ -1626,6 +1879,13 @@ endpointKey = 'route'; announcementMessage = strings.tripUpdated || ''; } else if (submitter.name === 'move_waypoint' && submitter.value) { + const routeFocusRequest = buildRouteFocusRequest(submitter); + + event.preventDefault(); + if (applyLocalWaypointMove(submitter.value, routeFocusRequest)) { + return; + } + payload.move_waypoint = submitter.value; endpointKey = 'route'; announcementMessage = strings.tripUpdated || ''; @@ -1655,6 +1915,26 @@ return; } + const waypointStatus = target.closest('[data-plan-waypoint-status]'); + + if (waypointStatus instanceof HTMLButtonElement) { + const scrollTarget = + refs.mapWrap instanceof HTMLElement && !refs.mapWrap.hidden + ? refs.mapWrap + : refs.previewCard; + + event.preventDefault(); + + if (scrollTarget instanceof HTMLElement) { + scrollTarget.scrollIntoView({ + behavior: prefersReducedMotion ? 'auto' : 'smooth', + block: 'start', + }); + } + + return; + } + const startToggle = target.closest('[data-plan-start-toggle]'); if (startToggle instanceof HTMLButtonElement) { diff --git a/plugin/waypoints/assets/js/plan.min.js b/plugin/waypoints/assets/js/plan.min.js new file mode 100644 index 0000000..ffad680 --- /dev/null +++ b/plugin/waypoints/assets/js/plan.min.js @@ -0,0 +1 @@ +(()=>{const t="planYourDayEnhanced",e="planYourDayColorMode",a="light",r="dark",n="system",o=[a,r],s=[...o,n],i="checking",l="found",u="not_found",d=[i,l,u],c=t=>String(t??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"),p=(t,e={})=>{let a=String(t||"");return Object.entries(e||{}).forEach(([t,e])=>{a=a.split(`{${t}}`).join(String(e??""))}),a},y=(t,e="")=>Array.isArray(t)?t.map(t=>y(t)):t&&"object"==typeof t?Object.fromEntries(Object.entries(t).map(([t,e])=>{const a=String(t).toLowerCase();return[t,a.includes("token")||a.includes("api_key")||a.includes("authorization")||a.includes("cookie")||a.includes("secret")?"[redacted]":y(e,t)]})):"string"==typeof t?String(e).toLowerCase().includes("token")?"[redacted]":t.replace(/([?&](?:key|api_key|token)=)[^&]+/gi,"$1[redacted]"):t,g=(t,e,a,r={})=>{if(!t?.debug||"undefined"==typeof console)return;("function"==typeof console[e]?console[e]:console.log).call(console,`[plan-your-day] ${a}`,y(r))},m=t=>Array.isArray(t)?t.map(t=>String(t??"")).filter(Boolean):[],S=(t,e)=>{t&&e&&(t.textContent="",window.requestAnimationFrame(()=>{t.textContent=e}))},h=t=>o.includes(String(t||"")),f=t=>{const e=String(t||"");return s.includes(e)?e:a},b=()=>{try{const t=window.localStorage?.getItem(e);return h(t)?t:""}catch(t){return""}},w=()=>"undefined"==typeof window||"function"!=typeof window.matchMedia?null:window.matchMedia("(prefers-color-scheme: dark)"),v=(t,e=w())=>{const o=b();if(h(o))return o;const s=f(t);return s===n?e?.matches?r:a:s},L=(t,e,n,o)=>{const s=h(n)?n:a;t.setAttribute("data-plan-color-mode",s),((t,e,a)=>{t.colorModeToggle instanceof HTMLButtonElement&&(t.colorModeToggle.hidden=!1,t.colorModeToggle.setAttribute("aria-pressed",String(e===r)),t.colorModeToggle.setAttribute("aria-label",String(a.darkModeLabel||"Dark mode")),t.colorModeToggleLabel instanceof HTMLElement&&(t.colorModeToggleLabel.textContent=String(a.darkModeLabel||"Dark mode")))})(e,s,o)},M=t=>{const e=t.find(t=>t.checked);return e?e.value:""},R=t=>{const e=String(t||"");return d.includes(e)?e:""},E=(t,e)=>{const a=t.form instanceof HTMLFormElement?new FormData(t.form):new FormData;return{category:String(a.get("category")||e.category||""),category_search:String(a.get("category_search")||e.categorySearch||""),waypoints:a.getAll("waypoints[]").map(t=>String(t||"")).filter(Boolean),start_mode:String(a.get("start_mode")||e.startMode||"default"),custom_start:String(a.get("custom_start")||"")}},_=(t,e,a)=>{const r=String(t?.id||""),n=String(t?.label||""),o=String(t?.address||""),s=String(t?.distance_label||""),i=String(t?.maps_uri||""),l=e.includes(r);return`\n
  • \n
    \n

    ${c(n)}

    \n ${s?`

    ${c(s)}

    `:""}\n

    ${c(o)}

    \n
    \n
    \n ${i?`${c(a.viewInGoogleMaps||"")}`:""}\n ${l?`${c(a.inTrip||"")}`:``}\n
    \n
  • \n `},A=(t,e,a=!1)=>{if(!t?.hasMoreResults)return"";const r=String(a?e.loadingMoreResults||e.moreResultsButton||"":e.moreResultsButton||"");return`\n
    \n \n
    \n `},q=(t,e,a,r={})=>`\n
    \n ${((t,e,a)=>{const r=Array.isArray(t?.searchResults)?t.searchResults:[];if(0===r.length){const e=t?.resultsEmptyState||{};return`\n
    \n

    ${c(e.heading||"")}

    \n

    ${c(e.body||"")}

    \n
    \n `}return`\n
      \n ${r.map(t=>_(t,e,a)).join("")}\n
    \n `})(t,e,a)}\n
    \n ${A(t,a,Boolean(r.isLoadingMore))}\n `,T=(t,e)=>{const a=new Set((t=>{const e=[];return(Array.isArray(t?.searchResults)?t.searchResults:[]).forEach(t=>{const a=String(t?.id||"");a&&!e.includes(a)&&e.push(a)}),e})(t));return(Array.isArray(e?.searchResults)?e.searchResults:[]).filter(t=>{const e=String(t?.id||"");return!e||!a.has(e)&&(a.add(e),!0)})},H=(t,e)=>{t.categoryInput&&(t.categoryInput.value=e.category||""),t.waypointInputs&&(t.waypointInputs.innerHTML=m(e.route?.selectedWaypointIds).map(t=>``).join(""))},k=(t,e)=>{if(!t.messages)return;const a=Array.isArray(e)?e:[];t.messages.hidden=0===a.length,t.messages.innerHTML=(t=>(Array.isArray(t)?t:[]).map(t=>{const e=String(t?.type||"note"),a=String(t?.text||""),r="warning"===e?"alert":"";return`
  • ${c(a)}
  • `}).join(""))(a)},C=(t,e,a,r)=>{if(!(t instanceof HTMLElement))return;const n=t.querySelector("[data-plan-load-more-wrap]"),o=A(e,a,r);o?n instanceof HTMLElement?n.outerHTML=o:t.insertAdjacentHTML("beforeend",o):n instanceof HTMLElement&&n.remove()},$=(t,e,a,r,n={})=>{if(!(t instanceof HTMLElement))return;const o=Boolean(n.appendResults),s=Array.isArray(n.appendedResults)?n.appendedResults:[];o&&(s.length>0&&((t,e,a,r)=>{if(!(t instanceof HTMLElement&&Array.isArray(e)&&0!==e.length))return!1;const n=t.querySelector("[data-plan-results-list]");if(!(n instanceof HTMLElement))return!1;const o=t.querySelector("[data-plan-results-empty]");return o instanceof HTMLElement&&o.remove(),n.insertAdjacentHTML("beforeend",e.map(t=>_(t,a,r)).join("")),!0})(t,s,a,r)||0===s.length&&(t=>t instanceof HTMLElement&&t.querySelector("[data-plan-results-list], [data-plan-results-empty]")instanceof HTMLElement)(t))?C(t,e,r,Boolean(n.isLoadingMore)):t.innerHTML=q(e,a,r,n)},x=(t,e,a,r={})=>{const n=e.category||"",o=e.expandedCategory||"",s=m(e.route?.selectedWaypointIds),i=e.browse||{},l=Boolean(i.hasSearch)&&!n,u=l||0===t.categoryButtons.length,d=l&&Boolean(e.customResultsExpanded)||!l&&0===t.categoryButtons.length,c={appendResults:Boolean(r.appendResults),appendedResults:Array.isArray(r.appendedResults)?r.appendedResults:[],isLoadingMore:Boolean(r.isLoadingMore)};t.categoryButtons.forEach(t=>{const e=t.getAttribute("data-category-key")||"",a=e===n&&e===o,r=t.closest(".plan-your-day__category-accordion-item");t.setAttribute("aria-expanded",String(a)),r instanceof HTMLElement&&r.classList.toggle("is-expanded",a)}),t.categoryRegions.forEach(t=>{const e=t.getAttribute("data-category-key")||"",r=e===n&&e===o,l=t.querySelector("[data-plan-category-results-panel]");if(t.hidden=!r,l instanceof HTMLElement){if(!r)return void(l.innerHTML="");$(l,i,s,a,c)}}),t.customResults&&(t.customResults.hidden=!u,t.customResults.classList.toggle("is-expanded",d)),t.customResultsButton&&t.customResultsButton.setAttribute("aria-expanded",String(d)),t.customResultsRegion&&(t.customResultsRegion.hidden=!d),t.customResultsHeading&&(t.customResultsHeading.textContent=l?p(a.searchResultsFor||"",{search:i.categoryLabel||""}):String(i?.resultsEmptyState?.heading||"")),t.customResultsDescription&&(t.customResultsDescription.textContent=String(l?a.customSearchResultsDescription||"":i?.resultsEmptyState?.body||"")),t.customResultsPanel&&(d?l?$(t.customResultsPanel,i,s,a,c):t.customResultsPanel.innerHTML=q(i,s,a,{isLoadingMore:!1}):t.customResultsPanel.innerHTML="")},I=(t,e,a)=>{t.tripHeaderActions&&(t.tripHeaderActions.innerHTML=((t,e)=>{const a=m(t?.selectedWaypointIds),r=String(t?.tripCountLabel||"");return`\n ${c(r)}\n ${a.length>0?``:""}\n `})(e.route,a)),t.tripRegion&&(t.tripRegion.innerHTML=((t,e,a)=>{const r=Array.isArray(t?.tripWaypoints)?t.tripWaypoints:[];if(0===r.length){const a=t?.tripEmptyState||{};return`\n
    \n

    ${c(a.heading||e.tripEmptyHeading||"")}

    \n

    ${c(a.body||e.tripEmptyBody||"")}

    \n
    \n `}return`\n
      \n ${r.map((t,a)=>{const n=String(t?.id||""),o=String(t?.label||""),s=String(t?.address||""),i=a>0,l=a\n
      \n \n
      \n

      ${c(o)}

      \n

      ${c(s)}

      \n
      \n
      \n
      \n \n ${c(e.moveUp||"")}\n \n \n ${c(e.moveDown||"")}\n \n \n
      \n \n `}).join("")}\n
    \n `})(e.route,a,t.tripRegion.getAttribute("data-plan-trip-help-id")||""))},B=(t,e,a)=>{const r=e.route||{},n=String(r.iframeSrc||""),o=r.emptyPreviewState||{},s=String(r.mapsUrl||""),i=m(r.selectedWaypointIds).length>0;k(t,r.messages),t.mapWrap&&(t.mapWrap.hidden=""===n),t.iframe&&(t.iframe.src=n),t.previewEmpty&&(t.previewEmpty.hidden=""!==n),t.previewEmptyHeading&&(t.previewEmptyHeading.textContent=String(o.heading||"")),t.previewEmptyBody&&(t.previewEmptyBody.textContent=String(o.body||"")),t.summaryCount&&(t.summaryCount.textContent=String(r.tripCountLabel||""),t.summaryCount.hidden=i),t.openLinkLabel&&(t.openLinkLabel.textContent=String(r.mapsLinkLabel||"")),t.openLink&&(t.openLink.hidden=!i,t.openLink.classList.toggle("is-disabled",""===s),s?(t.openLink.href=s,t.openLink.removeAttribute("aria-disabled"),t.openLink.removeAttribute("tabindex"),t.openLink.removeAttribute("role")):(t.openLink.removeAttribute("href"),t.openLink.setAttribute("aria-disabled","true"),t.openLink.setAttribute("tabindex","0"),t.openLink.setAttribute("role","button")))},P=(t,e,a)=>{t.waypointStatus instanceof HTMLButtonElement&&(t.waypointStatus.textContent=((t,e)=>{const a=m(t?.selectedWaypointIds).length;return a<=0?String(e.waypointStatusEmpty||"Add some waypoints!"):p(String(1===a?e.waypointStatusSingle||"{count} waypoint added":e.waypointStatusPlural||"{count} waypoints added"),{count:a})})(e.route,a))},D=t=>t instanceof HTMLElement&&"function"==typeof t.focus&&(t.focus(),document.activeElement===t),W=t=>{if(!(t instanceof HTMLButtonElement))return null;if(t.matches('[data-plan-action="add-waypoint"]'))return{action:"add-waypoint",placeId:t.getAttribute("data-place-id")||t.value||""};if(t.matches('[data-plan-action="remove-waypoint"]'))return{action:"remove-waypoint",placeId:t.getAttribute("data-place-id")||t.value||""};if(t.matches("[data-plan-clear-trip]"))return{action:"clear-trip",placeId:""};if("move_waypoint"===t.name&&t.value){const[e,a]=String(t.value).split(":",2);return{action:"move-waypoint",placeId:e||"",direction:a||""}}return null},U=(t,e)=>{const a=(t=>{const[e,a]=String(t||"").split(":",2);return!e||"up"!==a&&"down"!==a?null:{placeId:e,direction:a}})(e),r=m(t);if(!a)return r;const n=r.indexOf(a.placeId),o="up"===a.direction?n-1:n+1;return n<0||o<0||o>=r.length||([r[n],r[o]]=[r[o],r[n]]),r},F=t=>(Array.isArray(t)?t:[]).map(t=>({id:String(t?.id||"").trim(),label:String(t?.label||"").trim(),address:String(t?.address||"").trim()})).filter(t=>t.id&&(t.label||t.address)),j=t=>String(t?.address||t?.label||"").trim(),O=(t,e)=>{const a=F(e);if(0===a.length)return"";const r=String(t||"");if(!r)return"";try{const t=new URL(r),e=t.searchParams.get("key")||"",n=t.searchParams.get("origin")||"";if(!e||!n)return r;const o=a[a.length-1],s=a.slice(0,-1);return t.searchParams.set("key",e),t.searchParams.set("origin",n),t.searchParams.set("destination",`place_id:${o.id}`),t.searchParams.set("mode","walking"),s.length>0?t.searchParams.set("waypoints",s.map(t=>`place_id:${t.id}`).join("|")):t.searchParams.delete("waypoints"),t.toString()}catch(t){return r}},K=(t,e)=>{const a=F(e);if(0===a.length)return"";try{const e=new URL(String(t||"https://www.google.com/maps/dir/")),r=e.searchParams.get("origin")||"",n=a[a.length-1],o=a.slice(0,-1);return e.searchParams.set("api","1"),e.searchParams.set("destination",j(n)),e.searchParams.set("destination_place_id",n.id),e.searchParams.set("travelmode","walking"),r?e.searchParams.set("origin",r):e.searchParams.delete("origin"),o.length>0?(e.searchParams.set("waypoints",o.map(j).join("|")),e.searchParams.set("waypoint_place_ids",o.map(t=>t.id).join("|"))):(e.searchParams.delete("waypoints"),e.searchParams.delete("waypoint_place_ids")),e.toString()}catch(e){return String(t||"")}},N=(t,e)=>{if(!e)return;if("add-waypoint"===e.action)return;const a=String(e.placeId||""),r=t.tripHeaderActions?.querySelector("button:not([disabled]):not([hidden])");let n=null;if(a&&"move-waypoint"===e.action){const r=String(e.direction||"");n=t.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="move_waypoint"][value="${a}:${r}"]`)||t.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="remove_waypoint"]`)}else"remove-waypoint"===e.action?n=t.tripRegion?.querySelector('[data-plan-trip-list] button[name="remove_waypoint"]')||r:"clear-trip"===e.action&&(n=r||t.tripHeading);D(n)||D(t.tripHeading)},J=t=>0===m(t?.selectedWaypointIds).length,G=(t,e,a)=>{const r=R(e);t.customStartWrap instanceof HTMLElement&&t.customStartWrap.setAttribute("data-plan-custom-start-state",r),t.customStartStatus instanceof HTMLElement&&(t.customStartStatus.textContent=((t,e)=>t===i?String(e.customStartChecking||"Checking starting address."):t===l?String(e.customStartFound||"Starting address found. Results are ready."):t===u?String(e.customStartNotFound||"Starting address was not found."):"")(r,a))},Y=(t,e,a)=>{t.startModeInputs.forEach(t=>{t.checked=t.value===e.startMode}),t.customStartInput&&(t.customStartInput.value=e.customStart||"");const r="custom"===(M(t.startModeInputs)||e.startMode||"default");t.customStartWrap&&(t.customStartWrap.hidden=!r),t.customStartInput&&(t.customStartInput.disabled=!r),G(t,r?e.customStartStatus:"",a)},z=(t,e)=>{if(!(t.categorySearchInput instanceof HTMLInputElement))return;const a=String(e.categorySearch||"");t.categorySearchInput.value!==a&&(t.categorySearchInput.value=a)},V=(t,e)=>{t.classList.toggle("is-submitting",e),t.setAttribute("aria-busy",String(e))},Q=(t,e,a)=>{t.forEach(t=>{if(!(t instanceof HTMLButtonElement||t instanceof HTMLInputElement))return;if(e)return t.hasAttribute(a)||t.setAttribute(a,t.disabled?"true":"false"),void(t.disabled=!0);const r=t.getAttribute(a);null!==r&&(t.disabled="true"===r,t.removeAttribute(a))})},X=(t,e)=>{Q(t.querySelectorAll("[data-plan-route-mutation]"),e,"data-plan-disabled-before-request")},Z=(t,e)=>{Q(t.querySelectorAll(['[data-plan-form] button[type="submit"]:not([data-plan-route-mutation])',"[data-plan-load-more-button]","[data-plan-start-toggle]","[data-plan-custom-results-button]",'input[name="start_mode"]',"[data-plan-custom-start]","[data-plan-category-search]"].join(",")),e,"data-plan-browse-disabled-before-request")},tt=(t,e,a)=>{const r=e.category||"";if(t.categoryPanels.forEach(e=>{const n=e.getAttribute("data-category-key")||"",o=0===t.categoryButtons.length||n===r||!1===t.customResults?.hidden;e.setAttribute("aria-busy",String(a&&o))}),t.customResultsPanel instanceof HTMLElement){const e=!1===t.customResults?.hidden;t.customResultsPanel.setAttribute("aria-busy",String(a&&e))}t.tripRegion instanceof HTMLElement&&t.tripRegion.setAttribute("aria-busy",String(a)),t.previewCard instanceof HTMLElement&&t.previewCard.setAttribute("aria-busy",String(a))},et=o=>{if(!(o instanceof HTMLElement)||"true"===o.dataset[t])return;o.dataset[t]="true";const s=(t=>{const e=t.querySelector("[data-plan-config]");if(!e)return{};try{return JSON.parse(e.textContent||"{}")}catch(t){return{}}})(o),l=s.strings||{},u={form:o.querySelector("[data-plan-form]"),liveRegion:o.querySelector("[data-plan-live-region]"),categoryInput:o.querySelector("[data-plan-category-input]"),waypointInputs:o.querySelector("[data-plan-waypoint-inputs]"),categoryButtons:Array.from(o.querySelectorAll("[data-plan-category-button]")),categoryItems:Array.from(o.querySelectorAll("[data-plan-category-item]")),categoryRegions:Array.from(o.querySelectorAll("[data-plan-category-region]")),categoryPanels:Array.from(o.querySelectorAll("[data-plan-category-results-panel]")),categorySearchInput:o.querySelector("[data-plan-category-search]"),customResults:o.querySelector("[data-plan-custom-results]"),customResultsButton:o.querySelector("[data-plan-custom-results-button]"),customResultsHeading:o.querySelector("[data-plan-custom-results-heading]"),customResultsDescription:o.querySelector("[data-plan-custom-results-description]"),customResultsRegion:o.querySelector("[data-plan-custom-results-region]"),customResultsPanel:o.querySelector("[data-plan-custom-results-panel]"),resultsHeading:o.querySelector("[data-plan-results-heading]"),startModeInputs:Array.from(o.querySelectorAll('input[name="start_mode"]')),customStartWrap:o.querySelector("[data-plan-custom-start-wrap]"),customStartInput:o.querySelector("[data-plan-custom-start]"),customStartStatus:o.querySelector("[data-plan-custom-start-status]"),startToggle:o.querySelector("[data-plan-start-toggle]"),startToggleLabel:o.querySelector("[data-plan-start-toggle-label]"),startPanel:o.querySelector("[data-plan-start-panel]"),colorModeToggle:o.querySelector("[data-plan-color-mode-toggle]"),colorModeToggleLabel:o.querySelector("[data-plan-color-mode-toggle-label]"),tripHeaderActions:o.querySelector("[data-plan-trip-header-actions]"),tripHeading:o.querySelector("[data-plan-trip-heading]"),tripRegion:o.querySelector("[data-plan-trip-region]"),messages:o.querySelector("[data-plan-messages]"),previewCard:o.querySelector("[data-plan-preview-card]"),mapWrap:o.querySelector("[data-plan-map-wrap]"),iframe:o.querySelector("[data-plan-iframe]"),previewEmpty:o.querySelector("[data-plan-preview-empty]"),previewEmptyHeading:o.querySelector("[data-plan-preview-empty-heading]"),previewEmptyBody:o.querySelector("[data-plan-preview-empty-body]"),summaryCount:o.querySelector("[data-plan-summary-count]"),openLink:o.querySelector("[data-plan-open-link]"),openLinkLabel:o.querySelector("[data-plan-open-link-label]"),waypointStatus:o.querySelector("[data-plan-waypoint-status]"),apiCallCounter:o.querySelector("[data-plan-api-call-counter]"),apiCallCount:o.querySelector("[data-plan-api-call-count]"),apiCallBreakdown:o.querySelector("[data-plan-api-call-breakdown]")},d={category:String(s.initialState?.category||""),categorySearch:String(s.initialState?.categorySearch||""),startMode:String(s.initialState?.startMode||"default"),customStart:String(s.initialState?.customStart||""),customStartStatus:R(s.initialData?.browse?.customStartStatus||""),expandedCategory:String(s.initialState?.category||""),customResultsExpanded:Boolean(s.initialData?.browse?.isCustomSearch),isLoadingMore:!1,browse:s.initialData?.browse||{},route:s.initialData?.route||{}},c="string"==typeof s.rest?.bootstrapUrl&&""!==s.rest.bootstrapUrl;let y="string"==typeof s.rest?.endpointToken?s.rest.endpointToken:"",_=!1,A=null;const q=u.form instanceof HTMLFormElement&&"string"==typeof s.rest?.browseUrl&&""!==s.rest.browseUrl&&"string"==typeof s.rest?.routeUrl&&""!==s.rest.routeUrl&&(""!==y||c),C=Boolean(s.hydration?.shouldHydrateOnLoad),$=f(s.colorModeDefault||o.getAttribute("data-plan-color-mode-default")),D=w();let F=!0,j=null,G="",Q=0,et=null;const at="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches;let rt=0;const nt={bootstrap:0,browse:0,route:0},ot=()=>{if(!(u.apiCallCounter instanceof HTMLElement))return;const t=nt.bootstrap+nt.browse+nt.route,e=`Bootstrap ${nt.bootstrap}, Browse ${nt.browse}, Route ${nt.route}`;u.apiCallCount instanceof HTMLElement&&(u.apiCallCount.textContent=String(t)),u.apiCallBreakdown instanceof HTMLElement&&(u.apiCallBreakdown.textContent=e),u.apiCallCounter.setAttribute("title",e),u.apiCallCounter.setAttribute("aria-label",`API calls: ${t}. ${e}.`)},st=t=>{Object.prototype.hasOwnProperty.call(nt,t)&&(nt[t]+=1,ot())};L(o,u,v($,D),l),ot(),$===n&&D&&D.addEventListener("change",()=>{b()||L(o,u,v($,D),l)});const it=()=>{x(u,d,l,{isLoadingMore:d.isLoadingMore}),I(u,d,l),B(u,d),P(u,d,l),H(u,d),Y(u,d,l),z(u,d),X(o,!1)},lt=(t,e)=>{const a=m(d.route?.selectedWaypointIds),r=U(a,t);if(!(a.length!==r.length||a.some((t,e)=>t!==r[e])))return!0;const n=((t,e)=>{const a=new Map;return(Array.isArray(t)?t:[]).forEach(t=>{const e=String(t?.id||"");e&&a.set(e,t)}),m(e).map(t=>a.get(t)).filter(Boolean)})(d.route?.tripWaypoints,r);return n.length===r.length&&(d.route={...d.route||{},selectedWaypointIds:r,tripWaypoints:n,hasTrip:r.length>0,iframeSrc:O(d.route?.iframeSrc||"",n),mapsUrl:K(d.route?.mapsUrl||"",n)},I(u,d,l),B(u,d),P(u,d,l),H(u,d),N(u,e),S(u.liveRegion,l.tripUpdated||""),g(s,"info","route:move-local",{moveWaypoint:t,selectedWaypointIds:r}),!0)},ut=(t,e="")=>{k(u,[{type:"warning",text:t||l.requestFailed||""}]),S(u.liveRegion,e||t||l.requestFailed||"")},dt=(t={})=>{if(!u.startToggle||!u.startPanel)return;const e=!1!==t.syncHidden;u.startToggle.hidden=!1,u.startToggle.setAttribute("aria-expanded",String(F)),u.startToggle.classList.toggle("is-collapsed",!F),e&&(u.startPanel.hidden=!F),u.startToggleLabel&&(u.startToggleLabel.textContent=String(F?l.hideStartOptions||"Hide options":l.showStartOptions||"Show options"))},ct=t=>{if(!(u.startPanel instanceof HTMLElement&&u.startToggle))return F=t,void dt();const e=u.startPanel,a=at?0:480;rt&&(window.cancelAnimationFrame(rt),rt=0);const r=e.hidden?0:e.getBoundingClientRect().height;e.hidden=!1,t&&(e.style.height="");const n=t?e.scrollHeight:0,o=t&&n>0?Math.max(r/n,0):1,s=t?1:0;if(F=t,e.style.overflow="hidden",e.style.pointerEvents="none",e.style.height=`${r}px`,e.style.opacity=String(o),dt({syncHidden:!1}),a<=0||r===n)return e.style.height="",e.style.overflow="",e.style.pointerEvents="",e.style.opacity="",e.hidden=!t,void dt();const i=performance.now(),l=u=>{const d=Math.min((u-i)/a,1),c=(p=d)<.5?4*p*p*p:1-Math.pow(-2*p+2,3)/2;var p;const y=r+(n-r)*c,g=o+(s-o)*c;e.style.height=`${Math.max(y,0)}px`,e.style.opacity=String(Math.max(Math.min(g,1),0)),d<1?rt=window.requestAnimationFrame(l):(e.style.height="",e.style.overflow="",e.style.pointerEvents="",e.style.opacity="",e.hidden=!t,dt(),rt=0)};rt=window.requestAnimationFrame(l)},pt=async(t,e,a={})=>{if(!q)return"unsupported";const r=Boolean(a.appendBrowseResults),n=String(a.announcementMessage||""),m=String(a.errorMessage||""),h=String(a.searchContextKey||""),f="browse"===t&&!1!==a.refreshRoute,b="route"===t?a.routeFocusRequest??null:null,w=String(e.start_mode||""),v=String(e.custom_start||""),L="browse"===t&&!r&&"custom"===w&&""!==v.trim(),M="route"===t&&(d.customStartStatus===i||"custom"!==w||""===v.trim()||v!==String(d.customStart||""));if(j instanceof AbortController){if("route"===G)return g(s,"info","request:blocked",{endpointKey:t,blockedBy:G}),"busy";j.abort()}Q+=1;const E=Q;"route"===t&&(et=b?{requestId:E,focusRequest:b}:null),j=new AbortController,G=t,V(o,!0),tt(u,d,!0),X(o,"route"===t),Z(o,"route"===t),L?(d.customStartStatus=i,Y(u,d,l)):("browse"===t&&!r||M)&&(d.customStartStatus="",Y(u,d,l)),g(s,"info","request:start",{endpointKey:t,payload:e});try{const a=await(async()=>c?_&&""!==y?y:A||(st("bootstrap"),A=fetch(s.rest.bootstrapUrl,{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({})}).then(async t=>{const e=await t.json().catch(()=>({}));if(g(s,t.ok?"info":"warn","request:bootstrap",{status:t.status,ok:t.ok,body:e}),!t.ok)throw new Error(e?.message||l.requestFailed||"");const a=String(e?.endpointToken||"");if(""===a)throw new Error(l.requestFailed||"");return y=a,_=!0,y}).finally(()=>{A=null}),A):y)();if(""===a)throw new Error(l.requestFailed||"");const i={...e,endpoint_token:a};"browse"===t&&(i.refresh_route=f,""!==h&&(i.search_context_key=h)),st(t);const b=await fetch(s.rest["browse"===t?"browseUrl":"routeUrl"],{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(i),signal:j.signal}),w=await b.json().catch(()=>({}));if(g(s,b.ok?"info":"warn","request:response",{endpointKey:t,status:b.status,ok:b.ok,body:w}),!b.ok)throw new Error(w?.message||l.requestFailed||"");if(E!==Q)return!0;if("browse"===t){const t=d.category||"",a=d.expandedCategory||"",n=d.categorySearch||"",s=w?.browse||{};if(r&&""!==String(s.searchResultsError||""))return d.isLoadingMore=!1,x(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!1}),ut(m||l.requestFailed||"",m||l.requestFailed||""),"failed";const i=r&&""!==h&&h===String(d.browse?.searchContextKey||"")&&h===String(s.searchContextKey||""),c=i?T(d.browse,s):[];if(d.browse=i?{...(k=d.browse)||{},...(C=s)||{},searchResults:[...Array.isArray(k?.searchResults)?k.searchResults:[],...T(k,C)]}:s,d.route=w?.route||d.route||{},d.category=String(d.browse.categoryKey||e.category||""),d.categorySearch=String(d.browse.categorySearch||e.category_search||""),d.customStartStatus=R(d.browse.customStartStatus||""),d.isLoadingMore=!1,d.category?(d.expandedCategory=d.category===t?a:d.category,d.customResultsExpanded=!1):d.browse.hasSearch?String(e.category_search||"")!==n&&(d.expandedCategory="",d.customResultsExpanded=!0):(d.expandedCategory="",d.customResultsExpanded=!1),i)return x(u,d,l,{appendResults:!0,appendedResults:c,isLoadingMore:!1}),I(u,d,l),B(u,d),P(u,d,l),H(u,d),Y(u,d,l),z(u,d),X(o,!1),S(u.liveRegion,d.browse?.searchResultsError?m||l.requestFailed||"":((t,e,a)=>t>0?p(a.loadedMoreResults||"",{count:t}):String(e?.hasMoreResults?a.resultsUpdated||"":a.noMoreResults||""))(c.length,d.browse,l)),"success"}else d.route=w?.route||d.route||{},d.category=String(d.route.categoryKey||d.category||""),d.categorySearch=String(d.route.categorySearch||e.category_search||"");return d.startMode=String(e.start_mode||d.startMode||"default"),d.customStart=String(e.custom_start||""),it(),"route"===t&&et&&et.requestId===E&&(N(u,et.focusRequest),et=null),S(u.liveRegion,n||""),"success"}catch(a){return"AbortError"===a?.name?(g(s,"info","request:aborted",{endpointKey:t}),E===Q&&L&&(d.customStartStatus="",Y(u,d,l)),"aborted"):E!==Q?"stale":(g(s,"error","request:failed",{endpointKey:t,error:a instanceof Error?a.message:String(a||""),payload:e}),r&&(d.isLoadingMore=!1,x(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!1})),ut(r?m||l.requestFailed||"":a instanceof Error?a.message:l.requestFailed||"",r&&(m||l.requestFailed)||""),L&&(d.customStartStatus="",Y(u,d,l)),"failed")}finally{E===Q&&(r&&d.isLoadingMore&&(d.isLoadingMore=!1),V(o,!1),tt(u,d,!1),X(o,!1),"route"===t&&et&&et.requestId===E&&(et=null),Z(o,!1),j=null,G="")}var k,C};if(u.startModeInputs.forEach(t=>{t.addEventListener("change",()=>{d.startMode=M(u.startModeInputs)||d.startMode||"default",Y(u,d,l),q?pt("browse",E(u,d),{announcementMessage:l.startingPointUpdated||"",refreshRoute:!0}):S(u.liveRegion,l.startingPointUpdated||"")})}),u.customStartInput instanceof HTMLInputElement&&u.customStartInput.addEventListener("change",()=>{d.customStart=u.customStartInput.value||"",Y(u,d,l),q?pt("browse",E(u,d),{announcementMessage:l.startingPointUpdated||"",refreshRoute:!0}):S(u.liveRegion,l.startingPointUpdated||"")}),u.categorySearchInput instanceof HTMLInputElement&&(u.categorySearchInput.addEventListener("input",()=>{d.categorySearch=u.categorySearchInput.value||""}),u.categorySearchInput.addEventListener("keydown",t=>{if("Enter"!==t.key||!q)return;t.preventDefault();const e=E(u,d);e.category="",e.category_search=u.categorySearchInput.value||"",d.expandedCategory="",d.customResultsExpanded=!0,pt("browse",e,{announcementMessage:l.resultsUpdated||"",refreshRoute:J(d.route)})})),u.form instanceof HTMLFormElement&&u.form.addEventListener("submit",t=>{const e=t.submitter;if(!(e instanceof HTMLButtonElement&&q))return;let a="browse",r=l.resultsUpdated||"";const n=E(u,d);if(e.matches("[data-plan-category-button]")){const a=e.getAttribute("data-category-key")||"";if(a===d.category){const r=e.querySelector(".plan-your-day__category-title")?.textContent?.trim()||a;return t.preventDefault(),d.expandedCategory=d.expandedCategory===a?"":a,d.customResultsExpanded=!1,x(u,d,l),void S(u.liveRegion,d.expandedCategory===a?p(l.categoryResultsExpanded||"",{category:r}):p(l.categoryResultsCollapsed||"",{category:r}))}n.category=a,n.category_search="",d.expandedCategory=a,d.customResultsExpanded=!1}else if(e.matches('[data-plan-action="search-category-query"]'))n.category="",n.category_search=u.categorySearchInput instanceof HTMLInputElement&&u.categorySearchInput.value||"",d.expandedCategory="",d.customResultsExpanded=!0;else if(e.matches('[data-plan-action="add-waypoint"]'))n.waypoints=[...n.waypoints,e.getAttribute("data-place-id")||e.value||""],a="route",r=l.tripUpdated||"";else if(e.matches('[data-plan-action="remove-waypoint"]'))n.remove_waypoint=e.getAttribute("data-place-id")||e.value||"",a="route",r=l.tripUpdated||"";else if(e.matches("[data-plan-clear-trip]"))n.clear_trip=!0,a="route",r=l.tripUpdated||"";else if("move_waypoint"===e.name&&e.value){const o=W(e);if(t.preventDefault(),lt(e.value,o))return;n.move_waypoint=e.value,a="route",r=l.tripUpdated||""}t.preventDefault(),pt(a,n,{announcementMessage:r,refreshRoute:"browse"===a?J(d.route):void 0,routeFocusRequest:"route"===a?W(e):null})}),o.addEventListener("click",t=>{const n=t.target;if(!(n instanceof HTMLElement))return;if(n.closest('[data-plan-open-link][aria-disabled="true"]')instanceof HTMLElement)return t.preventDefault(),void S(u.liveRegion,l.openMapsDisabled||"");if(n.closest("[data-plan-waypoint-status]")instanceof HTMLButtonElement){const e=u.mapWrap instanceof HTMLElement&&!u.mapWrap.hidden?u.mapWrap:u.previewCard;return t.preventDefault(),void(e instanceof HTMLElement&&e.scrollIntoView({behavior:at?"auto":"smooth",block:"start"}))}if(n.closest("[data-plan-start-toggle]")instanceof HTMLButtonElement)return t.preventDefault(),F?F&&ct(!1):F||ct(!0),void S(u.liveRegion,F?l.startOptionsExpanded||"":l.startOptionsCollapsed||"");if(n.closest("[data-plan-color-mode-toggle]")instanceof HTMLButtonElement){t.preventDefault();const n=(o.getAttribute("data-plan-color-mode")===r?r:a)===r?a:r;return(t=>{if(h(t))try{window.localStorage?.setItem(e,t)}catch(t){}})(n),void L(o,u,n,l)}if(n.closest("[data-plan-custom-results-button]")instanceof HTMLButtonElement){if(t.preventDefault(),!d.browse?.hasSearch||d.category)return;return d.expandedCategory="",d.customResultsExpanded=!d.customResultsExpanded,x(u,d,l),void S(u.liveRegion,d.customResultsExpanded?String(l.customResultsExpanded||""):String(l.customResultsCollapsed||""))}const s=n.closest("[data-plan-load-more-button]");if(s instanceof HTMLButtonElement){if(t.preventDefault(),!q||d.isLoadingMore||s.disabled)return;const e=String(d.browse?.nextPageToken||"");if(!e)return void S(u.liveRegion,l.noMoreResults||"");d.isLoadingMore=!0,x(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!0}),S(u.liveRegion,l.loadingMoreResults||""),pt("browse",{...E(u,d),page_token:e,append_results:!0},{appendBrowseResults:!0,errorMessage:l.loadMoreError||"",refreshRoute:!1,searchContextKey:String(d.browse?.searchContextKey||"")})}}),o.addEventListener("keydown",t=>{const e=t.target;if(!(e instanceof HTMLElement))return;e.closest('[data-plan-open-link][aria-disabled="true"]')instanceof HTMLElement&&("Enter"!==t.key&&" "!==t.key||(t.preventDefault(),S(u.liveRegion,l.openMapsDisabled||"")))}),it(),dt(),o.classList.add("is-enhanced"),C){if(!q)return void ut(l.requestFailed||"");pt("browse",E(u,d),{refreshRoute:!0})}},at=()=>{document.querySelectorAll("[data-plan-root]").forEach(et)};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",at,{once:!0}):at()})(); \ No newline at end of file diff --git a/plugin/plan-your-day/blocks/planner/block.json b/plugin/waypoints/blocks/planner/block.json similarity index 76% rename from plugin/plan-your-day/blocks/planner/block.json rename to plugin/waypoints/blocks/planner/block.json index 73b58b0..c2b6e09 100644 --- a/plugin/plan-your-day/blocks/planner/block.json +++ b/plugin/waypoints/blocks/planner/block.json @@ -1,11 +1,11 @@ { "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, - "name": "plan-your-day/planner", + "name": "waypoints/planner", "title": "Waypoints", "category": "widgets", "description": "Render the Waypoints planner through the block editor.", - "textdomain": "plan-your-day", + "textdomain": "waypoints", "keywords": [ "planner", "maps", @@ -20,5 +20,5 @@ "supports": { "html": false }, - "editorScript": "plan-your-day-block-editor" + "editorScript": "waypoints-block-editor" } diff --git a/plugin/plan-your-day/composer.json b/plugin/waypoints/composer.json similarity index 96% rename from plugin/plan-your-day/composer.json rename to plugin/waypoints/composer.json index 953571f..958a1f9 100644 --- a/plugin/plan-your-day/composer.json +++ b/plugin/waypoints/composer.json @@ -1,5 +1,5 @@ { - "name": "acodebeard/plan-your-day", + "name": "acodebeard/waypoints", "description": "A configurable day planning plugin for WordPress.", "type": "wordpress-plugin", "license": "GPL-2.0-or-later", diff --git a/plugin/plan-your-day/composer.lock b/plugin/waypoints/composer.lock similarity index 99% rename from plugin/plan-your-day/composer.lock rename to plugin/waypoints/composer.lock index 7133c8a..609beaf 100644 --- a/plugin/plan-your-day/composer.lock +++ b/plugin/waypoints/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8a398f70242e673ebb3e69e04e537796", + "content-hash": "5da5570b9cea2c3b2b233b891b615f2e", "packages": [], "packages-dev": [ { diff --git a/plugin/plan-your-day/docs/phpstan.md b/plugin/waypoints/docs/phpstan.md similarity index 81% rename from plugin/plan-your-day/docs/phpstan.md rename to plugin/waypoints/docs/phpstan.md index 18661d8..1ba1042 100644 --- a/plugin/plan-your-day/docs/phpstan.md +++ b/plugin/waypoints/docs/phpstan.md @@ -14,7 +14,7 @@ Run PHPStan from the plugin directory: composer run phpstan ``` -The config scans `plan-your-day.php`, `uninstall.php`, and `src/`. WordPress +The config scans the root PHP bootstrap file, `uninstall.php`, and `src/`. WordPress symbols are provided by `php-stubs/wordpress-stubs`, and plugin constants are defined in `tools/phpstan/bootstrap.php` so the scan does not require a running WordPress install. diff --git a/plugin/plan-your-day/phpcs.xml.dist b/plugin/waypoints/phpcs.xml.dist similarity index 100% rename from plugin/plan-your-day/phpcs.xml.dist rename to plugin/waypoints/phpcs.xml.dist diff --git a/plugin/plan-your-day/phpstan.neon b/plugin/waypoints/phpstan.neon similarity index 100% rename from plugin/plan-your-day/phpstan.neon rename to plugin/waypoints/phpstan.neon diff --git a/plugin/plan-your-day/phpunit.xml.dist b/plugin/waypoints/phpunit.xml.dist similarity index 100% rename from plugin/plan-your-day/phpunit.xml.dist rename to plugin/waypoints/phpunit.xml.dist diff --git a/plugin/plan-your-day/plan-your-day.php b/plugin/waypoints/plan-your-day.php similarity index 93% rename from plugin/plan-your-day/plan-your-day.php rename to plugin/waypoints/plan-your-day.php index 3522366..d82de2c 100644 --- a/plugin/plan-your-day/plan-your-day.php +++ b/plugin/waypoints/plan-your-day.php @@ -2,11 +2,11 @@ /** * Plugin Name: Waypoints * Description: A configurable day planning plugin for WordPress. - * Version: 0.5 + * Version: 1.0 * Requires at least: 6.8 * Requires PHP: 8.2 * Author: acodebeard - * Text Domain: plan-your-day + * Text Domain: waypoints * Domain Path: /languages * License: GPL-2.0-or-later * License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -16,12 +16,12 @@ defined( 'ABSPATH' ) || exit; -define( 'PLAN_YOUR_DAY_VERSION', '0.5' ); +define( 'PLAN_YOUR_DAY_VERSION', '1.0' ); define( 'PLAN_YOUR_DAY_SCHEMA_VERSION', 5 ); define( 'PLAN_YOUR_DAY_PLUGIN_FILE', __FILE__ ); define( 'PLAN_YOUR_DAY_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'PLAN_YOUR_DAY_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); -define( 'PLAN_YOUR_DAY_TEXT_DOMAIN', 'plan-your-day' ); +define( 'PLAN_YOUR_DAY_TEXT_DOMAIN', 'waypoints' ); $plan_your_day_autoload = PLAN_YOUR_DAY_PLUGIN_DIR . 'vendor/autoload.php'; if ( ! is_readable( $plan_your_day_autoload ) ) { diff --git a/plugin/plan-your-day/readme.txt b/plugin/waypoints/readme.txt similarity index 63% rename from plugin/plan-your-day/readme.txt rename to plugin/waypoints/readme.txt index 5759cef..19f74e6 100644 --- a/plugin/plan-your-day/readme.txt +++ b/plugin/waypoints/readme.txt @@ -4,7 +4,7 @@ Tags: planning, maps, wayfinding Requires at least: 6.8 Tested up to: 7.0 Requires PHP: 8.2 -Stable tag: 0.5 +Stable tag: 1.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -14,23 +14,30 @@ A configurable day planning plugin for WordPress. Waypoints is a configurable day planning plugin for WordPress. -Current development builds include the plugin scaffold, Settings API -registration, admin settings screen, editable categories and interface copy, -Google API client abstraction, Google API caching, frontend planner rendering, -block and shortcode entry points, REST endpoints, and extracted planner helper -services. +Use Waypoints to add a searchable trip-planning interface to a page or post. +Visitors can search Google place results, add stops to a waypoint list, reorder +their trip, preview the route when map embeds are configured, and open the route +in Google Maps. + +== Credits == + +Special thanks to Hagan Franks (https://github.com/hagan) and Christopher +Reaume (https://github.com/datapoke) for development help. + +Thanks also to Destination Kona Coast (https://destinationkonacoast.com) for +the idea that started the project. == Installation == Release zip: -1. Build or download the packaged `plan-your-day` release zip. +1. Build or download the packaged `waypoints` release zip. 2. Upload it through the Plugins screen in WordPress. 3. Activate the plugin. Release zips include generated Composer autoload files. Source checkout: -1. Copy or symlink the `plan-your-day` directory to `/wp-content/plugins/`. +1. Copy or symlink this plugin directory as `/wp-content/plugins/waypoints/`. 2. Run `composer install` inside the plugin directory to generate `vendor/autoload.php`. 3. Activate the plugin through the Plugins screen in WordPress. @@ -39,8 +46,7 @@ Source checkout: == Configuration == -Settings are stored in the `plan_your_day_settings` option and managed through -Settings > Waypoints. +Settings are managed through Settings > Waypoints. Current settings include: @@ -52,6 +58,7 @@ Current settings include: * Server-side Places and Geocoding API keys. * Google API timeout and cache TTLs. * Rate-limit value and trusted proxy CIDRs for public endpoint protection. +* Admin-only API call counter for troubleshooting request behavior. == External services == @@ -85,17 +92,17 @@ Google provides these services. Review their terms and privacy information: == Frequently Asked Questions == -= Is this ready for production? = += What do I need before using it on a live site? = -Not yet. The plugin is installable and includes the planner frontend, REST -endpoints, and admin/settings flow, but production QA and release hardening are -still in progress. +Configure the required default location and Google API keys in Settings > +Waypoints. For best results, restrict Google API keys in Google Cloud Console +and test the planner flow on a staging site before adding it to a public page. = How do I display the planner? = -Use the Waypoints block in the block editor or add `[plan_your_day]` to a -page, post, or Shortcode block. The repository docs cover placement guidance -and the optional `action_url` / `Action URL` setting. +Use the Waypoints block in the block editor or add `[waypoints]` to a page, +post, or Shortcode block. The optional `action_url` shortcode attribute and +`Action URL` block setting can submit planner updates to a specific page URL. = Where is the developer documentation? = @@ -104,6 +111,13 @@ architecture, settings, security, and troubleshooting notes. == Changelog == += 1.0 = +* Renamed the public plugin surface to Waypoints, including the text domain, REST namespace, block name, preferred shortcode, release metadata, and package artifact. +* Added WordPress.org submission-readiness checks covering PHP quality, PHPStan, browser smoke tests, WordPress Plugin Check, release packaging, and metadata validation. +* Hardened the release artifact so it ships as a single `waypoints/` plugin directory with production Composer autoload files and normalized file permissions. +* Added admin-only API request counting for troubleshooting Google API usage. +* Added contributor credits and clearer production installation, configuration, and external-service documentation. + = 0.5 = * Added block-editor and shortcode entry points that share the same planner renderer. * Added admin-editable categories and interface copy settings. diff --git a/plugin/plan-your-day/release.json b/plugin/waypoints/release.json similarity index 68% rename from plugin/plan-your-day/release.json rename to plugin/waypoints/release.json index 905b87b..5f3b1c1 100644 --- a/plugin/plan-your-day/release.json +++ b/plugin/waypoints/release.json @@ -1,9 +1,9 @@ { "name": "Waypoints", - "slug": "plan-your-day", - "version": "0.5", + "slug": "waypoints", + "version": "1.0", "schemaVersion": 5, - "artifact": "../../dist/plan-your-day-0.5.zip", + "artifact": "../../dist/waypoints-1.0.zip", "distribution": "github-release-zip", "notes": "Installable WordPress admin zip built from this source repository with Composer production autoload files included." } diff --git a/plugin/plan-your-day/src/Activator.php b/plugin/waypoints/src/Activator.php similarity index 100% rename from plugin/plan-your-day/src/Activator.php rename to plugin/waypoints/src/Activator.php diff --git a/plugin/plan-your-day/src/Admin/SettingsPage.php b/plugin/waypoints/src/Admin/SettingsPage.php similarity index 79% rename from plugin/plan-your-day/src/Admin/SettingsPage.php rename to plugin/waypoints/src/Admin/SettingsPage.php index 1969dde..165f5be 100644 --- a/plugin/plan-your-day/src/Admin/SettingsPage.php +++ b/plugin/waypoints/src/Admin/SettingsPage.php @@ -33,8 +33,8 @@ public function __construct( public function register(): void { add_options_page( - __( 'Waypoints Settings', 'plan-your-day' ), - __( 'Waypoints', 'plan-your-day' ), + __( 'Waypoints Settings', 'waypoints' ), + __( 'Waypoints', 'waypoints' ), 'manage_options', Settings::PAGE_SLUG, [ $this, 'render' ] @@ -42,15 +42,15 @@ public function register(): void { add_settings_section( 'plan_your_day_default_location', - __( 'Default Location', 'plan-your-day' ), + __( 'Default Location', 'waypoints' ), [ $this, 'render_default_location_section' ], Settings::PAGE_SLUG ); $this->add_field( 'default_location_label', - __( 'Default location label', 'plan-your-day' ), - __( 'Required. Human-readable label used for the default trip start.', 'plan-your-day' ), + __( 'Default location label', 'waypoints' ), + __( 'Required. Human-readable label used for the default trip start.', 'waypoints' ), 'text', [], 'plan_your_day_default_location' @@ -58,8 +58,8 @@ public function register(): void { $this->add_field( 'default_location_address', - __( 'Default location address or search phrase', 'plan-your-day' ), - __( 'Required. Address, landmark, or search phrase used when the planner needs a stable starting area.', 'plan-your-day' ), + __( 'Default location address or search phrase', 'waypoints' ), + __( 'Required. Address, landmark, or search phrase used when the planner needs a stable starting area.', 'waypoints' ), 'textarea', [ 'rows' => 3, @@ -69,8 +69,8 @@ public function register(): void { $this->add_field( 'default_location_latitude', - __( 'Default latitude', 'plan-your-day' ), - __( 'Optional. Decimal latitude from -90 to 90 for distance hints and search biasing.', 'plan-your-day' ), + __( 'Default latitude', 'waypoints' ), + __( 'Optional. Decimal latitude from -90 to 90 for distance hints and search biasing.', 'waypoints' ), 'number', [ 'min' => -90, @@ -82,8 +82,8 @@ public function register(): void { $this->add_field( 'default_location_longitude', - __( 'Default longitude', 'plan-your-day' ), - __( 'Optional. Decimal longitude from -180 to 180 for distance hints and search biasing.', 'plan-your-day' ), + __( 'Default longitude', 'waypoints' ), + __( 'Optional. Decimal longitude from -180 to 180 for distance hints and search biasing.', 'waypoints' ), 'number', [ 'min' => -180, @@ -95,8 +95,8 @@ public function register(): void { $this->add_field( 'default_location_place_id', - __( 'Default Google Place ID', 'plan-your-day' ), - __( 'Optional. Save the exact Google Place ID for the default location when you want a stable place reference alongside the address.', 'plan-your-day' ), + __( 'Default Google Place ID', 'waypoints' ), + __( 'Optional. Save the exact Google Place ID for the default location when you want a stable place reference alongside the address.', 'waypoints' ), 'text', [], 'plan_your_day_default_location' @@ -104,15 +104,15 @@ public function register(): void { add_settings_section( 'plan_your_day_appearance', - __( 'Appearance', 'plan-your-day' ), + __( 'Appearance', 'waypoints' ), [ $this, 'render_appearance_section' ], Settings::PAGE_SLUG ); $this->add_field( 'color_mode_default', - __( 'Default color mode', 'plan-your-day' ), - __( 'Choose the public planner color mode before a visitor makes their own choice. System follows the visitor browser or OS preference.', 'plan-your-day' ), + __( 'Default color mode', 'waypoints' ), + __( 'Choose the public planner color mode before a visitor makes their own choice. System follows the visitor browser or OS preference.', 'waypoints' ), 'select', [ 'choices' => Settings::color_mode_choices(), @@ -122,15 +122,15 @@ public function register(): void { add_settings_section( 'plan_your_day_planner_behavior', - __( 'Planner Behavior', 'plan-your-day' ), + __( 'Planner Behavior', 'waypoints' ), [ $this, 'render_planner_behavior_section' ], Settings::PAGE_SLUG ); $this->add_field( 'allowed_start_modes', - __( 'Allowed start modes', 'plan-your-day' ), - __( 'Choose which starting-point controls the planner may offer.', 'plan-your-day' ), + __( 'Allowed start modes', 'waypoints' ), + __( 'Choose which starting-point controls the planner may offer.', 'waypoints' ), 'checkbox_group', [ 'choices' => Settings::start_mode_choices(), @@ -140,8 +140,8 @@ public function register(): void { $this->add_field( 'max_waypoints', - __( 'Max waypoints', 'plan-your-day' ), - __( 'Maximum selected Google places a public request may resolve.', 'plan-your-day' ), + __( 'Max waypoints', 'waypoints' ), + __( 'Maximum selected Google places a public request may resolve.', 'waypoints' ), 'number', [ 'min' => 1, @@ -152,8 +152,8 @@ public function register(): void { $this->add_field( 'result_count', - __( 'Result count', 'plan-your-day' ), - __( 'Maximum Google text-search results requested per browse action.', 'plan-your-day' ), + __( 'Result count', 'waypoints' ), + __( 'Maximum Google text-search results requested per browse action.', 'waypoints' ), 'number', [ 'min' => 1, @@ -164,8 +164,8 @@ public function register(): void { $this->add_field( 'distance_unit', - __( 'Distance unit', 'plan-your-day' ), - __( 'Unit for approximate distance hints.', 'plan-your-day' ), + __( 'Distance unit', 'waypoints' ), + __( 'Unit for approximate distance hints.', 'waypoints' ), 'select', [ 'choices' => Settings::distance_unit_choices(), @@ -175,8 +175,8 @@ public function register(): void { $this->add_field( 'map_preview_enabled', - __( 'Map preview', 'plan-your-day' ), - __( 'Allow on-page Google Maps preview rendering in the public planner.', 'plan-your-day' ), + __( 'Map preview', 'waypoints' ), + __( 'Allow on-page Google Maps preview rendering in the public planner.', 'waypoints' ), 'checkbox', [], 'plan_your_day_planner_behavior' @@ -184,8 +184,8 @@ public function register(): void { $this->add_field( 'maps_handoff_enabled', - __( 'Google Maps handoff', 'plan-your-day' ), - __( 'Allow outbound Google Maps links from the public planner.', 'plan-your-day' ), + __( 'Google Maps handoff', 'waypoints' ), + __( 'Allow outbound Google Maps links from the public planner.', 'waypoints' ), 'checkbox', [], 'plan_your_day_planner_behavior' @@ -193,7 +193,7 @@ public function register(): void { add_settings_section( 'plan_your_day_categories', - __( 'Categories', 'plan-your-day' ), + __( 'Categories', 'waypoints' ), [ $this, 'render_categories_section' ], Settings::PAGE_SLUG ); @@ -211,36 +211,36 @@ public function register(): void { add_settings_section( 'plan_your_day_google_api', - __( 'Google API', 'plan-your-day' ), + __( 'Google API', 'waypoints' ), [ $this, 'render_google_api_section' ], Settings::PAGE_SLUG ); $this->add_field( 'google_maps_embed_api_key', - __( 'Maps Embed API key', 'plan-your-day' ), - __( 'Browser-facing key for Google Maps Embed previews. This key can appear in frontend iframe URLs.', 'plan-your-day' ), + __( 'Maps Embed API key', 'waypoints' ), + __( 'Browser-facing key for Google Maps Embed previews. This key can appear in frontend iframe URLs.', 'waypoints' ), 'password' ); $this->add_field( 'google_places_api_key', - __( 'Places API key', 'plan-your-day' ), - __( 'Server-side key for Places API (New) text search and place details. This key is never sent to browser config.', 'plan-your-day' ), + __( 'Places API key', 'waypoints' ), + __( 'Server-side key for Places API (New) text search and place details. This key is never sent to browser config.', 'waypoints' ), 'password' ); $this->add_field( 'google_geocoding_api_key', - __( 'Geocoding API key', 'plan-your-day' ), - __( 'Optional server-side key for Geocoding API. Leave empty to use the Places API key for geocoding.', 'plan-your-day' ), + __( 'Geocoding API key', 'waypoints' ), + __( 'Optional server-side key for Geocoding API. Leave empty to use the Places API key for geocoding.', 'waypoints' ), 'password' ); $this->add_field( 'google_api_timeout', - __( 'API timeout', 'plan-your-day' ), - __( 'Request timeout in seconds. Saved values are clamped between 1 and 30 seconds.', 'plan-your-day' ), + __( 'API timeout', 'waypoints' ), + __( 'Request timeout in seconds. Saved values are clamped between 1 and 30 seconds.', 'waypoints' ), 'number', [ 'min' => 1, @@ -250,15 +250,15 @@ public function register(): void { add_settings_section( 'plan_your_day_google_cache', - __( 'Google API Cache', 'plan-your-day' ), + __( 'Google API Cache', 'waypoints' ), [ $this, 'render_google_cache_section' ], Settings::PAGE_SLUG ); $this->add_field( 'google_text_search_cache_ttl', - __( 'Text search cache TTL', 'plan-your-day' ), - __( 'Seconds to cache successful Google text search responses. Use 0 to disable this cache.', 'plan-your-day' ), + __( 'Text search cache TTL', 'waypoints' ), + __( 'Seconds to cache successful Google text search responses. Use 0 to disable this cache.', 'waypoints' ), 'number', [ 'min' => 0, @@ -269,8 +269,8 @@ public function register(): void { $this->add_field( 'google_place_details_cache_ttl', - __( 'Place details cache TTL', 'plan-your-day' ), - __( 'Seconds to cache successful Google place details responses. Use 0 to disable this cache.', 'plan-your-day' ), + __( 'Place details cache TTL', 'waypoints' ), + __( 'Seconds to cache successful Google place details responses. Use 0 to disable this cache.', 'waypoints' ), 'number', [ 'min' => 0, @@ -281,8 +281,8 @@ public function register(): void { $this->add_field( 'google_geocoding_cache_ttl', - __( 'Geocoding cache TTL', 'plan-your-day' ), - __( 'Seconds to cache successful Google geocoding responses. Use 0 to disable this cache.', 'plan-your-day' ), + __( 'Geocoding cache TTL', 'waypoints' ), + __( 'Seconds to cache successful Google geocoding responses. Use 0 to disable this cache.', 'waypoints' ), 'number', [ 'min' => 0, @@ -293,22 +293,22 @@ public function register(): void { add_settings_section( 'plan_your_day_interface_copy', - __( 'Interface Copy', 'plan-your-day' ), + __( 'Interface Copy', 'waypoints' ), [ $this, 'render_interface_copy_section' ], Settings::PAGE_SLUG ); add_settings_section( 'plan_your_day_rate_limiting', - __( 'Rate Limiting', 'plan-your-day' ), + __( 'Rate Limiting', 'waypoints' ), [ $this, 'render_rate_limiting_section' ], Settings::PAGE_SLUG ); $this->add_field( 'rate_limit_per_minute', - __( 'Requests per minute', 'plan-your-day' ), - __( 'Per-minute budget for the active public planner REST rate limiter. Requests are enforced by client IP and planner scope.', 'plan-your-day' ), + __( 'Requests per minute', 'waypoints' ), + __( 'Per-minute budget for the active public planner REST rate limiter. Requests are enforced by client IP and planner scope.', 'waypoints' ), 'number', [ 'min' => 1, @@ -317,17 +317,26 @@ public function register(): void { 'plan_your_day_rate_limiting' ); + $this->add_field( + 'debug_api_counter_enabled', + __( 'API call counter', 'waypoints' ), + __( 'Show a fixed frontend API call counter only to admins for troubleshooting request behavior.', 'waypoints' ), + 'checkbox', + [], + 'plan_your_day_rate_limiting' + ); + add_settings_section( 'plan_your_day_advanced', - __( 'Advanced', 'plan-your-day' ), + __( 'Advanced', 'waypoints' ), [ $this, 'render_advanced_section' ], Settings::PAGE_SLUG ); $this->add_field( 'trusted_proxy_cidrs', - __( 'Trusted proxy CIDRs', 'plan-your-day' ), - __( 'Optional. One IP or CIDR per line. Invalid entries are discarded on save.', 'plan-your-day' ), + __( 'Trusted proxy CIDRs', 'waypoints' ), + __( 'Optional. One IP or CIDR per line. Invalid entries are discarded on save.', 'waypoints' ), 'textarea', [ 'rows' => 5, @@ -338,7 +347,7 @@ public function register(): void { public function render(): void { if ( ! current_user_can( 'manage_options' ) ) { - wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'plan-your-day' ) ); + wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'waypoints' ) ); } ?> @@ -355,50 +364,50 @@ public function render(): void { ?>
    -

    -

    +

    +

    - +
    -

    -

    +

    +

    - + - +
    - + - +

    -

    -

    +

    +

    - +
    render_google_test_results_panel(); ?> build_setup_status_checks(); - $ready_check_count = 0; - $warning_check_count = 0; + $checks = $this->build_setup_status_checks(); + $ready_check_count = 0; + $warning_check_count = 0; $optional_check_count = 0; foreach ( $checks as $check ) { @@ -444,13 +453,13 @@ private function render_setup_status_panel(): void { } } ?> -

    +

    - - - + + + @@ -491,54 +500,54 @@ private function build_setup_status_checks(): array { return [ [ - 'label' => __( 'Required location settings', 'plan-your-day' ), - 'status' => [] === $missing_required ? __( 'Ready', 'plan-your-day' ) : __( 'Needs setup', 'plan-your-day' ), + 'label' => __( 'Required location settings', 'waypoints' ), + 'status' => [] === $missing_required ? __( 'Ready', 'waypoints' ) : __( 'Needs setup', 'waypoints' ), 'detail' => [] === $missing_required - ? __( 'The planner has the required default location label and address.', 'plan-your-day' ) + ? __( 'The planner has the required default location label and address.', 'waypoints' ) : sprintf( /* translators: %s is a comma-separated list of missing settings. */ - __( 'Missing: %s.', 'plan-your-day' ), + __( 'Missing: %s.', 'waypoints' ), implode( ', ', $missing_required ) ), 'type' => [] === $missing_required ? 'success' : 'warning', ], [ - 'label' => __( 'Places API key', 'plan-your-day' ), - 'status' => '' !== $places_key ? __( 'Ready', 'plan-your-day' ) : __( 'Missing', 'plan-your-day' ), + 'label' => __( 'Places API key', 'waypoints' ), + 'status' => '' !== $places_key ? __( 'Ready', 'waypoints' ) : __( 'Missing', 'waypoints' ), 'detail' => '' !== $places_key - ? __( 'Server-side Places requests can run.', 'plan-your-day' ) - : __( 'Add a Places API key before trying browse or place-detail requests.', 'plan-your-day' ), + ? __( 'Server-side Places requests can run.', 'waypoints' ) + : __( 'Add a Places API key before trying browse or place-detail requests.', 'waypoints' ), 'type' => '' !== $places_key ? 'success' : 'warning', ], [ - 'label' => __( 'Geocoding configuration', 'plan-your-day' ), - 'status' => '' !== $geocoding_key ? __( 'Ready', 'plan-your-day' ) : __( 'Missing', 'plan-your-day' ), + 'label' => __( 'Geocoding configuration', 'waypoints' ), + 'status' => '' !== $geocoding_key ? __( 'Ready', 'waypoints' ) : __( 'Missing', 'waypoints' ), 'detail' => '' !== $geocoding_key ? ( $geocoding_key === $places_key - ? __( 'Geocoding will use the Places API key fallback.', 'plan-your-day' ) - : __( 'A dedicated Geocoding API key is configured.', 'plan-your-day' ) ) - : __( 'Add a Geocoding API key or a Places API key fallback before testing location resolution.', 'plan-your-day' ), + ? __( 'Geocoding will use the Places API key fallback.', 'waypoints' ) + : __( 'A dedicated Geocoding API key is configured.', 'waypoints' ) ) + : __( 'Add a Geocoding API key or a Places API key fallback before testing location resolution.', 'waypoints' ), 'type' => '' !== $geocoding_key ? 'success' : 'warning', ], [ - 'label' => __( 'Maps Embed preview key', 'plan-your-day' ), - 'status' => '' !== $embed_key ? __( 'Ready', 'plan-your-day' ) : __( 'Optional', 'plan-your-day' ), + 'label' => __( 'Maps Embed preview key', 'waypoints' ), + 'status' => '' !== $embed_key ? __( 'Ready', 'waypoints' ) : __( 'Optional', 'waypoints' ), 'detail' => '' !== $embed_key - ? __( 'On-page Google Maps embeds can render when preview mode is enabled.', 'plan-your-day' ) - : __( 'Missing this key only affects on-page embed previews; Google Maps handoff links can still work.', 'plan-your-day' ), + ? __( 'On-page Google Maps embeds can render when preview mode is enabled.', 'waypoints' ) + : __( 'Missing this key only affects on-page embed previews; Google Maps handoff links can still work.', 'waypoints' ), 'type' => '' !== $embed_key ? 'success' : 'optional', ], [ - 'label' => __( 'Planner categories', 'plan-your-day' ), - 'status' => [] !== $active_categories ? __( 'Ready', 'plan-your-day' ) : __( 'Needs setup', 'plan-your-day' ), + 'label' => __( 'Planner categories', 'waypoints' ), + 'status' => [] !== $active_categories ? __( 'Ready', 'waypoints' ) : __( 'Needs setup', 'waypoints' ), 'detail' => [] !== $active_categories ? sprintf( /* translators: 1: active category count, 2: saved category count. */ - __( '%1$d active categories are available. %2$d categories are saved in settings.', 'plan-your-day' ), + __( '%1$d active categories are available. %2$d categories are saved in settings.', 'waypoints' ), count( $active_categories ), count( $saved_categories ) ) - : __( 'No active categories are available. Add at least one category, or leave the planner to custom search only.', 'plan-your-day' ), + : __( 'No active categories are available. Add at least one category, or leave the planner to custom search only.', 'waypoints' ), 'type' => [] !== $active_categories ? 'success' : 'warning', ], ]; @@ -547,35 +556,35 @@ private function build_setup_status_checks(): array { public function render_google_api_section(): void { printf( '

    %s

    ', - esc_html__( 'Configure only the Google keys and request behavior needed by the backend Google client.', 'plan-your-day' ) + esc_html__( 'Configure only the Google keys and request behavior needed by the backend Google client.', 'waypoints' ) ); } public function render_default_location_section(): void { printf( '

    %s

    ', - esc_html__( 'Set the required generic starting area. Site-specific destination values belong here, not in plugin defaults.', 'plan-your-day' ) + esc_html__( 'Set the required generic starting area. Site-specific destination values belong here, not in plugin defaults.', 'waypoints' ) ); } public function render_planner_behavior_section(): void { printf( '

    %s

    ', - esc_html__( 'Register conservative behavior limits for later renderer and REST endpoint work.', 'plan-your-day' ) + esc_html__( 'Register conservative behavior limits for later renderer and REST endpoint work.', 'waypoints' ) ); } public function render_appearance_section(): void { printf( '

    %s

    ', - esc_html__( 'Set the public planner default color mode. Visitors can switch modes on the planner without changing the saved plugin setting.', 'plan-your-day' ) + esc_html__( 'Set the public planner default color mode. Visitors can switch modes on the planner without changing the saved plugin setting.', 'waypoints' ) ); } public function render_interface_copy_section(): void { printf( '

    %s

    ', - esc_html__( 'Edit the public planner copy here. Button labels and accessible labels fall back to defaults if saved blank. Optional helper text fields can be saved blank to hide them. Dynamic tokens such as {count}, {search}, {place}, and {start} are replaced automatically.', 'plan-your-day' ) + esc_html__( 'Edit the public planner copy here. Button labels and accessible labels fall back to defaults if saved blank. Optional helper text fields can be saved blank to hide them. Dynamic tokens such as {count}, {search}, {place}, and {start} are replaced automatically.', 'waypoints' ) ); $this->render_interface_copy_accordion(); @@ -584,28 +593,28 @@ public function render_interface_copy_section(): void { public function render_categories_section(): void { printf( '

    %s

    ', - esc_html__( 'Edit the category buttons shown on the public planner. You can add, disable, delete, and reorder rows here, or leave the list empty to use custom search only.', 'plan-your-day' ) + esc_html__( 'Edit the category buttons shown on the public planner. You can add, disable, delete, and reorder rows here, or leave the list empty to use custom search only.', 'waypoints' ) ); } public function render_google_cache_section(): void { printf( '

    %s

    ', - esc_html__( 'Configure transient-based caching for successful Google API responses.', 'plan-your-day' ) + esc_html__( 'Configure transient-based caching for successful Google API responses.', 'waypoints' ) ); } public function render_rate_limiting_section(): void { printf( '

    %s

    ', - esc_html__( 'Control the active runtime limiter for public planner REST requests. Higher-cost requests can consume more of this budget before external Google work starts.', 'plan-your-day' ) + esc_html__( 'Control the active runtime limiter for public planner REST requests. Higher-cost requests can consume more of this budget before external Google work starts.', 'waypoints' ) ); } public function render_advanced_section(): void { printf( '

    %s

    ', - esc_html__( 'Advanced networking settings used by later security and rate-limit code.', 'plan-your-day' ) + esc_html__( 'Advanced networking settings used by later security and rate-limit code.', 'waypoints' ) ); } @@ -622,23 +631,23 @@ public function render_missing_required_settings_notice(): void { esc_html( sprintf( /* translators: %s is a comma-separated list of missing settings. */ - __( 'Waypoints needs required settings before the public planner can render: %s.', 'plan-your-day' ), + __( 'Waypoints needs required settings before the public planner can render: %s.', 'waypoints' ), $missing ) ), esc_url( $url ), - esc_html__( 'Open settings', 'plan-your-day' ) + esc_html__( 'Open settings', 'waypoints' ) ); } public function handle_clear_google_cache(): void { if ( ! current_user_can( 'manage_options' ) ) { - wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'plan-your-day' ) ); + wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'waypoints' ) ); } check_admin_referer( 'plan_your_day_clear_google_cache' ); - $cleared = $this->google_api_cache->clear(); + $cleared = $this->google_api_cache->clear(); $redirect_url = add_query_arg( [ 'page' => Settings::PAGE_SLUG, @@ -653,7 +662,7 @@ public function handle_clear_google_cache(): void { public function handle_clear_google_cache_scope(): void { if ( ! current_user_can( 'manage_options' ) ) { - wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'plan-your-day' ) ); + wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'waypoints' ) ); } check_admin_referer( 'plan_your_day_clear_google_cache_scope' ); @@ -675,7 +684,7 @@ public function handle_clear_google_cache_scope(): void { public function handle_clear_google_cache_place(): void { if ( ! current_user_can( 'manage_options' ) ) { - wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'plan-your-day' ) ); + wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'waypoints' ) ); } check_admin_referer( 'plan_your_day_clear_google_cache_place' ); @@ -697,12 +706,12 @@ public function handle_clear_google_cache_place(): void { public function handle_test_google_api(): void { if ( ! current_user_can( 'manage_options' ) ) { - wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'plan-your-day' ) ); + wp_die( esc_html__( 'You do not have permission to manage Waypoints settings.', 'waypoints' ) ); } check_admin_referer( 'plan_your_day_test_google_api' ); - $results = $this->run_google_api_test(); + $results = $this->run_google_api_test(); $transient_key = $this->google_test_transient_key(); if ( '' !== $transient_key ) { @@ -722,12 +731,12 @@ public function handle_test_google_api(): void { } private function run_google_api_test(): array { - $default_address = $this->settings->get_default_location_address(); - $categories = $this->category_catalog->get_all(); - $first_category = is_array( reset( $categories ) ) ? reset( $categories ) : []; - $probe_query = $this->build_google_test_query( $default_address, $first_category ); - $geocode_result = $this->google_api_client->geocode( $default_address ); - $origin_latitude = $this->settings->get_default_location_latitude(); + $default_address = $this->settings->get_default_location_address(); + $categories = $this->category_catalog->get_all(); + $first_category = is_array( reset( $categories ) ) ? reset( $categories ) : []; + $probe_query = $this->build_google_test_query( $default_address, $first_category ); + $geocode_result = $this->google_api_client->geocode( $default_address ); + $origin_latitude = $this->settings->get_default_location_latitude(); $origin_longitude = $this->settings->get_default_location_longitude(); if ( $geocode_result->is_success() ) { @@ -735,53 +744,53 @@ private function run_google_api_test(): array { $origin_longitude = isset( $geocode_result->data()['longitude'] ) ? (float) $geocode_result->data()['longitude'] : $origin_longitude; } - $text_search_result = $this->google_api_client->text_search( $probe_query, $origin_latitude, $origin_longitude ); - $places = $text_search_result->is_success() ? (array) ( $text_search_result->data()['places'] ?? [] ) : []; - $first_place = is_array( $places[0] ?? null ) ? $places[0] : []; - $place_id = is_scalar( $first_place['id'] ?? null ) ? (string) $first_place['id'] : ''; + $text_search_result = $this->google_api_client->text_search( $probe_query, $origin_latitude, $origin_longitude ); + $places = $text_search_result->is_success() ? (array) ( $text_search_result->data()['places'] ?? [] ) : []; + $first_place = is_array( $places[0] ?? null ) ? $places[0] : []; + $place_id = is_scalar( $first_place['id'] ?? null ) ? (string) $first_place['id'] : ''; $place_details_result = '' !== $place_id ? $this->google_api_client->place_details( $place_id ) : null; - $checks = [ + $checks = [ [ - 'label' => __( 'Geocoding probe', 'plan-your-day' ), + 'label' => __( 'Geocoding probe', 'waypoints' ), 'success' => $geocode_result->is_success(), 'detail' => $geocode_result->is_success() ? sprintf( /* translators: 1: latitude, 2: longitude. */ - __( 'Resolved the default location to %1$s, %2$s.', 'plan-your-day' ), + __( 'Resolved the default location to %1$s, %2$s.', 'waypoints' ), (string) $origin_latitude, (string) $origin_longitude ) : $geocode_result->message(), ], [ - 'label' => __( 'Text search probe', 'plan-your-day' ), + 'label' => __( 'Text search probe', 'waypoints' ), 'success' => $text_search_result->is_success(), 'detail' => $text_search_result->is_success() ? sprintf( /* translators: 1: query text, 2: result count. */ - __( 'Query "%1$s" returned %2$d place results.', 'plan-your-day' ), + __( 'Query "%1$s" returned %2$d place results.', 'waypoints' ), $probe_query, count( $places ) ) : $text_search_result->message(), ], [ - 'label' => __( 'Place details probe', 'plan-your-day' ), + 'label' => __( 'Place details probe', 'waypoints' ), 'success' => $place_details_result instanceof \Acodebeard\PlanYourDay\Google\GoogleApiResult && $place_details_result->is_success(), 'detail' => null === $place_details_result - ? __( 'Skipped because the text search probe did not return a place ID to inspect.', 'plan-your-day' ) + ? __( 'Skipped because the text search probe did not return a place ID to inspect.', 'waypoints' ) : ( $place_details_result->is_success() ? sprintf( /* translators: %s is a place label. */ - __( 'Loaded details for %s.', 'plan-your-day' ), + __( 'Loaded details for %s.', 'waypoints' ), (string) ( $place_details_result->data()['place']['label'] ?? $place_id ) ) : $place_details_result->message() ), ], ]; - $success_count = 0; + $success_count = 0; foreach ( $checks as $check ) { if ( ! empty( $check['success'] ) ) { @@ -790,11 +799,11 @@ private function run_google_api_test(): array { } return [ - 'checked_at' => current_time( 'mysql' ), - 'probe_query' => $probe_query, - 'success_count' => $success_count, - 'total_count' => count( $checks ), - 'checks' => $checks, + 'checked_at' => current_time( 'mysql' ), + 'probe_query' => $probe_query, + 'success_count' => $success_count, + 'total_count' => count( $checks ), + 'checks' => $checks, ]; } @@ -832,7 +841,7 @@ private function render_google_test_notice(): void { esc_html( sprintf( /* translators: 1: passed checks, 2: total checks. */ - __( 'Google API test completed: %1$d of %2$d probes passed.', 'plan-your-day' ), + __( 'Google API test completed: %1$d of %2$d probes passed.', 'waypoints' ), (int) ( $results['success_count'] ?? 0 ), (int) ( $results['total_count'] ?? 0 ) ) @@ -847,13 +856,13 @@ private function render_google_test_results_panel(): void { return; } ?> -

    +

    - - - + + + @@ -873,7 +882,7 @@ private function render_google_test_results_panel(): void { - + @@ -967,7 +976,7 @@ public function render_field( array $args ): void { esc_attr( $name ), esc_attr( $id ), checked( true, (bool) $value, false ), - esc_html__( 'Enabled', 'plan-your-day' ) + esc_html__( 'Enabled', 'waypoints' ) ); } elseif ( 'checkbox_group' === $type ) { $this->render_checkbox_group( $name, $id, is_array( $value ) ? $value : [], $attributes ); @@ -1082,14 +1091,14 @@ private function is_settings_screen( string $hook_suffix ): bool { } private function render_categories_editor( string $name ): void { - $categories = $this->settings->get_categories(); - $row_index = 0; - $next_sort = ( count( $categories ) + 1 ) * 10; + $categories = $this->settings->get_categories(); + $row_index = 0; + $next_sort = ( count( $categories ) + 1 ) * 10; ?>

    + data-plan-delete-category-confirm=""> @@ -1101,12 +1110,12 @@ class="plan-your-day-categories-editor" - - - - - - + + + + + + @@ -1116,7 +1125,7 @@ class="plan-your-day-categories-editor"

    - +