├── assets
├── css
│ ├── autosuggest.css
│ ├── comments.css
│ ├── dashboard.css
│ ├── facets-admin.css
│ ├── facets.css
│ ├── highlighting.css
│ ├── instant-results.css
│ ├── instant-results
│ │ ├── checkbox.css
│ │ ├── input.css
│ │ ├── modal.css
│ │ ├── options-list.css
│ │ ├── page.css
│ │ ├── pagination.css
│ │ ├── panel.css
│ │ ├── range-slider.css
│ │ ├── result.css
│ │ ├── results.css
│ │ ├── sidebar-toggle.css
│ │ ├── sidebar.css
│ │ ├── sort.css
│ │ ├── tokens.css
│ │ ├── toolbar.css
│ │ └── utilities.css
│ ├── ordering.css
│ ├── related-posts-block.css
│ ├── sync.css
│ ├── sync
│ │ ├── button.css
│ │ ├── controls.css
│ │ ├── heading.css
│ │ ├── messages.css
│ │ ├── panel.css
│ │ ├── progress-bar.css
│ │ ├── progress.css
│ │ ├── status.css
│ │ └── warning.css
│ └── synonyms.css
└── js
│ ├── autosuggest.js
│ ├── blocks
│ ├── facets
│ │ ├── block.json
│ │ ├── edit.js
│ │ └── index.js
│ └── related-posts
│ │ ├── Edit.js
│ │ └── block.js
│ ├── comments.js
│ ├── dashboard.js
│ ├── facets.js
│ ├── instant-results
│ ├── admin
│ │ ├── components
│ │ │ └── facet-selector.js
│ │ ├── config.js
│ │ └── index.js
│ ├── components
│ │ ├── common
│ │ │ ├── checkbox-list.js
│ │ │ ├── checkbox.js
│ │ │ ├── image.js
│ │ │ ├── modal.js
│ │ │ ├── panel.js
│ │ │ ├── range-slider.js
│ │ │ ├── small-button.js
│ │ │ └── star-rating.js
│ │ ├── facets
│ │ │ ├── facet.js
│ │ │ ├── post-type-facet.js
│ │ │ ├── price-range-facet.js
│ │ │ ├── search-term-facet.js
│ │ │ └── taxonomy-terms-facet.js
│ │ ├── layout.js
│ │ ├── layout
│ │ │ ├── results.js
│ │ │ ├── sidebar.js
│ │ │ └── toolbar.js
│ │ ├── results
│ │ │ ├── pagination.js
│ │ │ └── result.js
│ │ └── tools
│ │ │ ├── active-constraints.js
│ │ │ ├── clear-constraints.js
│ │ │ ├── sidebar-toggle.js
│ │ │ └── sort.js
│ ├── config.js
│ ├── context.js
│ ├── functions.js
│ ├── hooks.js
│ ├── index.js
│ ├── reducer.js
│ └── utilities.js
│ ├── notice.js
│ ├── ordering
│ ├── index.js
│ └── pointers.js
│ ├── settings.js
│ ├── sites-admin.js
│ ├── stats.js
│ ├── sync
│ ├── components
│ │ ├── common
│ │ │ ├── date-time.js
│ │ │ ├── message-log.js
│ │ │ └── progress-bar.js
│ │ ├── icons
│ │ │ ├── pause.js
│ │ │ ├── play.js
│ │ │ ├── stop.js
│ │ │ ├── sync.js
│ │ │ ├── thumbs-down.js
│ │ │ └── thumbs-up.js
│ │ ├── sync-page.js
│ │ └── sync
│ │ │ ├── controls.js
│ │ │ ├── log.js
│ │ │ ├── progress.js
│ │ │ └── status.js
│ ├── config.js
│ ├── hooks.js
│ ├── index.js
│ └── utilities.js
│ ├── synonyms
│ ├── components
│ │ ├── SynonymsEditor.js
│ │ ├── editors
│ │ │ ├── AlternativeEditor.js
│ │ │ ├── AlternativesEditor.js
│ │ │ ├── SetsEditor.js
│ │ │ └── SolrEditor.js
│ │ └── shared
│ │ │ └── LinkedMultiInput.js
│ ├── context.js
│ ├── index.js
│ ├── reducers
│ │ └── editorReducer.js
│ └── utils.js
│ ├── utils
│ └── helpers.js
│ └── weighting.js
├── dist
├── css
│ ├── autosuggest-styles.min.asset.php
│ ├── autosuggest-styles.min.css
│ ├── comments-styles.min.asset.php
│ ├── comments-styles.min.css
│ ├── dashboard-styles.min.asset.php
│ ├── dashboard-styles.min.css
│ ├── facets-admin-styles.min.asset.php
│ ├── facets-admin-styles.min.css
│ ├── facets-styles.min.asset.php
│ ├── facets-styles.min.css
│ ├── highlighting-styles.min.asset.php
│ ├── highlighting-styles.min.css
│ ├── instant-results-styles.min.asset.php
│ ├── instant-results-styles.min.css
│ ├── ordering-styles.min.asset.php
│ ├── ordering-styles.min.css
│ ├── related-posts-block-styles.min.asset.php
│ ├── related-posts-block-styles.min.css
│ ├── sync-styles.min.asset.php
│ ├── sync-styles.min.css
│ ├── synonyms-styles.min.asset.php
│ └── synonyms-styles.min.css
└── js
│ ├── autosuggest-script.min.asset.php
│ ├── autosuggest-script.min.js
│ ├── comments-script.min.asset.php
│ ├── comments-script.min.js
│ ├── dashboard-script.min.asset.php
│ ├── dashboard-script.min.js
│ ├── facets-block-script.min.asset.php
│ ├── facets-block-script.min.js
│ ├── facets-script.min.asset.php
│ ├── facets-script.min.js
│ ├── instant-results-admin-script.min.asset.php
│ ├── instant-results-admin-script.min.js
│ ├── instant-results-script.min.asset.php
│ ├── instant-results-script.min.js
│ ├── notice-script.min.asset.php
│ ├── notice-script.min.js
│ ├── ordering-script.min.asset.php
│ ├── ordering-script.min.js
│ ├── related-posts-block-script.min.asset.php
│ ├── related-posts-block-script.min.js
│ ├── settings-script.min.asset.php
│ ├── settings-script.min.js
│ ├── sites-admin-script.min.asset.php
│ ├── sites-admin-script.min.js
│ ├── stats-script.min.asset.php
│ ├── stats-script.min.js
│ ├── sync-script.min.asset.php
│ ├── sync-script.min.js
│ ├── synonyms-script.min.asset.php
│ ├── synonyms-script.min.js
│ ├── weighting-script.min.asset.php
│ └── weighting-script.min.js
├── elasticpress.php
├── images
├── features-screenshot.png
├── logo-elasticpress-io.svg
├── logo-icon.svg
├── logo.svg
├── setup-screenshot.png
├── sync-in-progress.png
├── vip-logo.svg
└── warning.svg
├── includes
├── classes
│ ├── AdminNotices.php
│ ├── Command.php
│ ├── Elasticsearch.php
│ ├── Feature.php
│ ├── Feature
│ │ ├── Comments
│ │ │ ├── Comments.php
│ │ │ └── Widget.php
│ │ ├── Facets
│ │ │ ├── Block.php
│ │ │ ├── Facets.php
│ │ │ ├── Renderer.php
│ │ │ └── Widget.php
│ │ ├── ProtectedContent
│ │ │ └── ProtectedContent.php
│ │ ├── RelatedPosts
│ │ │ ├── RelatedPosts.php
│ │ │ └── Widget.php
│ │ ├── Search
│ │ │ ├── Search.php
│ │ │ ├── Synonyms.php
│ │ │ └── Weighting.php
│ │ ├── SearchOrdering
│ │ │ └── SearchOrdering.php
│ │ ├── Terms
│ │ │ └── Terms.php
│ │ ├── Users
│ │ │ └── Users.php
│ │ └── WooCommerce
│ │ │ └── WooCommerce.php
│ ├── FeatureRequirementsStatus.php
│ ├── Features.php
│ ├── HealthCheck.php
│ ├── HealthCheck
│ │ └── HealthCheckElasticsearch.php
│ ├── IndexHelper.php
│ ├── Indexable.php
│ ├── Indexable
│ │ ├── Comment
│ │ │ ├── Comment.php
│ │ │ ├── QueryIntegration.php
│ │ │ └── SyncManager.php
│ │ ├── Post
│ │ │ ├── DateQuery.php
│ │ │ ├── Post.php
│ │ │ ├── QueryIntegration.php
│ │ │ └── SyncManager.php
│ │ ├── Term
│ │ │ ├── QueryIntegration.php
│ │ │ ├── SyncManager.php
│ │ │ └── Term.php
│ │ └── User
│ │ │ ├── QueryIntegration.php
│ │ │ ├── SyncManager.php
│ │ │ └── User.php
│ ├── Indexables.php
│ ├── Installer.php
│ ├── Screen.php
│ ├── Screen
│ │ └── Sync.php
│ ├── Stats.php
│ ├── SyncManager.php
│ └── Upgrades.php
├── compat.php
├── dashboard.php
├── health-check.php
├── mappings
│ ├── comment
│ │ ├── 7-0.php
│ │ ├── initial.php
│ │ └── pre-5-0.php
│ ├── post
│ │ ├── 5-0.php
│ │ ├── 5-2.php
│ │ ├── 7-0.php
│ │ └── pre-5-0.php
│ ├── term
│ │ ├── 7-0.php
│ │ ├── initial.php
│ │ └── pre-5-0.php
│ └── user
│ │ ├── 7-0.php
│ │ ├── initial.php
│ │ └── pre-5-0.php
├── partials
│ ├── dashboard-page.php
│ ├── header.php
│ ├── install-page.php
│ ├── settings-page.php
│ ├── stats-page.php
│ └── sync-page.php
└── utils.php
├── readme.txt
└── uninstall.php
/assets/css/autosuggest.css:
--------------------------------------------------------------------------------
1 | .ep-autosuggest-container {
2 | position: relative;
3 |
4 | & .ep-autosuggest {
5 | background: #fff;
6 | border: 1px solid #ccc;
7 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
8 | display: none;
9 | position: absolute;
10 |
11 | width: 100%;
12 | z-index: 200;
13 |
14 | & > ul {
15 | list-style: none;
16 | margin: 0 !important;
17 |
18 | & > li {
19 | font-family: sans-serif;
20 |
21 | & > a.autosuggest-link {
22 | color: #000;
23 | cursor: pointer;
24 | display: block;
25 | padding: 2px 10px;
26 |
27 | &:hover,
28 | &:active {
29 | background-color: #eee;
30 | text-decoration: none;
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
37 | & .selected {
38 | background-color: #eee;
39 | text-decoration: none;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/assets/css/comments.css:
--------------------------------------------------------------------------------
1 | .ep-widget-search-comments-results {
2 | list-style-type: none;
3 | margin-left: 0;
4 | }
5 |
6 | .ep-widget-search-comments-result-item,
7 | .ep-widget-search-comments-result-item-not-found {
8 | margin-left: 0;
9 | }
10 |
11 | .ep-widget-search-comments-result-item.selected a {
12 | border: 2px dotted #171923;
13 | }
14 |
--------------------------------------------------------------------------------
/assets/css/facets-admin.css:
--------------------------------------------------------------------------------
1 | .widget-ep-facet label {
2 | margin-right: 5px;
3 | }
4 |
--------------------------------------------------------------------------------
/assets/css/facets.css:
--------------------------------------------------------------------------------
1 | .widget_ep-facet,
2 | .wp-block-elasticpress-facet {
3 |
4 | & input[type="search"] {
5 | margin-bottom: 1rem;
6 | }
7 |
8 | & .searchable .inner {
9 | max-height: 20em;
10 | overflow: scroll;
11 | }
12 |
13 | & .term.hide {
14 | display: none;
15 | }
16 |
17 | & .empty-term {
18 | opacity: 0.5;
19 | position: relative;
20 | }
21 |
22 | & .empty-term::after {
23 | bottom: 0;
24 | content: " ";
25 | display: block;
26 | left: 0;
27 | position: absolute;
28 | right: 0;
29 | top: 0;
30 | width: 100%;
31 | z-index: 2;
32 | }
33 |
34 | & .level-1 {
35 | padding-left: 20px;
36 | }
37 |
38 | & .level-2 {
39 | padding-left: 40px;
40 | }
41 |
42 | & .level-3 {
43 | padding-left: 60px;
44 | }
45 |
46 | & .level-4 {
47 | padding-left: 80px;
48 | }
49 |
50 | & .level-5 {
51 | padding-left: 100px;
52 | }
53 |
54 | & input[disabled] {
55 | cursor: pointer;
56 | opacity: 1;
57 | }
58 |
59 | & .term a {
60 | align-items: center;
61 | display: flex;
62 | position: relative;
63 | }
64 |
65 | & .term a:hover .ep-checkbox {
66 | background-color: #ccc;
67 | }
68 | }
69 |
70 | .ep-checkbox {
71 | align-items: center;
72 | background-color: #eee;
73 | display: flex;
74 | flex-shrink: 0;
75 | height: 1em;
76 | justify-content: center;
77 | margin-right: 0.25em;
78 | width: 1em;
79 | }
80 |
81 | .ep-checkbox::after {
82 | border: solid #fff;
83 | border-width: 0 0.125em 0.125em 0;
84 | content: "";
85 | display: none;
86 | height: 0.5em;
87 | transform: rotate(45deg);
88 | width: 0.25em;
89 | }
90 |
91 | .ep-checkbox.checked {
92 | background-color: #5e5e5e;
93 | }
94 |
95 | .ep-checkbox.checked::after {
96 | display: block;
97 | }
98 |
--------------------------------------------------------------------------------
/assets/css/highlighting.css:
--------------------------------------------------------------------------------
1 | .ep-highlight {
2 | background-color: transparent;
3 | font-style: italic;
4 | font-weight: 700;
5 | }
6 |
--------------------------------------------------------------------------------
/assets/css/instant-results.css:
--------------------------------------------------------------------------------
1 | @import "instant-results/utilities.css";
2 | @import "instant-results/checkbox.css";
3 | @import "instant-results/input.css";
4 | @import "instant-results/modal.css";
5 | @import "instant-results/options-list.css";
6 | @import "instant-results/page.css";
7 | @import "instant-results/panel.css";
8 | @import "instant-results/pagination.css";
9 | @import "instant-results/range-slider.css";
10 | @import "instant-results/result.css";
11 | @import "instant-results/results.css";
12 | @import "instant-results/sidebar.css";
13 | @import "instant-results/sidebar-toggle.css";
14 | @import "instant-results/sort.css";
15 | @import "instant-results/tokens.css";
16 | @import "instant-results/toolbar.css";
17 |
18 | :root {
19 | --ep-search-background-color: #fff;
20 | --ep-search-alternate-background-color: #efefef;
21 | --ep-search-border-color: #dfdfdf;
22 | --ep-search-range-thumb-size: 1.625em;
23 | --ep-search-range-track-size: 0.75em;
24 |
25 | @media ( min-width: 768px ) {
26 | --ep-search-range-thumb-size: 1.25em;
27 | --ep-search-range-track-size: 0.5em;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/assets/css/instant-results/checkbox.css:
--------------------------------------------------------------------------------
1 | .ep-search-checkbox__count::before {
2 | content: "(";
3 | }
4 |
5 | .ep-search-checkbox__count::after {
6 | content: ")";
7 | }
8 |
--------------------------------------------------------------------------------
/assets/css/instant-results/input.css:
--------------------------------------------------------------------------------
1 | .ep-search-input {
2 | font-size: 1.25em;
3 | margin: 0 !important;
4 | width: 100%;
5 | }
6 |
--------------------------------------------------------------------------------
/assets/css/instant-results/modal.css:
--------------------------------------------------------------------------------
1 | .has-ep-search-modal {
2 | overflow: hidden;
3 | }
4 |
5 | .ep-search-modal {
6 | --ep-search-modal-focus-within: 0;
7 | background-color: rgba(43, 46, 56, 0.9);
8 | bottom: 0;
9 | display: flex;
10 | left: 0;
11 | position: fixed;
12 | right: 0;
13 | top: 0;
14 | z-index: 9999;
15 |
16 | @nest .rtl & {
17 | direction: rtl;
18 | text-align: right;
19 | }
20 |
21 | @nest .admin-bar & {
22 | top: 32px;
23 |
24 | @media ( max-width: 782px ) {
25 | top: 46px;
26 | }
27 | }
28 |
29 | &[aria-hidden="true"] {
30 | display: none;
31 | }
32 |
33 | &:focus-within {
34 | --ep-search-modal-focus-within: 1;
35 | }
36 | }
37 |
38 | .ep-search-modal__content {
39 | background-color: var(--ep-search-background-color);
40 | bottom: 0;
41 | display: flex;
42 | flex-direction: column;
43 | left: 0;
44 | position: absolute;
45 | right: 0;
46 | top: 0;
47 |
48 | @media ( min-width: 768px ) {
49 | bottom: 1em;
50 | margin: 0 auto;
51 | max-width: calc(100% - 2em);
52 | top: 1em;
53 | width: 80em;
54 | }
55 | }
56 |
57 | .ep-search-modal__close {
58 | align-self: flex-end;
59 | padding: 1em !important;
60 | }
61 |
--------------------------------------------------------------------------------
/assets/css/instant-results/options-list.css:
--------------------------------------------------------------------------------
1 | .ep-search-options-list {
2 | list-style: none;
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | .ep-search-options-list__item {
8 | margin: 0.5em 0;
9 |
10 | &::before {
11 | content: none;
12 | }
13 | }
14 |
15 | .ep-search-options-list__sub-menu {
16 | padding-left: 1em;
17 | }
18 |
--------------------------------------------------------------------------------
/assets/css/instant-results/page.css:
--------------------------------------------------------------------------------
1 | .ep-search-page {
2 | display: flex;
3 | flex-direction: column;
4 | flex-grow: 2;
5 | margin: 0;
6 | overflow-y: auto;
7 | transition: opacity 300ms ease-out;
8 | width: 100%;
9 |
10 | @media ( min-width: 768px ) {
11 | overflow: hidden;
12 | }
13 |
14 | & *,
15 | & *::before,
16 | & *::after {
17 | box-sizing: border-box;
18 | }
19 |
20 | &.is-loading {
21 | opacity: 0.5;
22 | }
23 | }
24 |
25 | .ep-search-page__header,
26 | .ep-search-page__tools,
27 | .ep-search-page__body {
28 | padding: 0 1em;
29 | }
30 |
31 | .ep-search-page__body {
32 |
33 | @media ( min-width: 768px ) {
34 | align-items: flex-start;
35 | display: flex;
36 | flex-grow: 2;
37 | overflow: hidden;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/assets/css/instant-results/pagination.css:
--------------------------------------------------------------------------------
1 | .ep-search-pagination {
2 | align-items: center;
3 | display: grid;
4 | grid-template-columns: 1fr 1fr 1fr;
5 | margin-top: auto;
6 | text-align: center;
7 |
8 | @nest .rtl & {
9 | direction: rtl;
10 | }
11 | }
12 |
13 | .ep-search-pagination__next {
14 | justify-self: end;
15 | }
16 |
17 | .ep-search-pagination__previous {
18 | justify-self: start;
19 | }
20 |
--------------------------------------------------------------------------------
/assets/css/instant-results/panel.css:
--------------------------------------------------------------------------------
1 | .ep-search-panel {
2 | border: 1px solid var(--ep-search-border-color);
3 | margin: 0;
4 | padding: 0;
5 |
6 | @nest .ep-search-panel + & {
7 | border-top-width: 0;
8 | }
9 | }
10 |
11 | .ep-search-panel__heading {
12 | font-size: inherit;
13 | margin: 0;
14 | }
15 |
16 | .ep-search-panel__button {
17 | padding: 1em !important;
18 | width: 100% !important;
19 | }
20 |
21 | .ep-search-panel__content {
22 | padding: 0 1em 1em 1em;
23 |
24 | &[aria-hidden="true"] {
25 | display: none;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/assets/css/instant-results/range-slider.css:
--------------------------------------------------------------------------------
1 | .ep-search-range-slider {
2 | align-items: center;
3 | display: flex;
4 | margin: 0.5em 0;
5 | min-height: var(--ep-search-range-thumb-size);
6 | }
7 |
8 | .ep-search-range-slider__track {
9 | background: var(--ep-search-alternate-background-color);
10 | border-radius: calc(var(--ep-search-range-track-size) / 2);
11 | height: var(--ep-search-range-track-size);
12 | }
13 |
14 | .ep-search-range-slider__track-1 {
15 | background-color: currentColor;
16 | }
17 |
18 | .ep-search-range-slider__thumb {
19 | background-color: currentColor;
20 | border-radius: calc(var(--ep-search-range-thumb-size) / 2);
21 | box-shadow:
22 | inset 0 0 0 calc(var(--ep-search-range-thumb-size) / 10) currentColor,
23 | inset 0 0 0 calc((var(--ep-search-range-thumb-size) - var(--ep-search-range-track-size)) / 2) var(--ep-search-background-color);
24 | height: var(--ep-search-range-thumb-size);
25 | width: var(--ep-search-range-thumb-size);
26 | }
27 |
--------------------------------------------------------------------------------
/assets/css/instant-results/result.css:
--------------------------------------------------------------------------------
1 | .ep-search-result {
2 | align-items: flex-start;
3 | display: grid;
4 | grid-gap: 0.5em;
5 | grid-template-areas:
6 | "header"
7 | "footer";
8 | grid-template-rows: auto 1fr;
9 |
10 | @media ( min-width: 768px ) {
11 | grid-gap: 1em;
12 | grid-template-areas:
13 | "header"
14 | "description"
15 | "footer";
16 | grid-template-rows: auto auto 1fr;
17 | }
18 | }
19 |
20 | .ep-search-result--has-thumbnail {
21 | grid-template-areas:
22 | "thumbnail header"
23 | "thumbnail footer";
24 | grid-template-columns: min(300px, 34%) auto;
25 |
26 | @media ( min-width: 768px ) {
27 | grid-template-areas:
28 | "thumbnail header"
29 | "thumbnail description"
30 | "thumbnail footer";
31 | }
32 | }
33 |
34 | .ep-search-result__thumbnail {
35 | display: block;
36 | grid-area: thumbnail;
37 |
38 | & img {
39 | display: block;
40 | margin: 0;
41 | width: 100%;
42 | }
43 | }
44 |
45 | .ep-search-result__header {
46 | display: grid;
47 | grid-area: header;
48 | grid-gap: 0.5em;
49 | grid-template-columns: auto;
50 | justify-items: start;
51 | }
52 |
53 | .ep-search-result__title {
54 | font-size: 1em;
55 | margin: 0;
56 |
57 | @media ( min-width: 768px ) {
58 | font-size: 1.25em;
59 | }
60 | }
61 |
62 | .ep-search-result__type {
63 | background-color: var(--ep-search-alternate-background-color);
64 | border-radius: 0.25em;
65 | display: inline-block;
66 | font-size: 0.875em;
67 | line-height: 1.5;
68 | padding: 0 0.25em;
69 | vertical-align: text-bottom;
70 | }
71 |
72 | .ep-search-result__description {
73 | display: none;
74 | font-size: 0.875em;
75 | grid-area: description;
76 | margin: 0;
77 |
78 | @media ( min-width: 768px ) {
79 | display: block;
80 | font-size: 1em;
81 | }
82 | }
83 |
84 | .ep-search-result__footer {
85 | display: grid;
86 | grid-area: footer;
87 | grid-gap: 0.5em;
88 | justify-items: start;
89 | }
90 |
--------------------------------------------------------------------------------
/assets/css/instant-results/results.css:
--------------------------------------------------------------------------------
1 | .ep-search-results {
2 | display: grid;
3 | grid-gap: 2em;
4 | grid-template-columns: 100%;
5 | grid-template-rows: max-content;
6 | padding: 0 0 1em 0;
7 | width: 100%;
8 |
9 | @media ( min-width: 768px ) {
10 | height: 100%;
11 | overflow-y: auto;
12 | padding: 0 1em 1em 1em;
13 | }
14 | }
15 |
16 | .ep-search-results__header {
17 | align-items: center;
18 | display: flex;
19 | gap: 1em;
20 | justify-content: space-between;
21 | }
22 |
23 | .ep-search-results__title {
24 | font-size: 1.25em;
25 | margin: 0 !important;
26 |
27 | @media ( min-width: 768px ) {
28 | font-size: 1.5em;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/assets/css/instant-results/sidebar-toggle.css:
--------------------------------------------------------------------------------
1 | .ep-search-sidebar-toggle {
2 | width: 100%;
3 |
4 | @media ( min-width: 768px ) {
5 | display: none;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/assets/css/instant-results/sidebar.css:
--------------------------------------------------------------------------------
1 | .ep-search-sidebar {
2 | display: none;
3 | margin-bottom: 2em;
4 |
5 | &.is-open {
6 | display: block;
7 | }
8 |
9 | @media ( min-width: 768px ) {
10 | display: block;
11 | max-height: calc(100% - 1em);
12 | min-width: 25%;
13 | overflow-y: auto;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/assets/css/instant-results/sort.css:
--------------------------------------------------------------------------------
1 | .ep-search-sort {
2 | flex-shrink: 0;
3 | gap: 0.5em;
4 | margin: 0;
5 |
6 | @nest .ep-search-results & {
7 | display: none;
8 |
9 | @media ( min-width: 768px ) {
10 | align-items: center;
11 | display: flex;
12 | }
13 | }
14 |
15 | @nest .ep-search-sidebar & {
16 | display: flex;
17 | flex-direction: column;
18 | margin-bottom: 1em;
19 |
20 | @media ( min-width: 768px ) {
21 | display: none;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/assets/css/instant-results/tokens.css:
--------------------------------------------------------------------------------
1 | .ep-search-tokens {
2 |
3 | @nest .ep-search-toolbar & {
4 | display: contents;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/assets/css/instant-results/toolbar.css:
--------------------------------------------------------------------------------
1 | .ep-search-toolbar {
2 | align-items: start;
3 | display: flex;
4 | flex-wrap: wrap;
5 | gap: 0.25em;
6 | margin: 1em 0;
7 |
8 | @media ( min-width: 768px ) {
9 | align-items: center;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/assets/css/instant-results/utilities.css:
--------------------------------------------------------------------------------
1 | .ep-search-reset-button {
2 | font: inherit !important;
3 | height: auto !important;
4 | letter-spacing: inherit !important;
5 | line-height: 1 !important;
6 | margin: 0 !important;
7 | padding: 0 !important;
8 | text-align: inherit !important;
9 | text-transform: inherit !important;
10 | width: auto !important;
11 |
12 | &,
13 | &:focus,
14 | &:hover {
15 | background: transparent !important;
16 | border: none !important;
17 | box-shadow: none !important;
18 | color: inherit !important;
19 | cursor: default !important;
20 | }
21 |
22 | &:focus {
23 | outline: medium auto Highlight !important;
24 | outline: medium auto -webkit-focus-ring-color !important;
25 | outline-offset: 0 !important;
26 | }
27 | }
28 |
29 | .ep-search-small-button {
30 | font-size: 0.875em !important;
31 | height: auto !important;
32 | line-height: 1 !important;
33 | padding: 0.5em !important;
34 | }
35 |
36 | .ep-search-icon-button {
37 | align-items: center;
38 | display: flex;
39 | justify-content: space-between;
40 |
41 | & svg {
42 | fill: currentColor;
43 | flex-shrink: 0;
44 | height: 1em;
45 | width: 1em;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/assets/css/ordering.css:
--------------------------------------------------------------------------------
1 | #ep-ordering {
2 | border-left-width: 0;
3 | border-right-width: 0;
4 |
5 | & .inside {
6 | background-color: #f1f1f1;
7 | margin: 0;
8 | padding: 0;
9 | }
10 |
11 | & .pointers,
12 | & .pointer-search,
13 | & .loading {
14 | background-color: #fff;
15 | border-left: 1px solid #eee;
16 | border-right: 1px solid #eee;
17 | }
18 |
19 | & .new-post {
20 | background: #fff;
21 | padding: 0 1em;
22 | }
23 |
24 | & .loading {
25 | padding: 1em;
26 |
27 | & .spinner {
28 | float: left;
29 | margin-left: 0;
30 | margin-top: 0;
31 | }
32 | }
33 |
34 | & .pointer-type {
35 | border: 2px solid #0073aa;
36 | border-radius: 2px;
37 | color: #0073aa;
38 | display: inline-block;
39 | font-size: 0.75em;
40 | font-weight: 700;
41 | margin-right: 8px;
42 | padding: 1px 2px;
43 | }
44 |
45 | & .pointers {
46 |
47 | & .pointer,
48 | & .post {
49 | padding: 1em;
50 |
51 | &:nth-child(odd) {
52 | background-color: #f9f9f9;
53 | }
54 | }
55 |
56 | & .title {
57 | color: #0073aa;
58 | }
59 |
60 | & .pointer-actions {
61 | float: right;
62 |
63 | & .handle {
64 | cursor: move;
65 | }
66 |
67 | & .delete-pointer {
68 | margin-left: 10px;
69 | }
70 | }
71 |
72 | & .next-page-notice {
73 | background-color: #fdeeca;
74 | padding: 1em 0;
75 | text-align: center;
76 | }
77 | }
78 |
79 | & .legend {
80 | background: #fff;
81 | border-bottom: 1px solid #eee;
82 | padding: 1em 0;
83 | text-align: center;
84 | }
85 |
86 | & .legend-item {
87 | display: inline-block;
88 | font-size: 0.875em;
89 | font-style: italic;
90 | margin: 0 0.5em;
91 | }
92 |
93 | & .pointer-search {
94 | margin-top: 2em;
95 |
96 | & .no-results {
97 | padding: 1em;
98 | }
99 |
100 | & .section-title {
101 | border-bottom: 1px solid #eee;
102 | border-top: 1px solid #eee;
103 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
104 | font-weight: 700;
105 | }
106 |
107 | & .search-wrapper {
108 | padding: 1em 0;
109 | }
110 |
111 | & .input-wrap {
112 | padding: 0 1em;
113 | }
114 |
115 | & .search-pointers {
116 | font-size: 18px;
117 | height: 1.7em;
118 | line-height: 100%;
119 | padding: 3px 8px;
120 | }
121 |
122 | & .pointer-results {
123 | padding: 1em 0 0;
124 | }
125 |
126 | & .pointer-result {
127 | padding: 10px 2em;
128 |
129 | & .dashicons {
130 | float: right;
131 | }
132 |
133 | &:hover {
134 | background-color: #f9f9f9;
135 | }
136 | }
137 | }
138 |
139 | & .delete-pointer,
140 | & .add-pointer {
141 | cursor: pointer;
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/assets/css/related-posts-block.css:
--------------------------------------------------------------------------------
1 | .editor-styles-wrapper .wp-block-elasticpress-related-posts ul,
2 | .wp-block-elasticpress-related-posts ul {
3 | list-style-type: none;
4 | padding: 0;
5 | }
6 |
7 | .editor-styles-wrapper .wp-block-elasticpress-related-posts ul li a > div {
8 | display: inline;
9 | }
10 |
--------------------------------------------------------------------------------
/assets/css/sync.css:
--------------------------------------------------------------------------------
1 | @import "sync/button.css";
2 | @import "sync/controls.css";
3 | @import "sync/heading.css";
4 | @import "sync/messages.css";
5 | @import "sync/panel.css";
6 | @import "sync/progress.css";
7 | @import "sync/progress-bar.css";
8 | @import "sync/status.css";
9 | @import "sync/warning.css";
10 |
11 | :root {
12 | --ep-sync-color-black: #1a1e24;
13 | --ep-sync-color-error: #b52727;
14 | --ep-sync-color-light-grey: #f0f0f0;
15 | --ep-sync-color-success: #46b450;
16 | --ep-sync-color-warning: #ffb359;
17 | --ep-sync-color-white: #fff;
18 | }
19 |
--------------------------------------------------------------------------------
/assets/css/sync/button.css:
--------------------------------------------------------------------------------
1 | .ep-sync-button {
2 |
3 | &.components-button.has-icon.has-text {
4 | height: 4rem;
5 | justify-content: center;
6 | width: 100%;
7 |
8 | & svg {
9 | height: 2em;
10 | margin: 0;
11 | width: 2em;
12 | }
13 | }
14 | }
15 |
16 | .ep-sync-button--sync {
17 | font-size: 1.5em;
18 | font-weight: 700;
19 | }
20 |
21 | .ep-sync-button--pause,
22 | .ep-sync-button--resume,
23 | .ep-sync-button--stop {
24 | flex-direction: column;
25 | }
26 |
--------------------------------------------------------------------------------
/assets/css/sync/controls.css:
--------------------------------------------------------------------------------
1 | .ep-sync-controls {
2 | display: grid;
3 | grid-gap: 1em;
4 | grid-template-columns: 1fr 1fr;
5 | margin: 0 auto;
6 | max-width: 16rem;
7 | }
8 |
9 | .ep-sync-controls__sync {
10 | grid-column: 1 / -1;
11 | }
12 |
13 | .ep-sync-controls__learn-more {
14 | grid-column: 1 / -1;
15 | text-align: center;
16 | }
17 |
--------------------------------------------------------------------------------
/assets/css/sync/heading.css:
--------------------------------------------------------------------------------
1 | .ep-sync-heading {
2 |
3 | @nest .wrap & {
4 | font-weight: 400;
5 | margin: 0.5rem 0 0.75rem;
6 | padding: 0;
7 |
8 | &h2 {
9 | color: inherit;
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/assets/css/sync/messages.css:
--------------------------------------------------------------------------------
1 | .ep-sync-messages {
2 | align-content: start;
3 | background: var(--ep-sync-color-black);
4 | color: var(--ep-sync-color-white);
5 | display: grid;
6 | font-family: monospace;
7 | grid-auto-flow: column;
8 | grid-template-columns: min-content auto;
9 | height: 21em;
10 | line-height: 2;
11 | overflow-y: auto;
12 | white-space: pre-wrap;
13 | }
14 |
15 | .ep-sync-messages__message {
16 | grid-column: 2;
17 | }
18 |
19 | .ep-sync-messages__line-number {
20 | box-sizing: content-box;
21 | min-width: 3ch;
22 | opacity: 0.5;
23 | padding: 0 0.5em;
24 | text-align: right;
25 | }
26 |
--------------------------------------------------------------------------------
/assets/css/sync/panel.css:
--------------------------------------------------------------------------------
1 | .ep-sync-panel {
2 | margin-bottom: 2rem;
3 | max-width: 1200px;
4 | }
5 |
6 | .ep-sync-panel__body {
7 | display: grid;
8 | grid-column-gap: 2rem;
9 | grid-row-gap: 1rem;
10 | grid-template-columns: auto 16rem;
11 |
12 | &.is-opened {
13 | padding: 2rem 2rem 1rem 2rem;
14 | }
15 |
16 | & p,
17 | & .components-toggle-control,
18 | & .components-tab-panel__tab-content {
19 | margin-bottom: 1rem;
20 | margin-top: 0;
21 | }
22 |
23 | @media (max-width: 960px) {
24 | grid-template-columns: 100%;
25 | }
26 | }
27 |
28 | .ep-sync-panel__row {
29 | grid-column: 1 / -1;
30 | }
31 |
32 | .ep-sync-panel__introduction {
33 | font-size: 18px;
34 | }
35 |
--------------------------------------------------------------------------------
/assets/css/sync/progress-bar.css:
--------------------------------------------------------------------------------
1 | .ep-sync-progress-bar {
2 | background: var(--ep-sync-color-light-grey);
3 | display: flex;
4 | overflow: hidden;
5 | text-align: center;
6 | }
7 |
8 | .ep-sync-progress-bar,
9 | .ep-sync-progress-bar__progress {
10 | border-radius: 0.875em;
11 | }
12 |
13 | .ep-sync-progress-bar__progress {
14 | background: var(--wp-admin-theme-color);
15 | color: var(--ep-sync-color-white);
16 | padding: 0 0.875em;
17 | transition: all 500ms ease-in-out;
18 | white-space: nowrap;
19 |
20 | @nest .ep-sync-progress-bar--complete & {
21 | background: var(--ep-sync-color-success);
22 | }
23 |
24 | @nest .ep-sync-progress-bar--paused & {
25 | opacity: 0.5;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/assets/css/sync/progress.css:
--------------------------------------------------------------------------------
1 | @keyframes epSyncRotation {
2 |
3 | from {
4 | transform: rotate(0deg);
5 | }
6 |
7 | to {
8 | transform: rotate(359deg);
9 | }
10 | }
11 |
12 | .ep-sync-progress {
13 | align-items: center;
14 | display: grid;
15 | grid-row-gap: 1rem;
16 | grid-template-columns: min-content minmax(max-content, 1fr) 3fr;
17 | margin-bottom: 1rem;
18 |
19 | @media (max-width: 960px) {
20 | grid-template-columns: min-content auto;
21 | }
22 |
23 | & svg {
24 | animation: epSyncRotation 1500ms infinite linear;
25 | animation-play-state: paused;
26 | height: 36px;
27 | margin-right: 12px;
28 | width: 36px;
29 | }
30 | }
31 |
32 | .ep-sync-progress--syncing {
33 |
34 | & svg {
35 | animation-play-state: running;
36 | }
37 | }
38 |
39 | .ep-sync-progress__details {
40 |
41 | & strong {
42 | display: block;
43 | font-size: 14px;
44 | }
45 | }
46 |
47 | .ep-sync-progress__progress-bar {
48 |
49 | @media (max-width: 960px) {
50 | grid-column: 1 / -1;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/assets/css/sync/status.css:
--------------------------------------------------------------------------------
1 | .ep-sync-status {
2 | align-items: center;
3 | display: grid;
4 | grid-gap: 0.5rem;
5 | grid-template-columns: min-content auto;
6 |
7 | & svg {
8 | fill: var(--ep-sync-color-error);
9 | }
10 | }
11 |
12 | .ep-sync-status--success {
13 |
14 | & svg {
15 | fill: var(--ep-sync-color-success);
16 | }
17 | }
18 |
19 | .ep-sync-status__time {
20 | background-color: var(--ep-sync-color-light-grey);
21 | border-radius: 2px;
22 | padding: 0.25em 0.5em;
23 | }
24 |
--------------------------------------------------------------------------------
/assets/css/sync/warning.css:
--------------------------------------------------------------------------------
1 | .ep-sync-warning {
2 | display: grid;
3 | grid-gap: 0.5rem;
4 | grid-template-columns: min-content auto;
5 |
6 | & svg {
7 | fill: var(--ep-sync-color-warning);
8 | margin-top: -3px;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/assets/css/synonyms.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --ep-synonyms-input-border-color: hsl(0, 0%, 80%);
3 | --ep-synonyms-color-black: #1a1e24;
4 | --ep-synonyms-color-error: #b52727;
5 | }
6 |
7 | html.wp-toolbar {
8 | background: transparent;
9 | }
10 |
11 | #synonym-root {
12 |
13 | & .page-title-action {
14 | margin-left: 10px;
15 | }
16 |
17 | & .postbox .hndle {
18 | cursor: default;
19 | }
20 |
21 | & h2 {
22 | color: var(--ep-synonyms-color-black);
23 | }
24 | }
25 |
26 | .synonym-editor {
27 |
28 | & .postbox {
29 | width: 100%;
30 |
31 | & > .hndle {
32 | display: flex;
33 | }
34 | }
35 | }
36 |
37 | .synonym-alternative-editor,
38 | .synonym-set-editor {
39 | align-items: flex-start;
40 | display: flex;
41 |
42 | & .components-form-token-field {
43 | flex: 1;
44 | margin-bottom: 0.5em;
45 | }
46 |
47 | & .components-form-token-field__label {
48 | display: none;
49 | }
50 |
51 | & .components-form-token-field__input-container {
52 | border-color: var(--ep-synonyms-input-border-color);
53 | box-sizing: border-box;
54 | }
55 |
56 | & .components-form-token-field__token-text {
57 | padding-bottom: 1px;
58 | padding-top: 1px;
59 |
60 | @media screen and (max-width: 782px) {
61 | padding-bottom: 3px;
62 | padding-top: 3px;
63 | }
64 | }
65 |
66 | & input[type="text"].components-form-token-field__input {
67 | margin-bottom: 2px;
68 | margin-top: 2px;
69 |
70 | @media screen and (max-width: 782px) {
71 | min-height: 30px;
72 | }
73 | }
74 |
75 | & .components-form-token-field__help {
76 | margin-top: 0;
77 | }
78 | }
79 |
80 | input[type="text"].ep-synonyms__input {
81 | border: 1px solid var(--ep-synonyms-input-border-color);
82 | margin-bottom: 0.5em;
83 | margin-right: 1em;
84 | min-height: 36px;
85 | width: 10em;
86 |
87 | @media screen and (max-width: 782px) {
88 | min-height: 40px;
89 | }
90 | }
91 |
92 | .synonym-alternatives__primary-heading {
93 | width: 11em;
94 | }
95 |
96 | .synonym-alternatives__input-heading {
97 | flex: 1;
98 | }
99 |
100 | button.synonym__remove {
101 | background-color: transparent;
102 | border: none;
103 | color: var(--ep-synonyms-color-error);
104 | cursor: pointer;
105 | margin: 0 0 0 10px;
106 | min-height: 36px;
107 | padding: 0;
108 |
109 | @media screen and (max-width: 782px) {
110 | min-height: 40px;
111 | }
112 |
113 | & .dashicons-dismiss {
114 | margin: -2px 2px 0 0;
115 | }
116 | }
117 |
118 | .synonym__validation::before {
119 | content: "";
120 | flex-basis: 100%;
121 | height: 0;
122 | }
123 |
124 | .synonym__validation,
125 | .synonym-solr-editor__validation p {
126 | color: var(--ep-synonyms-color-error);
127 | font-style: italic;
128 | }
129 |
130 | .synonym__validation {
131 | margin: 0 0 0.625em 0.5em;
132 | }
133 |
134 | .synonym-btn-group button.button {
135 | margin-right: 0.625em;
136 | }
137 |
--------------------------------------------------------------------------------
/assets/js/blocks/facets/block.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schemas.wp.org/trunk/block.json",
3 | "apiVersion": 2,
4 | "title": "Facet (ElasticPress)",
5 | "textdomain": "elasticpress",
6 | "name": "elasticpress/facet",
7 | "icon": "feedback",
8 | "category": "widgets",
9 | "attributes": {
10 | "facet": {
11 | "type": "string",
12 | "default": ""
13 | },
14 | "orderby": {
15 | "type" : "string",
16 | "default": "count",
17 | "enum" : [ "count", "name" ]
18 | },
19 | "order": {
20 | "type": "string",
21 | "default": "desc",
22 | "enum": [ "desc", "asc" ]
23 | }
24 | },
25 | "supports": {
26 | "html": false
27 | },
28 | "editorScript": "file:/../../../../dist/js/facets-block-script.min.js",
29 | "style": "file:/../../../../dist/css/facets-styles.min.css"
30 | }
--------------------------------------------------------------------------------
/assets/js/blocks/facets/edit.js:
--------------------------------------------------------------------------------
1 | import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
2 | import {
3 | PanelBody,
4 | RadioControl,
5 | SelectControl,
6 | Spinner,
7 | Placeholder,
8 | } from '@wordpress/components';
9 | import { Fragment, useEffect, useState, useCallback } from '@wordpress/element';
10 | import apiFetch from '@wordpress/api-fetch';
11 | import { __ } from '@wordpress/i18n';
12 |
13 | const FacetBlockEdit = (props) => {
14 | const { attributes, setAttributes } = props;
15 | const [taxonomies, setTaxonomies] = useState({});
16 | const [preview, setPreview] = useState('');
17 | const [loading, setLoading] = useState(false);
18 | const { facet, orderby, order } = attributes;
19 |
20 | const blockProps = useBlockProps();
21 |
22 | const load = useCallback(async () => {
23 | const taxonomies = await apiFetch({
24 | path: '/elasticpress/v1/facets/taxonomies',
25 | });
26 | setTaxonomies(taxonomies);
27 | }, [setTaxonomies]);
28 |
29 | useEffect(load, [load]);
30 |
31 | useEffect(() => {
32 | setLoading(true);
33 | const params = new URLSearchParams({
34 | facet,
35 | orderby,
36 | order,
37 | });
38 | apiFetch({
39 | path: `/elasticpress/v1/facets/block-preview?${params}`,
40 | })
41 | .then((preview) => setPreview(preview))
42 | .finally(() => setLoading(false));
43 | }, [facet, orderby, order]);
44 |
45 | return (
46 |
47 |
48 |
49 | ({
54 | label: taxonomy.label,
55 | value: slug,
56 | })),
57 | ]}
58 | onChange={(value) => setAttributes({ facet: value })}
59 | />
60 | setAttributes({ orderby: value })}
69 | />
70 | setAttributes({ order: value })}
78 | />
79 |
80 |
81 |
82 |
83 | {loading && (
84 |
85 |
86 |
87 | )}
88 | {/* eslint-disable-next-line react/no-danger */}
89 | {!loading &&
}
90 |
91 |
92 | );
93 | };
94 | export default FacetBlockEdit;
95 |
--------------------------------------------------------------------------------
/assets/js/blocks/facets/index.js:
--------------------------------------------------------------------------------
1 | import edit from './edit';
2 | import block from './block.json';
3 |
4 | const { registerBlockType } = wp.blocks;
5 |
6 | registerBlockType(block, {
7 | edit,
8 | save: () => {},
9 | });
10 |
--------------------------------------------------------------------------------
/assets/js/blocks/related-posts/Edit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { AlignmentToolbar, BlockControls, InspectorControls } from '@wordpress/block-editor';
5 | import { PanelBody, Placeholder, Spinner, QueryControls } from '@wordpress/components';
6 | import { Fragment, Component, RawHTML } from '@wordpress/element';
7 | import { __ } from '@wordpress/i18n';
8 | import { addQueryArgs } from '@wordpress/url';
9 |
10 | /**
11 | * Edit component
12 | */
13 | class Edit extends Component {
14 | /**
15 | * Setup class
16 | *
17 | * @param {object} props Component properties
18 | */
19 | constructor(props) {
20 | super(props);
21 |
22 | this.state = {
23 | posts: false,
24 | };
25 | }
26 |
27 | /**
28 | * Load preview data
29 | */
30 | componentDidMount() {
31 | const urlArgs = {
32 | number: 100,
33 | };
34 |
35 | // Use 0 if in the Widgets Screen
36 | const { context: { postId = 0 } = {} } = this.props;
37 |
38 | wp.apiFetch({
39 | path: addQueryArgs(`/wp/v2/posts/${postId}/related`, urlArgs),
40 | })
41 | .then((posts) => {
42 | this.setState({ posts });
43 | })
44 | .catch(() => {
45 | this.setState({ posts: false });
46 | });
47 | }
48 |
49 | render() {
50 | const {
51 | attributes: { alignment, number },
52 | setAttributes,
53 | className,
54 | } = this.props;
55 | const { posts } = this.state;
56 |
57 | const displayPosts = posts.length > number ? posts.slice(0, number) : posts;
58 |
59 | return (
60 |
61 |
62 | setAttributes({ alignment: newValue })}
65 | />
66 |
67 |
68 |
69 | setAttributes({ number: value })}
72 | />
73 |
74 |
75 |
76 |
77 | {displayPosts === false || displayPosts.length === 0 ? (
78 |
79 | {posts === false ? (
80 |
81 | ) : (
82 | __('No related posts yet.', 'elasticpress')
83 | )}
84 |
85 | ) : (
86 |
102 | )}
103 |
104 |
105 | );
106 | }
107 | }
108 |
109 | export default Edit;
110 |
--------------------------------------------------------------------------------
/assets/js/blocks/related-posts/block.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { registerBlockType } from '@wordpress/blocks';
5 | import { __ } from '@wordpress/i18n';
6 |
7 | /**
8 | * Internal dependencies.
9 | */
10 | import Edit from './Edit';
11 |
12 | registerBlockType('elasticpress/related-posts', {
13 | title: __('Related Posts (ElasticPress)', 'elasticpress'),
14 | supports: {
15 | align: true,
16 | },
17 | category: 'widgets',
18 | attributes: {
19 | alignment: {
20 | type: 'string',
21 | default: 'none',
22 | },
23 | number: {
24 | type: 'number',
25 | default: 5,
26 | },
27 | },
28 | usesContext: ['postId'],
29 |
30 | /**
31 | * Handle edit
32 | *
33 | * @param {object} props Component properties
34 | * @returns {object}
35 | */
36 | edit(props) {
37 | return ;
38 | },
39 |
40 | /**
41 | * Handle save
42 | *
43 | * @returns {void}
44 | */
45 | save() {
46 | return null;
47 | },
48 | });
49 |
--------------------------------------------------------------------------------
/assets/js/facets.js:
--------------------------------------------------------------------------------
1 | import { debounce } from './utils/helpers';
2 |
3 | /**
4 | * Filters the facets to match the input search term when
5 | * the number of terms exceeds the threshold determined
6 | * by the ep_facet_search_threshold filter
7 | *
8 | * @param {event} event - keyup
9 | * @param {Node} facetTerms - terms node
10 | */
11 | const handleFacetSearch = (event, facetTerms) => {
12 | const { target } = event;
13 | const searchTerm = target.value.toLowerCase();
14 | const terms = facetTerms.querySelectorAll('.term');
15 |
16 | terms.forEach((term) => {
17 | const slug = term.getAttribute('data-term-slug');
18 | const name = term.getAttribute('data-term-name');
19 |
20 | if (name.includes(searchTerm) || slug.includes(searchTerm)) {
21 | term.classList.remove('hide');
22 | } else {
23 | term.classList.add('hide');
24 | }
25 | });
26 | };
27 |
28 | /**
29 | * Filter facet choices to match the search field term
30 | */
31 | const facets = document.querySelectorAll('.widget_ep-facet, .wp-block-elasticpress-facet');
32 |
33 | facets.forEach((facet) => {
34 | const facetSearchInput = facet.querySelector('.facet-search');
35 |
36 | if (!facetSearchInput) {
37 | return;
38 | }
39 |
40 | const facetTerms = facet.querySelector('.terms');
41 |
42 | facet.querySelector('.facet-search').addEventListener(
43 | 'keyup',
44 | debounce((event) => {
45 | if (event.keyCode === 13) {
46 | return;
47 | }
48 |
49 | handleFacetSearch(event, facetTerms);
50 | }, 200),
51 | );
52 | });
53 |
--------------------------------------------------------------------------------
/assets/js/instant-results/admin/components/facet-selector.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { FormTokenField } from '@wordpress/components';
5 | import { useMemo, useState, WPElement } from '@wordpress/element';
6 | import { __ } from '@wordpress/i18n';
7 |
8 | /**
9 | * Internal dependencies.
10 | */
11 | import { facets } from '../config';
12 |
13 | /**
14 | * Facet selector component.
15 | *
16 | * @param {object} props Props.
17 | * @param {string} props.defaultValue Default value.
18 | * @returns {WPElement} Element.
19 | */
20 | export default ({ defaultValue, ...props }) => {
21 | const defaultValues = defaultValue.split(',');
22 | const [selectedFacets, setSelectedFacets] = useState(defaultValues);
23 |
24 | /**
25 | * Get the label for a facet from the facet key.
26 | *
27 | * @param {string} key Facet key.
28 | * @returns {string} Facet label.
29 | */
30 | const getLabelFromKey = (key) => {
31 | return facets[key]?.label;
32 | };
33 |
34 | /**
35 | * Get the key for a facet from the facet label.
36 | *
37 | * @param {string} label Facet label.
38 | * @returns {string} Facet key.
39 | */
40 | const getKeyFromLabel = (label) => {
41 | return Object.keys(facets).find((key) => {
42 | return label === facets[key].label;
43 | });
44 | };
45 |
46 | /**
47 | * Suggestions for the token field.
48 | */
49 | const suggestions = useMemo(() => Object.keys(facets).map(getLabelFromKey).filter(Boolean), []);
50 |
51 | /**
52 | * Values for the token field.
53 | */
54 | const value = useMemo(
55 | () => selectedFacets.map(getLabelFromKey).filter(Boolean),
56 | [selectedFacets],
57 | );
58 |
59 | /**
60 | * Handle change to token field.
61 | *
62 | * @param {Array} tokens Selected tokens.
63 | */
64 | const onChange = (tokens) => {
65 | setSelectedFacets(tokens.map(getKeyFromLabel).filter(Boolean));
66 | };
67 |
68 | return (
69 | <>
70 |
78 |
79 | >
80 | );
81 | };
82 |
--------------------------------------------------------------------------------
/assets/js/instant-results/admin/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Window dependencies.
3 | */
4 | const { facets } = window.epInstantResultsAdmin;
5 |
6 | export { facets };
7 |
--------------------------------------------------------------------------------
/assets/js/instant-results/admin/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { render } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependences.
8 | */
9 | import FacetSelector from './components/facet-selector';
10 |
11 | document.addEventListener('DOMContentLoaded', () => {
12 | const input = document.getElementById('feature_instant_results_facets');
13 |
14 | const {
15 | className,
16 | dataset: { fieldName },
17 | id,
18 | name,
19 | value,
20 | } = input;
21 |
22 | render(
23 | ,
30 | input.parentElement,
31 | );
32 | });
33 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/common/checkbox.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Checkbox component.
8 | *
9 | * @param {Option} props Component props.
10 | * @param {string} props.count Checkbox count.
11 | * @param {string} props.id Checkbox ID.
12 | * @param {string} props.label Checkbox label.
13 | *
14 | * @returns {WPElement} Component element.
15 | */
16 | export default ({ count, id, label, ...props }) => {
17 | return (
18 |
19 | {' '}
20 |
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/common/image.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Image component.
8 | *
9 | * @param {Option} props Component props.
10 | *
11 | * @returns {WPElement} Component element.
12 | */
13 | export default ({ alt, height, ID, src, width, ...props }) => {
14 | return
;
15 | };
16 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/common/modal.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies.
3 | */
4 | import FocusTrap from 'focus-trap-react';
5 |
6 | /**
7 | * WordPress dependencies.
8 | */
9 | import { forwardRef, useCallback, useEffect, useRef, WPElement } from '@wordpress/element';
10 | import { closeSmall, Icon } from '@wordpress/icons';
11 | import { __ } from '@wordpress/i18n';
12 |
13 | /**
14 | * Modal components.
15 | *
16 | * @param {object} props Component props.
17 | * @param {WPElement} props.children Component children.
18 | * @param {boolean} props.isOpen Whether the modal is open.
19 | * @param {Function} props.onClose Callback to run when modal is closed.
20 | * @param {object} ref Ref.
21 | * @returns {WPElement} React element.
22 | */
23 | const Modal = ({ children, isOpen, onClose, ...props }, ref) => {
24 | /**
25 | * Reference to close button element.
26 | */
27 | const closeRef = useRef(null);
28 |
29 | /**
30 | * Handle key down.
31 | *
32 | * @param {Event} event Keydown event.
33 | */
34 | const onKeyDown = useCallback(
35 | (event) => {
36 | if (event.key === 'Escape' || event.key === 'Esc') {
37 | onClose();
38 | }
39 | },
40 | [onClose],
41 | );
42 |
43 | /**
44 | * Handle binding events to outside DOM elements.
45 | *
46 | * @returns {Function} Clean up function that removes events.
47 | */
48 | const handleEvents = () => {
49 | const { current: modalEl } = ref;
50 |
51 | modalEl.ownerDocument.body.addEventListener('keydown', onKeyDown);
52 |
53 | return () => {
54 | modalEl.ownerDocument.body.removeEventListener('keydown', onKeyDown);
55 | };
56 | };
57 |
58 | /**
59 | * Handle the model being opened or closed.
60 | *
61 | * Adds a class to the body element to allow controlling scrolling.
62 | */
63 | const handleOpen = () => {
64 | const { current: modalEl } = ref;
65 |
66 | if (isOpen) {
67 | modalEl.ownerDocument.body.classList.add('has-ep-search-modal');
68 | closeRef.current.focus();
69 | } else {
70 | modalEl.ownerDocument.body.classList.remove('has-ep-search-modal');
71 | }
72 | };
73 |
74 | useEffect(handleEvents, [onKeyDown, ref]);
75 | useEffect(handleOpen, [isOpen, ref]);
76 |
77 | return (
78 |
86 | {isOpen && (
87 |
88 |
89 |
98 | {children}
99 |
100 |
101 | )}
102 |
103 | );
104 | };
105 |
106 | export default forwardRef(Modal);
107 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/common/panel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { useState, WPElement } from '@wordpress/element';
5 | import { chevronDown, chevronUp, Icon } from '@wordpress/icons';
6 |
7 | /**
8 | * Facet wrapper component.
9 | *
10 | * @param {object} props Component props.
11 | * @param {WPElement} props.children Component children.
12 | * @param {boolean} props.defaultIsOpen Whether the panel is open by default.
13 | * @param {string} props.label Facet label.
14 | * @returns {WPElement} Component element.
15 | */
16 | export default ({ children, defaultIsOpen, label }) => {
17 | const [isOpen, setIsOpen] = useState(defaultIsOpen);
18 |
19 | /**
20 | * Handle click event on the header.
21 | */
22 | const onClick = () => {
23 | setIsOpen(!isOpen);
24 | };
25 |
26 | return (
27 |
28 |
29 |
38 |
39 |
40 | {children(isOpen)}
41 |
42 |
43 | );
44 | };
45 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/common/range-slider.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies.
3 | */
4 | import ReactSlider from 'react-slider';
5 |
6 | /**
7 | * WordPress dependencies.
8 | */
9 | import { WPElement } from '@wordpress/element';
10 |
11 | /**
12 | * Range slider component.
13 | *
14 | * @param {object} props Props.
15 | * @returns {WPElement} Element.
16 | */
17 | export default ({ ...props }) => {
18 | return (
19 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/common/small-button.js:
--------------------------------------------------------------------------------
1 | import { WPElement } from '@wordpress/element';
2 |
3 | /**
4 | * Small button component.
5 | *
6 | * @param {object} props Props.
7 | * @param {WPElement} props.children Children.
8 | * @param {string} props.className Class attribute.
9 | * @returns {WPElement} Element.
10 | */
11 | export default ({ children, className, ...props }) => {
12 | return (
13 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/common/star-rating.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { __, sprintf } from '@wordpress/i18n';
5 | import { WPElement } from '@wordpress/element';
6 |
7 | /**
8 | * Star rating component.
9 | *
10 | * @param {Option} props Component props.
11 | * @param {string} props.rating Rating.
12 | *
13 | * @returns {WPElement} Component element.
14 | */
15 | export default ({ rating }) => {
16 | const label = sprintf(
17 | /* translators: %1$f Rating. %2$d Max rating. */
18 | __('Rated %1$f out of %2$d', 'elasticpress'),
19 | rating,
20 | 5,
21 | );
22 |
23 | return (
24 |
25 |
26 | {label}
27 |
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/facets/facet.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependencies.
8 | */
9 | import PostTypeFacet from './post-type-facet';
10 | import PriceRangeFacet from './price-range-facet';
11 | import TaxonomyTermsFacet from './taxonomy-terms-facet';
12 |
13 | /**
14 | * Facet component.
15 | *
16 | * @param {object} props Props.
17 | * @param {number} props.index Facet index.
18 | * @param {string} props.name Facet name.
19 | * @param {string} props.label Facet label.
20 | * @param {string} props.postTypes Facet post types.
21 | * @param {'post_type'|'price_range'|'taxonomy'} props.type Facet type.
22 | * @returns {WPElement} Component element.
23 | */
24 | export default ({ index, label, name, postTypes, type }) => {
25 | const defaultIsOpen = index < 2;
26 |
27 | switch (type) {
28 | case 'post_type':
29 | return ;
30 | case 'price_range':
31 | return ;
32 | case 'taxonomy':
33 | return (
34 |
40 | );
41 | default:
42 | return null;
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/facets/post-type-facet.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { useCallback, useContext, useMemo, WPElement } from '@wordpress/element';
5 | import { __ } from '@wordpress/i18n';
6 |
7 | /**
8 | * Internal dependencies.
9 | */
10 | import { postTypeLabels } from '../../config';
11 | import Context from '../../context';
12 | import Panel from '../common/panel';
13 | import CheckboxList from '../common/checkbox-list';
14 | import { ActiveContraint } from '../tools/active-constraints';
15 |
16 | /**
17 | * Post type facet component.
18 | *
19 | * @param {object} props Props.
20 | * @param {boolean} props.defaultIsOpen Whether the panel is open by default.
21 | * @param {string} props.label Facet label.
22 | * @returns {WPElement} Component element.
23 | */
24 | export default ({ defaultIsOpen, label }) => {
25 | const {
26 | state: {
27 | aggregations: { post_type: { post_type: { buckets = [] } = {} } = {} },
28 | args: { post_type: selectedPostTypes = [] },
29 | isLoading,
30 | },
31 | dispatch,
32 | } = useContext(Context);
33 |
34 | /**
35 | * Create list of filter options from aggregation buckets.
36 | *
37 | * @param {Array} options List of options.
38 | * @param {object} bucket Aggregation bucket.
39 | * @param {string} bucket.key Aggregation key.
40 | * @param {number} index Bucket index.
41 | * @returns {Array} Array of options.
42 | */
43 | const reduceOptions = useCallback(
44 | (options, { doc_count, key }, index) => {
45 | if (!Object.prototype.hasOwnProperty.call(postTypeLabels, key)) {
46 | return options;
47 | }
48 |
49 | options.push({
50 | checked: selectedPostTypes.includes(key),
51 | count: doc_count,
52 | id: `ep-search-post-type-${key}`,
53 | label: postTypeLabels[key].singular,
54 | order: index,
55 | value: key,
56 | });
57 |
58 | return options;
59 | },
60 | [selectedPostTypes],
61 | );
62 |
63 | /**
64 | * Reduce buckets to options.
65 | */
66 | const options = useMemo(() => buckets.reduce(reduceOptions, []), [buckets, reduceOptions]);
67 |
68 | /**
69 | * Handle checkbox change event.
70 | *
71 | * @param {string[]} postTypes Selected post types.
72 | */
73 | const onChange = (postTypes) => {
74 | dispatch({ type: 'APPLY_ARGS', payload: { post_type: postTypes } });
75 | };
76 |
77 | /**
78 | * Handle clearing a post type.
79 | *
80 | * @param {string} postType Post type being cleared.
81 | */
82 | const onClear = (postType) => {
83 | const postTypes = [...selectedPostTypes];
84 | const index = postTypes.indexOf(postType);
85 |
86 | postTypes.splice(index, 1);
87 |
88 | dispatch({ type: 'APPLY_ARGS', payload: { post_type: postTypes } });
89 | };
90 |
91 | return (
92 | options.length > 0 && (
93 |
94 | {() => (
95 | <>
96 |
103 |
104 | {selectedPostTypes.map((value) => (
105 | onClear(value)}
109 | />
110 | ))}
111 | >
112 | )}
113 |
114 | )
115 | );
116 | };
117 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/facets/search-term-facet.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { useContext, useEffect, useState, WPElement } from '@wordpress/element';
5 | import { __, sprintf } from '@wordpress/i18n';
6 |
7 | /**
8 | * Internal dependencies.
9 | */
10 | import Context from '../../context';
11 | import { useDebounce } from '../../hooks';
12 | import { ActiveContraint } from '../tools/active-constraints';
13 |
14 | /**
15 | * Search field component.
16 | *
17 | * @returns {WPElement} Component element.
18 | */
19 | export default () => {
20 | const {
21 | state: {
22 | args: { search },
23 | searchedTerm,
24 | },
25 | dispatch,
26 | } = useContext(Context);
27 |
28 | const [value, setValue] = useState(search);
29 |
30 | /**
31 | * Dispatch the change, with debouncing.
32 | */
33 | const dispatchChange = useDebounce((value) => {
34 | dispatch({ type: 'NEW_SEARCH_TERM', payload: value });
35 | }, 300);
36 |
37 | /**
38 | * Handle input changes.
39 | *
40 | * @param {Event} event Change event.
41 | */
42 | const onChange = (event) => {
43 | setValue(event.target.value);
44 | dispatchChange(event.target.value);
45 | };
46 |
47 | /**
48 | * Handle clearing.
49 | */
50 | const onClear = () => {
51 | dispatch({ type: 'NEW_SEARCH_TERM', payload: '' });
52 | };
53 |
54 | /**
55 | * Handle an external change to the search value, such as from popping
56 | * state.
57 | */
58 | const handleSearch = () => {
59 | setValue(search);
60 | };
61 |
62 | /**
63 | * Effects.
64 | */
65 | useEffect(handleSearch, [search]);
66 |
67 | return (
68 | <>
69 |
76 | {searchedTerm && (
77 |
85 | )}
86 | >
87 | );
88 | };
89 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/layout.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { useContext, WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependencies.
8 | */
9 | import { facets } from '../config';
10 | import Context from '../context';
11 | import Facet from './facets/facet';
12 | import SearchTermFacet from './facets/search-term-facet';
13 | import Results from './layout/results';
14 | import Sidebar from './layout/sidebar';
15 | import Toolbar from './layout/toolbar';
16 | import ActiveConstraints from './tools/active-constraints';
17 | import ClearConstraints from './tools/clear-constraints';
18 | import SidebarToggle from './tools/sidebar-toggle';
19 | import Sort from './tools/sort';
20 |
21 | /**
22 | * Search dialog.
23 | *
24 | * @returns {WPElement} Component element.
25 | */
26 | export default () => {
27 | const {
28 | state: { isLoading },
29 | } = useContext(Context);
30 |
31 | return (
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | {facets.map(({ label, name, postTypes, type }, index) => (
47 |
55 | ))}
56 |
57 |
58 |
59 |
60 |
61 | );
62 | };
63 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/layout/results.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Internal depenencies.
3 | */
4 | import { useContext, useEffect, useRef, WPElement } from '@wordpress/element';
5 | import { _n, sprintf } from '@wordpress/i18n';
6 |
7 | /**
8 | * Internal dependencies.
9 | */
10 | import Context from '../../context';
11 | import Pagination from '../results/pagination';
12 | import Result from '../results/result';
13 | import Sort from '../tools/sort';
14 |
15 | /**
16 | * Search results component.
17 | *
18 | * @returns {WPElement} Component element.
19 | */
20 | export default () => {
21 | const {
22 | state: {
23 | args: { offset, per_page },
24 | searchResults,
25 | searchedTerm,
26 | totalResults,
27 | },
28 | dispatch,
29 | } = useContext(Context);
30 |
31 | const headingRef = useRef();
32 |
33 | /**
34 | * Handle clicking next.
35 | */
36 | const onNext = () => {
37 | dispatch({ type: 'NEXT_PAGE' });
38 | };
39 |
40 | /**
41 | * Handle clicking previous.
42 | */
43 | const onPrevious = () => {
44 | dispatch({ type: 'PREVIOUS_PAGE' });
45 | };
46 |
47 | /**
48 | * Effects.
49 | */
50 | useEffect(() => {
51 | headingRef.current.scrollIntoView({ behavior: 'smooth' });
52 | }, [offset]);
53 |
54 | return (
55 |
56 |
57 |
58 | {searchedTerm
59 | ? sprintf(
60 | /* translators: %1$d: results count. %2$s: Search term. */
61 | _n(
62 | '%1$d result for “%2$s“',
63 | '%1$d results for “%2$s“',
64 | totalResults,
65 | 'elasticpress',
66 | ),
67 | totalResults,
68 | searchedTerm,
69 | )
70 | : sprintf(
71 | /* translators: %d: results count. */
72 | _n('%d result', '%d results', totalResults, 'elasticpress'),
73 | totalResults,
74 | )}
75 |
76 |
77 |
78 |
79 |
80 | {searchResults.map((hit) => (
81 |
82 | ))}
83 |
84 |
91 |
92 | );
93 | };
94 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/layout/sidebar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { useContext, WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependencies.
8 | */
9 | import Context from '../../context';
10 |
11 | /**
12 | * Search field component.
13 | *
14 | * @param {object} props Props.
15 | * @param {WPElement} props.children Children.
16 | * @returns {WPElement} Element.
17 | */
18 | export default ({ children }) => {
19 | const {
20 | state: { isSidebarOpen },
21 | } = useContext(Context);
22 |
23 | return (
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/layout/toolbar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Search field component.
8 | *
9 | * @param {object} props Props.
10 | * @param {WPElement} props.children Children.
11 | * @returns {WPElement} Element.
12 | */
13 | export default ({ children }) => {
14 | return {children}
;
15 | };
16 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/results/pagination.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { __, sprintf } from '@wordpress/i18n';
5 | import { WPElement } from '@wordpress/element';
6 |
7 | /**
8 | * Search results component.
9 | *
10 | * @param {object} props Props.
11 | * @param {number} props.offset Current items offset.
12 | * @param {Function} props.onNext Next button handler.
13 | * @param {Function} props.onPrevious Previous button handler.
14 | * @param {number} props.perPage Items per page.
15 | * @param {number} props.total Total number of items.
16 | * @returns {WPElement} Element.
17 | */
18 | export default ({ offset, onNext, onPrevious, perPage, total }) => {
19 | /**
20 | * Current page number.
21 | */
22 | const currentPage = (offset + perPage) / perPage;
23 |
24 | /**
25 | * Whether there are more pages.
26 | */
27 | const nextIsAvailable = total > offset + perPage;
28 |
29 | /**
30 | * Whether the are previous pages.
31 | */
32 | const previousIsAvailable = offset > 0;
33 |
34 | /**
35 | * Total pages.
36 | */
37 | const totalPages = Math.ceil(total / perPage);
38 |
39 | return (
40 |
75 | );
76 | };
77 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/results/result.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependencies.
8 | */
9 | import { postTypeLabels, isWooCommerce } from '../../config';
10 | import { formatDate } from '../../functions';
11 | import StarRating from '../common/star-rating';
12 | import Image from '../common/image';
13 |
14 | /**
15 | * Search result.
16 | *
17 | * @param {object} props Component props.
18 | * @param {object} props.hit Elasticsearch hit.
19 | * @returns {WPElement} Component element.
20 | */
21 | export default ({ hit }) => {
22 | const {
23 | highlight: { post_title: resultTitle, post_content_plain: resultContent = [] },
24 | _source: {
25 | meta: { _wc_average_rating: [{ value: resultRating = 0 } = {}] = [] },
26 | post_date: resultDate,
27 | permalink: resultPermalink,
28 | post_type: resultPostType,
29 | price_html: priceHtml,
30 | thumbnail: resultThumbnail = false,
31 | },
32 | } = hit;
33 |
34 | const postTypeLabel = postTypeLabels[resultPostType]?.singular;
35 |
36 | return (
37 |
40 | {resultThumbnail && (
41 |
42 |
43 |
44 | )}
45 |
46 |
47 | {postTypeLabel && {postTypeLabel}}
48 |
49 |
50 | {/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
51 |
56 |
57 |
58 | {isWooCommerce && priceHtml && (
59 | // eslint-disable-next-line react/no-danger
60 |
61 | )}
62 |
63 |
64 | {resultContent.length > 0 && (
65 |
70 | )}
71 |
72 |
76 |
77 | );
78 | };
79 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/tools/active-constraints.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { createPortal, createRef, WPElement } from '@wordpress/element';
5 | import { closeSmall, Icon } from '@wordpress/icons';
6 | import { __, sprintf } from '@wordpress/i18n';
7 |
8 | /**
9 | * Internal dependencies.
10 | */
11 | import SmallButton from '../common/small-button';
12 |
13 | /**
14 | * Create ref for portal.
15 | */
16 | const ref = createRef();
17 |
18 | /**
19 | * Active filter component.
20 | *
21 | * @param {object} props Props.
22 | * @param {string} props.label Constraint label.
23 | * @param {Function} props.onClick Click handler.
24 | * @returns {WPElement} Element.
25 | */
26 | export const ActiveContraint = ({ label, onClick }) => {
27 | if (!ref.current) {
28 | return null;
29 | }
30 |
31 | return createPortal(
32 |
41 |
42 | {label}
43 | ,
44 | ref.current,
45 | );
46 | };
47 |
48 | /**
49 | * Active constraints component.
50 | *
51 | * @returns {WPElement} Element.
52 | */
53 | export default () => {
54 | return ;
55 | };
56 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/tools/clear-constraints.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { useContext, useMemo, WPElement } from '@wordpress/element';
5 | import { __ } from '@wordpress/i18n';
6 |
7 | /**
8 | * Internal dependencies.
9 | */
10 | import { facets } from '../../config';
11 | import Context from '../../context';
12 | import SmallButton from '../common/small-button';
13 |
14 | /**
15 | * Active constraints component.
16 | *
17 | * @returns {WPElement} Element.
18 | */
19 | export default () => {
20 | const {
21 | state: { args },
22 | dispatch,
23 | } = useContext(Context);
24 |
25 | /**
26 | * Return whether there are active filters.
27 | *
28 | * Only filters that are available as facets are checked, as these are the
29 | * only filters that will be cleared. This is to support applying filters
30 | * that cannot be modified by the user.
31 | *
32 | * @returns {boolean} Whether there are active filters.
33 | */
34 | const hasFilters = useMemo(() => {
35 | return facets.some(({ name, type }) => {
36 | switch (type) {
37 | case 'post_type':
38 | case 'taxonomy':
39 | return args[name]?.length > 0;
40 | case 'price_range':
41 | return args.max_price || args.min_price;
42 | default:
43 | return args[name];
44 | }
45 | });
46 | }, [args]);
47 |
48 | /**
49 | * Handle clicking button.
50 | *
51 | * @returns {void}
52 | */
53 | const onClick = () => {
54 | dispatch({ type: 'CLEAR_FACETS' });
55 | };
56 |
57 | return (
58 | hasFilters && (
59 | {__('Clear filters', 'elasticpress')}
60 | )
61 | );
62 | };
63 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/tools/sidebar-toggle.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress deendencies.
3 | */
4 | import { useContext, WPElement } from '@wordpress/element';
5 | import { chevronDown, chevronUp, Icon } from '@wordpress/icons';
6 | import { __ } from '@wordpress/i18n';
7 |
8 | /**
9 | * Internal deendencies.
10 | */
11 | import Context from '../../context';
12 |
13 | /**
14 | * Open sidebar component.
15 | *
16 | * @returns {WPElement} Element.
17 | */
18 | export default () => {
19 | const {
20 | state: { isSidebarOpen },
21 | dispatch,
22 | } = useContext(Context);
23 |
24 | /**
25 | * Handle click.
26 | */
27 | const onClick = () => {
28 | dispatch({ type: 'TOGGLE_SIDEBAR' });
29 | };
30 |
31 | return (
32 |
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/assets/js/instant-results/components/tools/sort.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress deendencies.
3 | */
4 | import { useContext, useMemo, WPElement } from '@wordpress/element';
5 | import { __ } from '@wordpress/i18n';
6 |
7 | /**
8 | * Internal deendencies.
9 | */
10 | import { sortOptions } from '../../config';
11 | import Context from '../../context';
12 |
13 | /**
14 | * Search results component.
15 | *
16 | * @returns {WPElement} Component element.
17 | */
18 | export default () => {
19 | const {
20 | state: {
21 | args: { orderby, order },
22 | },
23 | dispatch,
24 | } = useContext(Context);
25 |
26 | /**
27 | * The key for the current sorting option.
28 | */
29 | const currentOption = useMemo(() => {
30 | return Object.keys(sortOptions).find((key) => {
31 | return sortOptions[key].orderby === orderby && sortOptions[key].order === order;
32 | });
33 | }, [orderby, order]);
34 |
35 | /**
36 | * Handle sorting option change.
37 | *
38 | * @param {Event} event Change event.
39 | */
40 | const onChange = (event) => {
41 | const { orderby, order } = sortOptions[event.target.value];
42 |
43 | dispatch({ type: 'APPLY_ARGS', payload: { orderby, order } });
44 | };
45 |
46 | return (
47 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/assets/js/instant-results/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | /**
7 | * Window dependencies.
8 | */
9 | const {
10 | apiEndpoint,
11 | apiHost,
12 | argsSchema,
13 | currencyCode,
14 | facets,
15 | isWooCommerce,
16 | locale,
17 | matchType,
18 | paramPrefix,
19 | postTypeLabels,
20 | taxonomyLabels,
21 | } = window.epInstantResults;
22 |
23 | /**
24 | * Sorting options configuration.
25 | */
26 | const sortOptions = {
27 | relevance_desc: {
28 | name: __('Most relevant', 'elasticpress'),
29 | orderby: 'relevance',
30 | order: 'desc',
31 | currencyCode,
32 | },
33 | date_desc: {
34 | name: __('Date, newest to oldest', 'elasticpress'),
35 | orderby: 'date',
36 | order: 'desc',
37 | },
38 | date_asc: {
39 | name: __('Date, oldest to newest', 'elasticpress'),
40 | orderby: 'date',
41 | order: 'asc',
42 | },
43 | };
44 |
45 | /**
46 | * Sort by price is only available for WooCommerce.
47 | */
48 | if (isWooCommerce) {
49 | sortOptions.price_desc = {
50 | name: __('Price, highest to lowest', 'elasticpress'),
51 | orderby: 'price',
52 | order: 'desc',
53 | };
54 |
55 | sortOptions.price_asc = {
56 | name: __('Price, lowest to highest', 'elasticpress'),
57 | orderby: 'price',
58 | order: 'asc',
59 | };
60 | }
61 |
62 | export {
63 | apiEndpoint,
64 | apiHost,
65 | argsSchema,
66 | currencyCode,
67 | facets,
68 | isWooCommerce,
69 | locale,
70 | matchType,
71 | paramPrefix,
72 | postTypeLabels,
73 | sortOptions,
74 | taxonomyLabels,
75 | };
76 |
--------------------------------------------------------------------------------
/assets/js/instant-results/context.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Internal dependencies.
3 | */
4 | import { createContext } from '@wordpress/element';
5 |
6 | export default createContext();
7 |
--------------------------------------------------------------------------------
/assets/js/instant-results/functions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Internal deendencies.
3 | */
4 | import { currencyCode, facets, locale } from './config';
5 | import { sanitizeArg, sanitizeParam } from './utilities';
6 |
7 | /**
8 | * Clear facet filters from a set of args.
9 | *
10 | * @param {object} args Args to clear facets from.
11 | * @returns {object} Cleared args.
12 | */
13 | export const clearFacetsFromArgs = (args) => {
14 | const clearedArgs = { ...args };
15 |
16 | facets.forEach(({ name, type }) => {
17 | switch (type) {
18 | case 'price_range':
19 | delete clearedArgs.max_price;
20 | delete clearedArgs.min_price;
21 | break;
22 | default:
23 | delete clearedArgs[name];
24 | break;
25 | }
26 | });
27 |
28 | return clearedArgs;
29 | };
30 |
31 | /**
32 | * Format a date.
33 | *
34 | * @param {string} date Date string.
35 | * @returns {string} Formatted number.
36 | */
37 | export const formatDate = (date) => {
38 | return new Date(date).toLocaleString(locale, { dateStyle: 'long' });
39 | };
40 |
41 | /**
42 | * Format a number as a price.
43 | *
44 | * @param {number} number Number to format.
45 | * @param {object} options Formatter options.
46 | * @returns {string} Formatted number.
47 | */
48 | export const formatPrice = (number, options) => {
49 | const format = new Intl.NumberFormat(navigator.language, {
50 | style: 'currency',
51 | currency: currencyCode,
52 | currencyDisplay: 'narrowSymbol',
53 | ...options,
54 | });
55 |
56 | return format.format(number);
57 | };
58 |
59 | /**
60 | * Get the post types from a search form.
61 | *
62 | * @param {HTMLFormElement} form Form element.
63 | * @returns {Array} Post types.
64 | */
65 | export const getPostTypesFromForm = (form) => {
66 | const data = new FormData(form);
67 |
68 | if (data.has('post_type')) {
69 | return data.getAll('post_type').slice(-1);
70 | }
71 |
72 | if (data.has('post_type[]')) {
73 | return data.getAll('post_type[]');
74 | }
75 |
76 | return [];
77 | };
78 |
79 | /**
80 | * Get permalink URL parameters from args.
81 | *
82 | * @typedef {object} ArgSchema
83 | * @property {string} type Arg type.
84 | * @property {any} [default] Default arg value.
85 | * @property {Array} [allowedValues] Array of allowed values.
86 | *
87 | * @param {object} args Args
88 | * @param {ArgSchema} schema Args schema.
89 | * @param {string} [prefix] Prefix to prepend to args.
90 | * @returns {URLSearchParams} URLSearchParams instance.
91 | */
92 | export const getUrlParamsFromArgs = (args, schema, prefix = '') => {
93 | const urlParams = new URLSearchParams();
94 |
95 | Object.entries(schema).forEach(([arg, options]) => {
96 | const param = prefix + arg;
97 | const value = typeof args[arg] !== 'undefined' ? sanitizeParam(args[arg], options) : null;
98 |
99 | if (value !== null) {
100 | urlParams.set(param, value);
101 | }
102 | });
103 |
104 | return urlParams;
105 | };
106 |
107 | /**
108 | * Build request args from URL parameters using a given schema.
109 | *
110 | * @typedef {object} ArgSchema
111 | * @property {string} type Arg type.
112 | * @property {any} [default] Default arg value.
113 | * @property {Array} [allowedValues] Array of allowed values.
114 | *
115 | * @param {URLSearchParams} urlParams URL parameters.
116 | * @param {object.} schema Schema to build args from.
117 | * @param {string} [prefix] Parameter prefix.
118 | * @param {boolean} [useDefaults] Whether to populate params with default values.
119 | * @returns {object.} Query args.
120 | */
121 | export const getArgsFromUrlParams = (urlParams, schema, prefix = '', useDefaults = true) => {
122 | const args = Object.entries(schema).reduce((args, [arg, options]) => {
123 | const param = urlParams.get(prefix + arg);
124 | const value =
125 | typeof param !== 'undefined' ? sanitizeArg(param, options, useDefaults) : null;
126 |
127 | if (value !== null) {
128 | args[arg] = value;
129 | }
130 |
131 | return args;
132 | }, {});
133 |
134 | return args;
135 | };
136 |
--------------------------------------------------------------------------------
/assets/js/instant-results/hooks.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useRef } from '@wordpress/element';
2 | import { apiEndpoint, apiHost } from './config';
3 |
4 | /**
5 | * Get debounced version of a function that only runs a given ammount of time
6 | * after the last time it was run.
7 | *
8 | * @param {Function} callback Function to debounce.
9 | * @param {number} delay Milliseconds to delay.
10 | * @returns {Function} Debounced function.
11 | */
12 | export const useDebounce = (callback, delay) => {
13 | const timeout = useRef(null);
14 |
15 | return useCallback(
16 | (...args) => {
17 | window.clearTimeout(timeout.current);
18 |
19 | timeout.current = window.setTimeout(() => {
20 | callback(...args);
21 | }, delay);
22 | },
23 | [callback, delay],
24 | );
25 | };
26 |
27 | /**
28 | * Get a callback function for retrieving search results.
29 | *
30 | * @returns {Function} Memoized callback function for retrieving search results.
31 | */
32 | export const useGetResults = () => {
33 | const abort = useRef(new AbortController());
34 | const request = useRef(null);
35 |
36 | /**
37 | * Get new search results from the API.
38 | *
39 | * @param {URLSearchParams} urlParams Query arguments.
40 | * @returns {Promise} Request promise.
41 | */
42 | const getResults = async (urlParams) => {
43 | const url = `${apiHost}${apiEndpoint}?${urlParams.toString()}`;
44 |
45 | abort.current.abort();
46 | abort.current = new AbortController();
47 |
48 | request.current = fetch(url, {
49 | signal: abort.current.signal,
50 | headers: {
51 | Accept: 'application/json',
52 | },
53 | })
54 | .then((response) => {
55 | return response.json();
56 | })
57 | .catch((error) => {
58 | if (error?.name !== 'AbortError' && !request.current) {
59 | throw error;
60 | }
61 | })
62 | .finally(() => {
63 | request.current = null;
64 | });
65 |
66 | return request.current;
67 | };
68 |
69 | return useCallback(getResults, []);
70 | };
71 |
--------------------------------------------------------------------------------
/assets/js/instant-results/reducer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Internal dependencies.
3 | */
4 | import { matchType } from './config';
5 | import { clearFacetsFromArgs } from './functions';
6 |
7 | /**
8 | * Initial state.
9 | */
10 | export const initialState = {
11 | aggregations: {},
12 | args: {
13 | highlight: '',
14 | offset: 0,
15 | orderby: 'relevance',
16 | order: 'desc',
17 | per_page: 6,
18 | relation: matchType === 'all' ? 'and' : 'or',
19 | search: '',
20 | },
21 | isLoading: false,
22 | isOpen: false,
23 | isSidebarOpen: false,
24 | isPoppingState: false,
25 | searchResults: [],
26 | searchedTerm: '',
27 | totalResults: 0,
28 | };
29 |
30 | /**
31 | * Reducer function for handling state changes.
32 | *
33 | * @param {object} state The current state.
34 | * @param {object} action Action data.
35 | * @param {string} action.type The action name.
36 | * @param {object} action.payload New state data from the action.
37 | * @returns {object} Updated state.
38 | */
39 | export const reducer = (state, { type, payload }) => {
40 | const newState = { ...state, isPoppingState: false };
41 |
42 | switch (type) {
43 | case 'APPLY_ARGS': {
44 | newState.args = { ...newState.args, ...payload, offset: 0 };
45 | newState.isOpen = true;
46 | break;
47 | }
48 | case 'CLEAR_FACETS': {
49 | newState.args = clearFacetsFromArgs(newState.args);
50 | break;
51 | }
52 | case 'NEW_SEARCH_TERM': {
53 | newState.args = clearFacetsFromArgs(newState.args);
54 | newState.args.offset = 0;
55 | newState.args.search = payload;
56 |
57 | break;
58 | }
59 | case 'NEW_SEARCH_RESULTS': {
60 | const {
61 | hits: { hits, total },
62 | aggregations,
63 | } = payload;
64 |
65 | /**
66 | * Total number of items.
67 | */
68 | const totalNumber = typeof total === 'number' ? total : total.value;
69 |
70 | newState.aggregations = aggregations;
71 | newState.searchResults = hits;
72 | newState.searchedTerm = newState.args.search;
73 | newState.totalResults = totalNumber;
74 |
75 | break;
76 | }
77 | case 'NEXT_PAGE': {
78 | newState.args.offset += newState.args.per_page;
79 | break;
80 | }
81 | case 'PREVIOUS_PAGE': {
82 | newState.args.offset = Math.max(newState.args.offset - newState.args.per_page, 0);
83 | break;
84 | }
85 | case 'START_LOADING': {
86 | newState.isLoading = true;
87 | break;
88 | }
89 | case 'FINISH_LOADING': {
90 | newState.isLoading = false;
91 | break;
92 | }
93 | case 'TOGGLE_SIDEBAR': {
94 | newState.isSidebarOpen = !state.isSidebarOpen;
95 | break;
96 | }
97 | case 'CLOSE_MODAL': {
98 | newState.args = clearFacetsFromArgs(newState.args);
99 | newState.isOpen = false;
100 | break;
101 | }
102 | case 'POP_STATE': {
103 | const { isOpen, ...args } = payload;
104 |
105 | newState.args = args;
106 | newState.isOpen = isOpen;
107 | newState.isPoppingState = true;
108 |
109 | break;
110 | }
111 | default:
112 | break;
113 | }
114 |
115 | return newState;
116 | };
117 |
--------------------------------------------------------------------------------
/assets/js/instant-results/utilities.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Sanitize an argument value based on its type.
3 | *
4 | * @param {*} value The value.
5 | * @param {object} options Sanitization options.
6 | * @param {'number'|'numbers'|'string'|'strings'} options.type (optional) Value type.
7 | * @param {Array} options.allowedValues (optional) Allowed values.
8 | * @param {*} options.default (optional) Default value.
9 | * @param {boolean} [useDefaults] Whether to return default values.
10 | * @returns {*} Sanitized value.
11 | */
12 | export const sanitizeArg = (value, options, useDefaults = true) => {
13 | let sanitizedValue = null;
14 |
15 | switch (value && options.type) {
16 | case 'number':
17 | sanitizedValue = parseFloat(value, 10) || null;
18 | break;
19 | case 'numbers':
20 | sanitizedValue = decodeURIComponent(value)
21 | .split(',')
22 | .map((v) => parseFloat(v, 10))
23 | .filter(Boolean);
24 | break;
25 | case 'string':
26 | sanitizedValue = value.toString();
27 | break;
28 | case 'strings':
29 | sanitizedValue = decodeURIComponent(value)
30 | .split(',')
31 | .map((v) => v.toString().trim());
32 | break;
33 | default:
34 | break;
35 | }
36 |
37 | /**
38 | * If there is a list of allowed values, make sure the value is
39 | * allowed.
40 | */
41 | if (options.allowedValues) {
42 | sanitizedValue = options.allowedValues.includes(sanitizedValue) ? sanitizedValue : null;
43 | }
44 |
45 | /**
46 | * Populate a default value if one is available and we still don't
47 | * have a value.
48 | */
49 | if (useDefaults && sanitizedValue === null && typeof options.default !== 'undefined') {
50 | sanitizedValue = options.default;
51 | }
52 |
53 | return sanitizedValue;
54 | };
55 |
56 | /**
57 | * Sanitize a parameter value based on its type.
58 | *
59 | * @param {*} value The value.
60 | * @param {object} options Sanitization options.
61 | * @param {'number'|'numbers'|'string'|'strings'} options.type (optional) Value type.
62 | * @param {Array} options.allowedValues (optional) Allowed values.
63 | * @param {*} options.default (optional) Default value.
64 | * @param {boolean} [useDefaults] Whether to return default values.
65 | * @returns {*} Sanitized value.
66 | */
67 | export const sanitizeParam = (value, options, useDefaults = true) => {
68 | let sanitizedValue = null;
69 |
70 | switch (value && options.type) {
71 | case 'number':
72 | case 'string':
73 | sanitizedValue = value;
74 | break;
75 | case 'numbers':
76 | case 'strings':
77 | sanitizedValue = value.join(',');
78 | break;
79 | default:
80 | break;
81 | }
82 |
83 | /**
84 | * If there is a list of allowed values, make sure the value is
85 | * allowed.
86 | */
87 | if (options.allowedValues) {
88 | sanitizedValue = options.allowedValues.includes(sanitizedValue) ? sanitizedValue : null;
89 | }
90 |
91 | /**
92 | * Populate a default value if one is available and we still don't
93 | * have a value.
94 | */
95 | if (useDefaults && sanitizedValue === null && typeof options.default !== 'undefined') {
96 | sanitizedValue = options.default;
97 | }
98 |
99 | return sanitizedValue;
100 | };
101 |
--------------------------------------------------------------------------------
/assets/js/notice.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import apiFetch from '@wordpress/api-fetch';
5 | import domReady from '@wordpress/dom-ready';
6 |
7 | /**
8 | * Window dependencies.
9 | */
10 | const { epAdmin, ajaxurl } = window;
11 |
12 | /**
13 | * Initialize.
14 | *
15 | * @returns {void}
16 | */
17 | const init = () => {
18 | const notices = document.querySelectorAll('.notice[data-ep-notice]');
19 |
20 | /**
21 | * Handle clicking in an ElasticPress notice.
22 | *
23 | * If the click target is the dismiss button send an AJAX request to remember
24 | * the dismissal.
25 | *
26 | * @param {Event} event Click event.
27 | * @returns {void}
28 | */
29 | const onClick = (event) => {
30 | /**
31 | * Only proceed if we're clicking dismiss.
32 | */
33 | if (!event.target.classList.contains('notice-dismiss')) {
34 | return;
35 | }
36 |
37 | /**
38 | * Handler is admin-ajax.php, so the body needs to be form data.
39 | */
40 | const formData = new FormData();
41 |
42 | formData.append('action', 'ep_notice_dismiss');
43 | formData.append('notice', event.currentTarget.dataset.epNotice);
44 | formData.append('nonce', epAdmin.nonce);
45 |
46 | apiFetch({
47 | method: 'POST',
48 | url: ajaxurl,
49 | body: formData,
50 | });
51 | };
52 |
53 | /**
54 | * Bind click events to notices.
55 | */
56 | for (const notice of notices) {
57 | notice.addEventListener('click', onClick);
58 | }
59 | };
60 |
61 | /**
62 | * Initialize.
63 | */
64 | domReady(init);
65 |
--------------------------------------------------------------------------------
/assets/js/ordering/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { render } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependencies.
8 | */
9 | import { Pointers } from './pointers';
10 |
11 | render(, document.getElementById('ordering-app'));
12 |
--------------------------------------------------------------------------------
/assets/js/settings.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import domReady from '@wordpress/dom-ready';
5 | import { __ } from '@wordpress/i18n';
6 |
7 | /**
8 | * Initialize.
9 | *
10 | * @returns {void}
11 | */
12 | const init = () => {
13 | const tabs = document.querySelectorAll('.ep-credentials-tab');
14 | const host = document.getElementById('ep_host');
15 | const hostLabel = host.labels[0];
16 | const hostDescription = host.nextElementSibling;
17 | const additionalFields = document.getElementsByClassName('ep-additional-fields');
18 |
19 | let activeTab = document.querySelector('.nav-tab-active');
20 |
21 | /**
22 | * Is the current tab the ElasticPress.io tab?
23 | *
24 | * @returns {boolean} Whether the current tab is for ElasticPress.io.
25 | */
26 | const isEpio = () => {
27 | return activeTab && 'epio' in activeTab.dataset;
28 | };
29 |
30 | let epioHost = isEpio() ? host.value : '';
31 | let esHost = isEpio() ? '' : host.value;
32 |
33 | /**
34 | * Handle input on the host field.
35 | *
36 | * @param {Event} event Input event.
37 | * @returns {void}
38 | */
39 | const onInput = (event) => {
40 | if (isEpio()) {
41 | epioHost = event.currentTarget.value;
42 | } else {
43 | esHost = event.currentTarget.value;
44 | }
45 | };
46 |
47 | /**
48 | * Handle clicking on a tab.
49 | *
50 | * @param {Event} event Click event.
51 | * @returns {void}
52 | */
53 | const onClick = (event) => {
54 | activeTab = event.currentTarget;
55 |
56 | /**
57 | * Set active tab.
58 | */
59 | for (const tab of tabs) {
60 | tab.classList.toggle('nav-tab-active', tab === activeTab);
61 | }
62 |
63 | /**
64 | * Hide or show additional fields.
65 | */
66 | for (const additionalField of additionalFields) {
67 | additionalField.classList.toggle('hidden', !isEpio());
68 | }
69 |
70 | /**
71 | * Update field label.
72 | */
73 | hostLabel.innerText = isEpio()
74 | ? __('ElasticPress.io Host URL', 'elasticpress')
75 | : __('Elasticsearch Host URL', 'elasticpress');
76 |
77 | /**
78 | * If the host field is disabled, we're done.
79 | */
80 | if (host.disabled) {
81 | return;
82 | }
83 |
84 | /**
85 | * Restore field value for the current tab.
86 | */
87 | host.value = isEpio() ? epioHost : esHost;
88 |
89 | /**
90 | * Update host field description.
91 | */
92 | hostDescription.innerText = isEpio()
93 | ? __('Plug in your ElasticPress.io server here!', 'elasticpress')
94 | : __('Plug in your Elasticsearch server here!', 'elasticpress');
95 | };
96 |
97 | /**
98 | * Bind input event to host field.
99 | */
100 | if (host) {
101 | host.addEventListener('input', onInput);
102 | }
103 |
104 | /**
105 | * Bind click events to tabs.
106 | */
107 | for (const tab of tabs) {
108 | tab.addEventListener('click', onClick);
109 | }
110 | };
111 |
112 | /**
113 | * Initialize.
114 | */
115 | domReady(init);
116 |
--------------------------------------------------------------------------------
/assets/js/sites-admin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import apiFetch from '@wordpress/api-fetch';
5 | import { ToggleControl } from '@wordpress/components';
6 | import domReady from '@wordpress/dom-ready';
7 | import { render, useState, WPElement } from '@wordpress/element';
8 | import { __ } from '@wordpress/i18n';
9 |
10 | /**
11 | * Window dependencies.
12 | */
13 | const { ajaxurl, epsa } = window;
14 |
15 | /**
16 | * Toggle component.
17 | *
18 | * @param {object} props Component props.
19 | * @param {string} props.blogId Blog ID.
20 | * @param {boolean} props.isDefaultChecked Whether checked by default.
21 | * @returns {WPElement} Toggle component.
22 | */
23 | const ElasticPressToggleControl = ({ blogId, isDefaultChecked }) => {
24 | const [isChecked, setIsChecked] = useState(isDefaultChecked);
25 | const [isLoading, setIsLoading] = useState(false);
26 |
27 | /**
28 | * Handle toggle change.
29 | *
30 | * @param {boolean} isChecked New checked state.
31 | * @returns {void}
32 | */
33 | const onChange = async (isChecked) => {
34 | setIsChecked(isChecked);
35 | setIsLoading(true);
36 |
37 | const formData = new FormData();
38 |
39 | formData.append('action', 'ep_site_admin');
40 | formData.append('blog_id', blogId);
41 | formData.append('checked', isChecked ? 'yes' : 'no');
42 | formData.append('nonce', epsa.nonce);
43 |
44 | await apiFetch({
45 | method: 'POST',
46 | url: ajaxurl,
47 | body: formData,
48 | });
49 |
50 | setIsLoading(false);
51 | };
52 |
53 | return (
54 |
61 | );
62 | };
63 |
64 | /**
65 | * Initialize.
66 | *
67 | * @returns {void}
68 | */
69 | const init = () => {
70 | const toggles = document.getElementsByClassName('index-toggle');
71 |
72 | for (const toggle of toggles) {
73 | render(
74 | ,
78 | toggle.parentElement,
79 | );
80 | }
81 | };
82 |
83 | domReady(init);
84 |
--------------------------------------------------------------------------------
/assets/js/stats.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-new */
2 |
3 | import Chart from 'chart.js';
4 |
5 | const { epChartData } = window;
6 |
7 | /**
8 | * Generates a random string representing a color.
9 | *
10 | * @returns {string} Random color
11 | */
12 | function getRandomColor() {
13 | const letters = '0123456789ABCDEF';
14 | let color = '#';
15 |
16 | for (let i = 0; i < 6; i += 1) {
17 | color += letters[Math.floor(Math.random() * 16)];
18 | }
19 |
20 | return color;
21 | }
22 |
23 | const barData = Object.entries(epChartData.indices_data);
24 | const barLabels = [];
25 | const barDocs = [];
26 | const barColors = [];
27 |
28 | Chart.defaults.global.legend.labels.usePointStyle = true;
29 |
30 | barData.forEach(function (data) {
31 | barLabels.push(data[1].name);
32 | barDocs.push(data[1].docs);
33 | barColors.push(getRandomColor());
34 | });
35 |
36 | const documentChart = document.getElementById('documentChart');
37 | if (documentChart) {
38 | new Chart(documentChart, {
39 | type: 'horizontalBar',
40 | data: {
41 | labels: barLabels,
42 | datasets: [
43 | {
44 | label: 'Documents',
45 | backgroundColor: barColors,
46 | data: barDocs,
47 | },
48 | ],
49 | },
50 | options: {
51 | legend: {
52 | display: false,
53 | },
54 | title: {
55 | display: true,
56 | },
57 | },
58 | });
59 | }
60 |
61 | const queriesTotalChart = document.getElementById('queriesTotalChart');
62 | if (queriesTotalChart) {
63 | new Chart(document.getElementById('queriesTotalChart'), {
64 | type: 'pie',
65 | data: {
66 | labels: ['Indexing operations', 'Total Query operations'],
67 | datasets: [
68 | {
69 | label: '',
70 | backgroundColor: ['#5ba9a7', '#2e7875', '#a980a4'],
71 | data: [epChartData.index_total, epChartData.query_total],
72 | },
73 | ],
74 | },
75 | options: {
76 | responsive: false,
77 | title: {
78 | display: true,
79 | },
80 | legend: {
81 | position: 'right',
82 | },
83 | tooltips: {
84 | callbacks: {
85 | /**
86 | * Appends the string operations before tooltip value
87 | *
88 | * @param {object} item Chat item
89 | * @param {object} data Data
90 | * @returns {string} Operations
91 | */
92 | label(item, data) {
93 | const dataset = data.datasets[item.datasetIndex];
94 | const currentValue = dataset.data[item.index];
95 |
96 | return `Operations: ${currentValue}`;
97 | },
98 | },
99 | },
100 | },
101 | });
102 | }
103 |
--------------------------------------------------------------------------------
/assets/js/sync/components/common/date-time.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { dateI18n } from '@wordpress/date';
5 | import { WPElement } from '@wordpress/element';
6 |
7 | /**
8 | * Log component.
9 | *
10 | * @param {object} props Component props.
11 | * @param {string} props.dateTime Date and time.
12 | * @returns {WPElement} Component.
13 | */
14 | export default ({ dateTime, ...props }) => {
15 | return (
16 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/assets/js/sync/components/common/message-log.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Log component.
8 | *
9 | * @param {object} props Component props.
10 | * @param {object[]} props.messages Log messages.
11 | * @returns {WPElement} Component.
12 | */
13 | export default ({ messages }) => {
14 | return (
15 |
16 | {messages.map((m, i) => (
17 |
22 | {i + 1}
23 |
24 | ))}
25 | {messages.map((m) => (
26 |
30 | {m.message}
31 |
32 | ))}
33 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/assets/js/sync/components/common/progress-bar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Progress bar component.
8 | *
9 | * @param {object} props Component props.
10 | * @param {number} props.current Current value.
11 | * @param {number} props.total Current total.
12 | * @param {boolean} props.isComplete If operation is complete.
13 | * @returns {WPElement} Component.
14 | */
15 | export default ({ isComplete, current, total }) => {
16 | const now = Math.floor((current / total) * 100);
17 |
18 | return (
19 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/assets/js/sync/components/icons/pause.js:
--------------------------------------------------------------------------------
1 | import { SVG, Path } from '@wordpress/primitives';
2 |
3 | export default () => {
4 | return (
5 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/assets/js/sync/components/icons/play.js:
--------------------------------------------------------------------------------
1 | import { SVG, Path } from '@wordpress/primitives';
2 |
3 | export default () => {
4 | return (
5 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/assets/js/sync/components/icons/stop.js:
--------------------------------------------------------------------------------
1 | import { SVG, Path } from '@wordpress/primitives';
2 |
3 | export default () => {
4 | return (
5 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/assets/js/sync/components/icons/sync.js:
--------------------------------------------------------------------------------
1 | import { SVG, Path } from '@wordpress/primitives';
2 |
3 | export default () => {
4 | return (
5 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/assets/js/sync/components/icons/thumbs-down.js:
--------------------------------------------------------------------------------
1 | import { SVG, Path } from '@wordpress/primitives';
2 |
3 | export default () => {
4 | return (
5 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/assets/js/sync/components/icons/thumbs-up.js:
--------------------------------------------------------------------------------
1 | import { SVG, Path } from '@wordpress/primitives';
2 |
3 | export default () => {
4 | return (
5 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/assets/js/sync/components/sync/controls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { Button } from '@wordpress/components';
5 | import { WPElement } from '@wordpress/element';
6 | import { __ } from '@wordpress/i18n';
7 | import { update } from '@wordpress/icons';
8 |
9 | /**
10 | * Internal dependencies.
11 | */
12 | import pause from '../icons/pause';
13 | import play from '../icons/play';
14 | import stop from '../icons/stop';
15 |
16 | /**
17 | * Sync button component.
18 | *
19 | * @param {object} props Component props.
20 | * @param {boolean} props.disabled If controls are disabled.
21 | * @param {boolean} props.isPaused If syncing is paused.
22 | * @param {boolean} props.isSyncing If syncing is in progress.
23 | * @param {Function} props.onPause Pause button click callback.
24 | * @param {Function} props.onResume Play button click callback.
25 | * @param {Function} props.onStop Stop button click callback.
26 | * @param {Function} props.onSync Sync button click callback.
27 | * @param {boolean} props.showSync If sync button is shown.
28 | * @returns {WPElement} Component.
29 | */
30 | export default ({ disabled, isPaused, isSyncing, onPause, onResume, onStop, onSync, showSync }) => {
31 | /**
32 | * Render.
33 | */
34 | return (
35 |
36 | {showSync && !isSyncing ? (
37 |
38 |
47 |
48 | ) : null}
49 |
50 | {isSyncing ? (
51 | <>
52 |
53 | {isPaused ? (
54 |
63 | ) : (
64 |
73 | )}
74 |
75 |
76 |
77 |
85 |
86 | >
87 | ) : null}
88 |
89 | {showSync ? (
90 |
91 |
98 |
99 | ) : null}
100 |
101 | );
102 | };
103 |
--------------------------------------------------------------------------------
/assets/js/sync/components/sync/log.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { TabPanel, ToggleControl } from '@wordpress/components';
5 | import { useState, WPElement } from '@wordpress/element';
6 | import { __, sprintf } from '@wordpress/i18n';
7 |
8 | /**
9 | * Internal dependencies.
10 | */
11 | import MessageLog from '../common/message-log';
12 |
13 | /**
14 | * Sync logs component.
15 | *
16 | * @param {object} props Component props.
17 | * @param {object[]} props.messages Log messages.
18 | * @returns {WPElement} Component.
19 | */
20 | export default ({ messages }) => {
21 | const [isOpen, setIsOpen] = useState(false);
22 |
23 | /**
24 | * Messages with the error status.
25 | */
26 | const errorMessages = messages.filter((m) => m.status === 'error' || m.status === 'warning');
27 |
28 | /**
29 | * Log tabs.
30 | */
31 | const tabs = [
32 | {
33 | messages,
34 | name: 'full',
35 | title: __('Full Log', 'elasticpress'),
36 | },
37 | {
38 | messages: errorMessages,
39 | name: 'error',
40 | title: sprintf(
41 | /* translators: %d: Error message count. */
42 | __('Errors (%d)', 'elasticpress'),
43 | errorMessages.length,
44 | ),
45 | },
46 | ];
47 |
48 | /**
49 | * Handle clicking show log button.
50 | *
51 | * @param {boolean} checked If toggle is checked.
52 | * @returns {void}
53 | */
54 | const onToggle = (checked) => {
55 | setIsOpen(checked);
56 | };
57 |
58 | return (
59 | <>
60 |
65 | {isOpen ? (
66 |
67 | {({ messages }) => }
68 |
69 | ) : null}
70 | >
71 | );
72 | };
73 |
--------------------------------------------------------------------------------
/assets/js/sync/components/sync/progress.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { Icon } from '@wordpress/components';
5 | import { useMemo, WPElement } from '@wordpress/element';
6 | import { dateI18n } from '@wordpress/date';
7 | import { __ } from '@wordpress/i18n';
8 |
9 | /**
10 | * Internal dependencies.
11 | */
12 | import DateTime from '../common/date-time';
13 | import ProgressBar from '../common/progress-bar';
14 | import sync from '../icons/sync';
15 |
16 | /**
17 | * Sync button component.
18 | *
19 | * @param {object} props Component props.
20 | * @param {boolean} props.isCli If progress is for a CLI sync.
21 | * @param {boolean} props.isComplete If sync is complete.
22 | * @param {boolean} props.isPaused If sync is paused.
23 | * @param {number} props.itemsProcessed Number of items processed.
24 | * @param {number} props.itemsTotal Total number of items.
25 | * @param {string} props.dateTime Start date and time.
26 | * @returns {WPElement} Component.
27 | */
28 | export default ({ isCli, isComplete, isPaused, itemsProcessed, itemsTotal, dateTime }) => {
29 | /**
30 | * Sync progress label.
31 | */
32 | const label = useMemo(
33 | /**
34 | * Determine appropriate sync status label.
35 | *
36 | * @returns {string} Sync progress label.
37 | */
38 | () => {
39 | if (isComplete) {
40 | return __('Sync complete', 'elasticpress');
41 | }
42 |
43 | if (isPaused) {
44 | return __('Sync paused', 'elasticpress');
45 | }
46 |
47 | if (isCli) {
48 | return __('WP CLI sync in progress', 'elasticpress');
49 | }
50 |
51 | return __('Sync in progress', 'elasticpress');
52 | },
53 | [isCli, isComplete, isPaused],
54 | );
55 |
56 | return (
57 |
62 |
63 |
64 |
65 | {label}
66 | {dateTime ? (
67 | <>
68 | {__('Started on', 'elasticpress')}{' '}
69 |
70 | >
71 | ) : null}
72 |
73 |
74 |
77 |
78 | );
79 | };
80 |
--------------------------------------------------------------------------------
/assets/js/sync/components/sync/status.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { Icon } from '@wordpress/components';
5 | import { dateI18n } from '@wordpress/date';
6 | import { WPElement } from '@wordpress/element';
7 | import { __ } from '@wordpress/i18n';
8 |
9 | /**
10 | * Internal dependencies.
11 | */
12 | import DateTime from '../common/date-time';
13 | import thumbsDown from '../icons/thumbs-down';
14 | import thumbsUp from '../icons/thumbs-up';
15 |
16 | /**
17 | * Sync button component.
18 | *
19 | * @param {object} props Component props.
20 | * @param {string} props.dateTime Sync date and time.
21 | * @param {boolean} props.isSuccess If sync was a success.
22 | * @returns {WPElement} Component.
23 | */
24 | export default ({ dateTime, isSuccess }) => {
25 | return (
26 |
31 |
32 |
33 | {isSuccess
34 | ? __('Sync success on', 'elasticpress')
35 | : __('Sync unsuccessful on', 'elasticpress')}{' '}
36 |
37 |
38 |
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/assets/js/sync/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Window dependencies.
3 | */
4 | const {
5 | auto_start_index: autoIndex,
6 | ajax_url: ajaxUrl,
7 | index_meta: indexMeta = null,
8 | is_epio: isEpio,
9 | ep_last_sync_date: lastSyncDateTime = null,
10 | ep_last_sync_failed: lastSyncFailed = false,
11 | nonce,
12 | } = window.epDash;
13 |
14 | export { autoIndex, ajaxUrl, indexMeta, isEpio, lastSyncDateTime, lastSyncFailed, nonce };
15 |
--------------------------------------------------------------------------------
/assets/js/sync/hooks.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import apiFetch from '@wordpress/api-fetch';
5 | import { useCallback, useRef } from '@wordpress/element';
6 |
7 | /**
8 | * Internal dependencies.
9 | */
10 | import { ajaxUrl, nonce } from './config';
11 |
12 | /**
13 | * Indexing hook.
14 | *
15 | * Provides methods for indexing, getting indexing status, and cancelling
16 | * indexing. Methods share an abort controller so that requests can
17 | * interrupt eachother to avoid multiple sync requests causing race conditions
18 | * or duplicate output, such as by rapidly pausing and unpausing indexing.
19 | *
20 | * @returns {object} Sync, sync status, and cancel functions.
21 | */
22 | export const useIndex = () => {
23 | const abort = useRef(new AbortController());
24 | const request = useRef(null);
25 |
26 | const sendRequest = useCallback(
27 | /**
28 | * Send AJAX request.
29 | *
30 | * Silently catches abort errors and clears the current request on
31 | * completion.
32 | *
33 | * @param {object} options Request options.
34 | * @throws {Error} Any non-abort errors.
35 | * @returns {Promise} Current request promise.
36 | */
37 | (options) => {
38 | request.current = apiFetch(options).finally(() => {
39 | request.current = null;
40 | });
41 |
42 | return request.current;
43 | },
44 | [],
45 | );
46 |
47 | const cancelIndex = useCallback(
48 | /**
49 | * Send a request to cancel sync.
50 | *
51 | * @returns {Promise} Fetch request promise.
52 | */
53 | async () => {
54 | abort.current.abort();
55 | abort.current = new AbortController();
56 |
57 | const body = new FormData();
58 |
59 | body.append('action', 'ep_cancel_index');
60 | body.append('nonce', nonce);
61 |
62 | const options = {
63 | url: ajaxUrl,
64 | method: 'POST',
65 | body,
66 | signal: abort.current.signal,
67 | };
68 |
69 | return sendRequest(options);
70 | },
71 | [sendRequest],
72 | );
73 |
74 | const index = useCallback(
75 | /**
76 | * Send a request to sync.
77 | *
78 | * @param {boolean} putMapping Whether to put mapping.
79 | * @returns {Promise} Fetch request promise.
80 | */
81 | async (putMapping) => {
82 | abort.current.abort();
83 | abort.current = new AbortController();
84 |
85 | const body = new FormData();
86 |
87 | body.append('action', 'ep_index');
88 | body.append('put_mapping', putMapping ? 1 : 0);
89 | body.append('nonce', nonce);
90 |
91 | const options = {
92 | url: ajaxUrl,
93 | method: 'POST',
94 | body,
95 | signal: abort.current.signal,
96 | };
97 |
98 | return sendRequest(options);
99 | },
100 | [sendRequest],
101 | );
102 |
103 | const indexStatus = useCallback(
104 | /**
105 | * Send a request for CLI sync status.
106 | *
107 | * @returns {Promise} Fetch request promise.
108 | */
109 | async () => {
110 | abort.current.abort();
111 | abort.current = new AbortController();
112 |
113 | const body = new FormData();
114 |
115 | body.append('action', 'ep_index_status');
116 | body.append('nonce', nonce);
117 |
118 | const options = {
119 | url: ajaxUrl,
120 | method: 'POST',
121 | body,
122 | signal: abort.current.signal,
123 | };
124 |
125 | return sendRequest(options);
126 | },
127 | [sendRequest],
128 | );
129 |
130 | return { cancelIndex, index, indexStatus };
131 | };
132 |
--------------------------------------------------------------------------------
/assets/js/sync/utilities.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Clear sync parameter from the URL.
3 | *
4 | * @returns {void}
5 | */
6 | export const clearSyncParam = () => {
7 | window.history.replaceState(
8 | {},
9 | document.title,
10 | document.location.pathname + document.location.search.replace(/&do_sync/, ''),
11 | );
12 | };
13 |
14 | /**
15 | * Get the total number of items from index meta.
16 | *
17 | * @param {object} indexMeta Index meta.
18 | * @returns {number} Number of items.
19 | */
20 | export const getItemsTotalFromIndexMeta = (indexMeta) => {
21 | let itemsTotal = 0;
22 |
23 | if (indexMeta.current_sync_item) {
24 | itemsTotal += indexMeta.current_sync_item.found_items;
25 | }
26 |
27 | itemsTotal = indexMeta.sync_stack.reduce(
28 | (itemsTotal, sync) => itemsTotal + sync.found_items,
29 | itemsTotal,
30 | );
31 |
32 | itemsTotal += indexMeta.totals.failed;
33 | itemsTotal += indexMeta.totals.skipped;
34 | itemsTotal += indexMeta.totals.synced;
35 |
36 | return itemsTotal;
37 | };
38 |
39 | /**
40 | * Get the number of processed items from index meta.
41 | *
42 | * @param {object} indexMeta Index meta.
43 | * @returns {number} Number of processed items.
44 | */
45 | export const getItemsProcessedFromIndexMeta = (indexMeta) => {
46 | let itemsProcessed = 0;
47 |
48 | if (indexMeta.current_sync_item) {
49 | itemsProcessed += indexMeta.current_sync_item.failed;
50 | itemsProcessed += indexMeta.current_sync_item.skipped;
51 | itemsProcessed += indexMeta.current_sync_item.synced;
52 | }
53 |
54 | itemsProcessed += indexMeta.totals.failed;
55 | itemsProcessed += indexMeta.totals.skipped;
56 | itemsProcessed += indexMeta.totals.synced;
57 |
58 | return itemsProcessed;
59 | };
60 |
--------------------------------------------------------------------------------
/assets/js/synonyms/components/SynonymsEditor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { useContext, useEffect, WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependencies.
8 | */
9 | import { State, Dispatch } from '../context';
10 | import AlterativesEditor from './editors/AlternativesEditor';
11 | import SetsEditor from './editors/SetsEditor';
12 | import SolrEditor from './editors/SolrEditor';
13 |
14 | /**
15 | * Synonyms editor component.
16 | *
17 | * @returns {WPElement} Synonyms component
18 | */
19 | const SynonymsEditor = () => {
20 | const state = useContext(State);
21 | const dispatch = useContext(Dispatch);
22 | const { alternatives, sets, isSolrEditable, isSolrVisible, dirty, submit } = state;
23 | const {
24 | pageHeading,
25 | pageDescription,
26 | pageToggleAdvanceText,
27 | pageToggleSimpleText,
28 | alternativesTitle,
29 | alternativesDescription,
30 | setsTitle,
31 | setsDescription,
32 | solrTitle,
33 | solrDescription,
34 | submitText,
35 | } = window.epSynonyms.i18n;
36 |
37 | /**
38 | * Checks if the form is valid.
39 | *
40 | * @param {object} _state Current state.
41 | * @returns {boolean} If the form is valid
42 | */
43 | const isValid = (_state) => {
44 | return [..._state.sets, ..._state.alternatives].reduce((valid, item) => {
45 | return !valid ? valid : item.valid;
46 | }, true);
47 | };
48 |
49 | /**
50 | * Handles submitting the form.
51 | */
52 | const handleSubmit = () => {
53 | if (isSolrEditable) {
54 | dispatch({ type: 'REDUCE_SOLR_TO_STATE' });
55 | }
56 |
57 | dispatch({ type: 'VALIDATE_ALL' });
58 | dispatch({ type: 'REDUCE_STATE_TO_SOLR' });
59 | dispatch({ type: 'SUBMIT' });
60 | };
61 |
62 | /**
63 | * Handle toggling the editor type.
64 | */
65 | const handleToggleAdvance = () => {
66 | if (isSolrEditable) {
67 | dispatch({ type: 'REDUCE_SOLR_TO_STATE' });
68 | } else {
69 | dispatch({ type: 'REDUCE_STATE_TO_SOLR' });
70 | }
71 |
72 | dispatch({ type: 'SET_SOLR_EDITABLE', data: !isSolrEditable });
73 | };
74 |
75 | useEffect(() => {
76 | if (submit && !dirty && isValid(state)) {
77 | document.querySelector('.wrap form').submit();
78 | }
79 | }, [submit, dirty, state]);
80 |
81 | return (
82 | <>
83 |
84 | {pageHeading}{' '}
85 |
88 |
89 | {pageDescription}
90 |
91 | {!isSolrEditable && (
92 | <>
93 |
94 |
{`${setsTitle} (${sets.length})`}
95 |
{setsDescription}
96 |
97 |
98 |
99 |
{`${alternativesTitle} (${alternatives.length})`}
100 |
{alternativesDescription}
101 |
102 |
103 | >
104 | )}
105 |
106 |
107 | {isSolrVisible &&
{solrTitle}
}
108 | {isSolrVisible &&
{solrDescription}
}
109 |
110 |
111 |
112 |
117 |
118 |
119 |
122 |
123 | >
124 | );
125 | };
126 |
127 | export default SynonymsEditor;
128 |
--------------------------------------------------------------------------------
/assets/js/synonyms/components/editors/AlternativeEditor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { useContext, useEffect, useMemo, useRef, useState, WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependencies.
8 | */
9 | import { Dispatch } from '../../context';
10 | import LinkedMultiInput from '../shared/LinkedMultiInput';
11 |
12 | /**
13 | * Alternative Editor
14 | *
15 | * @param {object} props Props.
16 | * @returns {WPElement} AlternativeEditor component
17 | */
18 | const AlternativeEditor = (props) => {
19 | const { id, synonyms, removeAction, updateAction } = props;
20 | const primary = synonyms.find((item) => item.primary);
21 | const [primaryTerm, setPrimaryTerm] = useState(primary ? primary.value : '');
22 | const dispatch = useContext(Dispatch);
23 | const primaryRef = useRef(null);
24 |
25 | /**
26 | * Create primary token
27 | *
28 | * @param {string} label Label.
29 | * @returns {object} Primary token
30 | */
31 | const createPrimaryToken = (label) => {
32 | return {
33 | label,
34 | value: label,
35 | primary: true,
36 | };
37 | };
38 |
39 | /**
40 | * Handle key down.
41 | *
42 | * @param {Event} event Keydown event.
43 | */
44 | const handleKeyDown = (event) => {
45 | switch (event.key) {
46 | case 'Enter':
47 | event.preventDefault();
48 | break;
49 | default:
50 | }
51 | };
52 |
53 | useEffect(() => {
54 | dispatch({
55 | type: 'UPDATE_ALTERNATIVE_PRIMARY',
56 | data: { id, token: createPrimaryToken(primaryTerm) },
57 | });
58 | }, [primaryTerm, id, dispatch]);
59 |
60 | useEffect(() => {
61 | primaryRef.current.focus();
62 | }, [primaryRef]);
63 |
64 | const memoizedSynonyms = useMemo(() => {
65 | return synonyms.filter((item) => !item.primary);
66 | }, [synonyms]);
67 |
68 | return (
69 | <>
70 | setPrimaryTerm(e.target.value)}
74 | value={primaryTerm}
75 | onKeyDown={handleKeyDown}
76 | ref={primaryRef}
77 | />
78 |
84 | >
85 | );
86 | };
87 |
88 | export default AlternativeEditor;
89 |
--------------------------------------------------------------------------------
/assets/js/synonyms/components/editors/AlternativesEditor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { Fragment, useContext, WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependencies.
8 | */
9 | import { Dispatch, State } from '../../context';
10 | import AlternativeEditor from './AlternativeEditor';
11 |
12 | /**
13 | * Synonyms editor component.
14 | *
15 | * @param {object} props Props.
16 | * @param {object[]} props.alternatives Defined alternatives (explicit mappings).
17 | * @returns {WPElement} AlternativesEditor component
18 | */
19 | const AlternativesEditor = ({ alternatives }) => {
20 | const dispatch = useContext(Dispatch);
21 | const state = useContext(State);
22 | const {
23 | alternativesInputHeading,
24 | alternativesPrimaryHeading,
25 | alternativesAddButtonText,
26 | alternativesErrorMessage,
27 | } = window.epSynonyms.i18n;
28 |
29 | /**
30 | * Handle click.
31 | *
32 | * @param {Event} e Event.
33 | */
34 | const handleClick = (e) => {
35 | const [lastItem] = state.alternatives.slice(-1);
36 | if (!alternatives.length || lastItem.synonyms.filter(({ value }) => value.length).length) {
37 | dispatch({ type: 'ADD_ALTERNATIVE' });
38 | }
39 | e.preventDefault();
40 | };
41 |
42 | return (
43 |
44 |
45 |
46 |
47 | {alternativesPrimaryHeading}
48 |
49 |
50 | {alternativesInputHeading}
51 |
52 |
53 |
54 | {alternatives.map((props) => (
55 |
56 |
63 | {!props.valid && (
64 | {alternativesErrorMessage}
65 | )}
66 |
67 | ))}
68 |
71 |
72 |
73 |
74 | );
75 | };
76 |
77 | export default AlternativesEditor;
78 |
--------------------------------------------------------------------------------
/assets/js/synonyms/components/editors/SetsEditor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { Fragment, useContext, WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependencies.
8 | */
9 | import { Dispatch, State } from '../../context';
10 | import LinkedMultiInput from '../shared/LinkedMultiInput';
11 |
12 | /**
13 | * Synonyms editor component.
14 | *
15 | * @param {object} props Props
16 | * @param {object[]} props.sets Defined sets (equivalent synonyms).
17 | * @returns {WPElement} SetsEditor component
18 | */
19 | const SetsEditor = ({ sets }) => {
20 | const dispatch = useContext(Dispatch);
21 | const state = useContext(State);
22 | const { setsInputHeading, setsAddButtonText, setsErrorMessage } = window.epSynonyms.i18n;
23 |
24 | /**
25 | * Handle click.
26 | *
27 | * @param {Event} e Event
28 | */
29 | const handleClick = (e) => {
30 | const [lastSet] = state.sets.slice(-1);
31 | if (!sets.length || lastSet.synonyms.length) {
32 | dispatch({ type: 'ADD_SET' });
33 | }
34 | e.preventDefault();
35 | };
36 |
37 | return (
38 |
39 |
40 |
41 | {setsInputHeading}
42 |
43 |
44 | {sets.map((props) => (
45 |
46 |
47 |
52 |
53 | {!props.valid && (
54 | {setsErrorMessage}
55 | )}
56 |
57 | ))}
58 |
61 |
62 |
63 |
64 | );
65 | };
66 |
67 | export default SetsEditor;
68 |
--------------------------------------------------------------------------------
/assets/js/synonyms/components/editors/SolrEditor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { useContext, WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependencies.
8 | */
9 | import { State, Dispatch } from '../../context';
10 |
11 | /**
12 | * Synonym Inspector
13 | *
14 | * @returns {WPElement} SolrEditor Component
15 | */
16 | const SolrEditor = () => {
17 | const state = useContext(State);
18 | const dispatch = useContext(Dispatch);
19 | const { alternatives, isSolrEditable, isSolrVisible, sets, solr } = state;
20 | const {
21 | synonymsTextareaInputName,
22 | solrInputHeading,
23 | solrAlternativesErrorMessage,
24 | solrSetsErrorMessage,
25 | } = window.epSynonyms.i18n;
26 |
27 | return (
28 |
29 |
30 |
31 | {solrInputHeading}
32 |
33 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default SolrEditor;
62 |
--------------------------------------------------------------------------------
/assets/js/synonyms/components/shared/LinkedMultiInput.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { FormTokenField } from '@wordpress/components';
5 | import { useContext, WPElement } from '@wordpress/element';
6 |
7 | /**
8 | * Internal dependencies.
9 | */
10 | import { Dispatch } from '../../context';
11 |
12 | /**
13 | * Linked MultiInput
14 | *
15 | * @param {object} props Props.
16 | * @param {string} props.id Set/Alternative id.
17 | * @param {object[]} props.synonyms Array of synonyms.
18 | * @param {string} props.removeAction Name of action to dispatch on remove.
19 | * @param {string} props.updateAction Name of action to dispatch on update.
20 | * @returns {WPElement} LinkedMultiInput component
21 | */
22 | const LinkedMultiInput = ({ id, synonyms, removeAction, updateAction }) => {
23 | const dispatch = useContext(Dispatch);
24 | const { removeItemText } = window.epSynonyms.i18n;
25 |
26 | /**
27 | * Handle change to tokens.
28 | *
29 | * @param {string[]} value Array of tokens.
30 | */
31 | const handleChange = (value) => {
32 | const tokens = value.map((v) => {
33 | const token = {
34 | label: v,
35 | value: v,
36 | primary: false,
37 | };
38 |
39 | return token;
40 | });
41 |
42 | dispatch({ type: updateAction, data: { id, tokens } });
43 | };
44 |
45 | /**
46 | * Handle clearing the synonym.
47 | */
48 | const handleClear = () => {
49 | dispatch({ type: removeAction, data: id });
50 | };
51 |
52 | return (
53 | <>
54 | s.value)}
59 | />
60 |
64 | >
65 | );
66 | };
67 |
68 | export default LinkedMultiInput;
69 |
--------------------------------------------------------------------------------
/assets/js/synonyms/context.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { createContext, useReducer, WPElement } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependencies.
8 | */
9 | import { editorReducer, initialState } from './reducers/editorReducer';
10 |
11 | const State = createContext();
12 | const Dispatch = createContext();
13 |
14 | /**
15 | * App Context.
16 | *
17 | * @param {object} props Props.
18 | * @returns {WPElement} AppContext component
19 | */
20 | const AppContext = (props) => {
21 | const { children } = props;
22 | const [state, dispatch] = useReducer(editorReducer, initialState);
23 |
24 | return (
25 |
26 | {children}
27 |
28 | );
29 | };
30 |
31 | export { AppContext, State, Dispatch };
32 |
--------------------------------------------------------------------------------
/assets/js/synonyms/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { render } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependencies.
8 | */
9 | import { AppContext } from './context';
10 | import SynonymsEditor from './components/SynonymsEditor';
11 |
12 | const SELECTOR = '#synonym-root';
13 |
14 | /**
15 | * Get Root.
16 | *
17 | * @returns {Element|false} Root element
18 | */
19 | const getRoot = () => document.querySelector(SELECTOR) || false;
20 |
21 | render(
22 |
23 |
24 | ,
25 | getRoot(),
26 | );
27 |
--------------------------------------------------------------------------------
/assets/js/synonyms/reducers/editorReducer.js:
--------------------------------------------------------------------------------
1 | import { reduceSolrToState, reduceStateToSolr, mapEntry } from '../utils';
2 |
3 | /**
4 | * The synonym editor reducer.
5 | */
6 |
7 | const { alternatives, sets, initialMode } = window.epSynonyms.data;
8 | const mappedSets = sets ? sets.map(mapEntry) : [mapEntry()];
9 | const mappedAlternatives = alternatives ? alternatives.map(mapEntry) : [mapEntry()];
10 | const initialState = {
11 | isSolrEditable: initialMode === 'advanced',
12 | isSolrVisible: initialMode === 'advanced',
13 | alternatives: mappedAlternatives,
14 | sets: mappedSets,
15 | solr: reduceStateToSolr({ sets: mappedSets, alternatives: mappedAlternatives }),
16 | dirty: false,
17 | submit: false,
18 | };
19 |
20 | /**
21 | * editorReducer
22 | *
23 | * @param {object} state Current state.
24 | * @param {object} action The action.
25 | * @returns {object} New state.
26 | */
27 | const editorReducer = (state, action) => {
28 | switch (action.type) {
29 | case 'ADD_SET':
30 | return {
31 | ...state,
32 | sets: [...state.sets, mapEntry()],
33 | dirty: true,
34 | };
35 | case 'UPDATE_SET':
36 | return {
37 | ...state,
38 | sets: state.sets.map((entry) => {
39 | if (entry.id !== action.data.id) {
40 | return entry;
41 | }
42 | return mapEntry(action.data.tokens, action.data.id);
43 | }),
44 | dirty: true,
45 | };
46 | case 'REMOVE_SET':
47 | return {
48 | ...state,
49 | sets: state.sets.filter(({ id }) => id !== action.data),
50 | dirty: true,
51 | };
52 | case 'ADD_ALTERNATIVE':
53 | return {
54 | ...state,
55 | alternatives: [...state.alternatives, mapEntry()],
56 | dirty: true,
57 | };
58 | case 'UPDATE_ALTERNATIVE':
59 | return {
60 | ...state,
61 | alternatives: [
62 | ...state.alternatives.map((entry) => {
63 | if (entry.id !== action.data.id) {
64 | return entry;
65 | }
66 | return mapEntry(
67 | [...action.data.tokens, ...entry.synonyms.filter((t) => t.primary)],
68 | action.data.id,
69 | );
70 | }),
71 | ],
72 | dirty: true,
73 | };
74 | case 'UPDATE_ALTERNATIVE_PRIMARY':
75 | return {
76 | ...state,
77 | alternatives: [
78 | ...state.alternatives.map((entry) => {
79 | if (entry.id !== action.data.id) {
80 | return entry;
81 | }
82 | return mapEntry(
83 | [action.data.token, ...entry.synonyms.filter((t) => !t.primary)],
84 | action.data.id,
85 | );
86 | }),
87 | ],
88 | dirty: true,
89 | };
90 | case 'REMOVE_ALTERNATIVE':
91 | return {
92 | ...state,
93 | alternatives: state.alternatives.filter(({ id }) => id !== action.data),
94 | dirty: true,
95 | };
96 | case 'SET_SOLR_EDITABLE':
97 | return {
98 | ...state,
99 | isSolrEditable: !!action.data,
100 | isSolrVisible: !!action.data,
101 | };
102 | case 'UPDATE_SOLR':
103 | return {
104 | ...state,
105 | solr: action.data,
106 | dirty: true,
107 | };
108 | case 'REDUCE_SOLR_TO_STATE':
109 | return {
110 | ...reduceSolrToState(state.solr, state),
111 | dirty: true,
112 | };
113 | case 'REDUCE_STATE_TO_SOLR':
114 | return {
115 | ...state,
116 | solr: reduceStateToSolr(state),
117 | };
118 | case 'VALIDATE_ALL':
119 | return {
120 | ...state,
121 | sets: state.sets.map((set) => ({
122 | ...set,
123 | valid: set.synonyms.length > 1,
124 | })),
125 | alternatives: state.alternatives.map((alternative) => ({
126 | ...alternative,
127 | valid:
128 | alternative.synonyms.length > 1 &&
129 | !!alternative.synonyms.filter(
130 | ({ primary, value }) => primary && value.length,
131 | ).length,
132 | })),
133 | dirty: false,
134 | };
135 | case 'SUBMIT':
136 | return {
137 | ...state,
138 | submit: true,
139 | };
140 | default:
141 | return state;
142 | }
143 | };
144 |
145 | export { editorReducer, initialState };
146 |
--------------------------------------------------------------------------------
/assets/js/synonyms/utils.js:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from 'uuid';
2 |
3 | /**
4 | * Generate universally unique identifier.
5 | *
6 | * @returns {string} A universally unique identifier
7 | */
8 | const uuid = () => {
9 | return uuidv4();
10 | };
11 |
12 | /**
13 | * Map entry
14 | *
15 | * @param {Array} synonyms Array of synonyms.
16 | * @param {string} id The id, default generated by the application.
17 | * @returns {object} Map entry
18 | */
19 | const mapEntry = (synonyms = [], id = '') => {
20 | return {
21 | id: id.length ? id : uuidv4(),
22 | synonyms,
23 | valid: true,
24 | };
25 | };
26 |
27 | /**
28 | * Reduce state to Solr spec.
29 | *
30 | * @param {object} state Current state.
31 | * @param {object[]} state.sets Array of synonym sets.
32 | * @param {object[]} state.alternatives Array of alternative sets.
33 | * @returns {string} new state
34 | */
35 | const reduceStateToSolr = ({ sets, alternatives }) => {
36 | const synonymsList = [];
37 |
38 | // Handle sets.
39 | synonymsList.push('# Defined sets ( equivalent synonyms).');
40 | synonymsList.push(...sets.map(({ synonyms }) => synonyms.map(({ value }) => value).join(', ')));
41 |
42 | // Handle alternatives.
43 | synonymsList.push('\r');
44 | synonymsList.push('# Defined alternatives (explicit mappings).');
45 | synonymsList.push(
46 | ...alternatives.map((alternative) =>
47 | alternative.synonyms.find((item) => item.primary && item.value.length)
48 | ? alternative.synonyms
49 | .find((item) => item.primary)
50 | .value.concat(' => ')
51 | .concat(
52 | alternative.synonyms
53 | .filter((i) => !i.primary)
54 | .map(({ value }) => value)
55 | .join(', '),
56 | )
57 | : false,
58 | ),
59 | );
60 |
61 | return synonymsList.filter(Boolean).join('\n');
62 | };
63 |
64 | /**
65 | * Reduce Solr text file to State.
66 | *
67 | * @param {string} solr A string in the Solr parseable synonym format.
68 | * @param {object} currentState The current sate.
69 | * @returns {object} State
70 | */
71 | const reduceSolrToState = (solr, currentState) => {
72 | /**
73 | * Format token.
74 | *
75 | * @param {string} value The value.
76 | * @param {boolean} primary Whether it's a primary.
77 | * @returns {object} Formated token
78 | */
79 | const formatToken = (value, primary = false) => {
80 | return {
81 | label: value,
82 | value,
83 | primary,
84 | };
85 | };
86 |
87 | return {
88 | ...currentState,
89 | ...solr.split(/\r?\n/).reduce(
90 | (newState, line) => {
91 | if (line.indexOf('#') === 0 || !line.trim().length) {
92 | return newState;
93 | }
94 |
95 | if (line.indexOf('=>') !== -1) {
96 | const parts = line.split('=>');
97 | return {
98 | ...newState,
99 | alternatives: [
100 | ...newState.alternatives,
101 | mapEntry([
102 | formatToken(parts[0].trim(), true),
103 | ...parts[1]
104 | .split(',')
105 | .filter((v) => v.trim())
106 | .map((token) => formatToken(token.trim())),
107 | ]),
108 | ],
109 | };
110 | }
111 |
112 | return {
113 | ...newState,
114 | sets: [
115 | ...newState.sets,
116 | mapEntry([
117 | ...line
118 | .split(',')
119 | .filter((v) => v.trim())
120 | .map((token) => formatToken(token.trim())),
121 | ]),
122 | ],
123 | };
124 | },
125 | { alternatives: [], sets: [] },
126 | ),
127 | };
128 | };
129 |
130 | export { reduceStateToSolr, reduceSolrToState, uuid, mapEntry };
131 |
--------------------------------------------------------------------------------
/assets/js/weighting.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import domReady from '@wordpress/dom-ready';
5 |
6 | /**
7 | * Initialize.
8 | *
9 | * @returns {void}
10 | */
11 | const init = () => {
12 | /**
13 | * Update the value in the range input label.
14 | *
15 | * @param {Element} el Range input element.
16 | */
17 | const updateSliderValue = (el) => {
18 | el.labels[0].querySelector('.weighting-value').textContent = el.disabled ? '0' : el.value;
19 | };
20 |
21 | /**
22 | * Handle range slider input.
23 | *
24 | * @param {Event} event Input event.
25 | */
26 | const onSliderInput = (event) => {
27 | updateSliderValue(event.currentTarget);
28 | };
29 |
30 | /**
31 | * Handle checkbox change.
32 | *
33 | * @param {Event} event Change event.
34 | */
35 | const onCheckboxChange = (event) => {
36 | const el = event.currentTarget.closest('fieldset').querySelector('input[type="range"]');
37 |
38 | el.disabled = !event.currentTarget.checked;
39 |
40 | updateSliderValue(el);
41 | };
42 |
43 | /**
44 | * Bind events.
45 | */
46 | const sliders = document.querySelectorAll('.weighting-settings input[type=range]');
47 |
48 | for (const slider of sliders) {
49 | slider.addEventListener('input', onSliderInput);
50 | }
51 |
52 | const checkboxes = document.querySelectorAll(
53 | '.weighting-settings .searchable input[type=checkbox]',
54 | );
55 |
56 | for (const checkbox of checkboxes) {
57 | checkbox.addEventListener('change', onCheckboxChange);
58 | }
59 | };
60 |
61 | /**
62 | * Initialize.
63 | */
64 | domReady(init);
65 |
--------------------------------------------------------------------------------
/dist/css/autosuggest-styles.min.asset.php:
--------------------------------------------------------------------------------
1 | array(), 'version' => '26e3c1cd915b73e742ab41d02cd86563');
--------------------------------------------------------------------------------
/dist/css/autosuggest-styles.min.css:
--------------------------------------------------------------------------------
1 | .ep-autosuggest-container{position:relative}.ep-autosuggest-container .ep-autosuggest{background:#fff;border:1px solid #ccc;-webkit-box-shadow:0 2px 4px rgba(0,0,0,.2);box-shadow:0 2px 4px rgba(0,0,0,.2);display:none;position:absolute;width:100%;z-index:200}.ep-autosuggest-container .ep-autosuggest>ul{list-style:none;margin:0!important}.ep-autosuggest-container .ep-autosuggest>ul>li{font-family:sans-serif}.ep-autosuggest-container .ep-autosuggest>ul>li>a.autosuggest-link{color:#000;cursor:pointer;display:block;padding:2px 10px}.ep-autosuggest-container .ep-autosuggest>ul>li>a.autosuggest-link:active,.ep-autosuggest-container .ep-autosuggest>ul>li>a.autosuggest-link:hover{background-color:#eee;text-decoration:none}.ep-autosuggest-container .selected{background-color:#eee;text-decoration:none}
2 |
--------------------------------------------------------------------------------
/dist/css/comments-styles.min.asset.php:
--------------------------------------------------------------------------------
1 | array(), 'version' => 'd5cf29bd60b0dd4480724d7b606e458f');
--------------------------------------------------------------------------------
/dist/css/comments-styles.min.css:
--------------------------------------------------------------------------------
1 | .ep-widget-search-comments-results{list-style-type:none;margin-left:0}.ep-widget-search-comments-result-item,.ep-widget-search-comments-result-item-not-found{margin-left:0}.ep-widget-search-comments-result-item.selected a{border:2px dotted #171923}
2 |
--------------------------------------------------------------------------------
/dist/css/dashboard-styles.min.asset.php:
--------------------------------------------------------------------------------
1 | array(), 'version' => '71469307d99d88ac231f099cc82fa522');
--------------------------------------------------------------------------------
/dist/css/facets-admin-styles.min.asset.php:
--------------------------------------------------------------------------------
1 | array(), 'version' => 'f8e36dd4c8462c8408ca6f064bb7d277');
--------------------------------------------------------------------------------
/dist/css/facets-admin-styles.min.css:
--------------------------------------------------------------------------------
1 | .widget-ep-facet label{margin-right:5px}
2 |
--------------------------------------------------------------------------------
/dist/css/facets-styles.min.asset.php:
--------------------------------------------------------------------------------
1 | array(), 'version' => 'be774ee7a4f194dfe65a980bdb745f6a');
--------------------------------------------------------------------------------
/dist/css/facets-styles.min.css:
--------------------------------------------------------------------------------
1 | .widget_ep-facet input[type=search],.wp-block-elasticpress-facet input[type=search]{margin-bottom:1rem}.widget_ep-facet .searchable .inner,.wp-block-elasticpress-facet .searchable .inner{max-height:20em;overflow:scroll}.widget_ep-facet .term.hide,.wp-block-elasticpress-facet .term.hide{display:none}.widget_ep-facet .empty-term,.wp-block-elasticpress-facet .empty-term{opacity:.5;position:relative}.widget_ep-facet .empty-term:after,.wp-block-elasticpress-facet .empty-term:after{bottom:0;content:" ";display:block;left:0;position:absolute;right:0;top:0;width:100%;z-index:2}.widget_ep-facet .level-1,.wp-block-elasticpress-facet .level-1{padding-left:20px}.widget_ep-facet .level-2,.wp-block-elasticpress-facet .level-2{padding-left:40px}.widget_ep-facet .level-3,.wp-block-elasticpress-facet .level-3{padding-left:60px}.widget_ep-facet .level-4,.wp-block-elasticpress-facet .level-4{padding-left:5pc}.widget_ep-facet .level-5,.wp-block-elasticpress-facet .level-5{padding-left:75pt}.widget_ep-facet input[disabled],.wp-block-elasticpress-facet input[disabled]{cursor:pointer;opacity:1}.widget_ep-facet .term a,.wp-block-elasticpress-facet .term a{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;position:relative}.widget_ep-facet .term a:hover .ep-checkbox,.wp-block-elasticpress-facet .term a:hover .ep-checkbox{background-color:#ccc}.ep-checkbox{-webkit-box-align:center;-ms-flex-align:center;-ms-flex-negative:0;-webkit-box-pack:center;-ms-flex-pack:center;align-items:center;background-color:#eee;display:-webkit-box;display:-ms-flexbox;display:flex;flex-shrink:0;height:1em;justify-content:center;margin-right:.25em;width:1em}.ep-checkbox:after{border:solid #fff;border-width:0 .125em .125em 0;content:"";display:none;height:.5em;-webkit-transform:rotate(45deg);transform:rotate(45deg);width:.25em}.ep-checkbox.checked{background-color:#5e5e5e}.ep-checkbox.checked:after{display:block}
2 |
--------------------------------------------------------------------------------
/dist/css/highlighting-styles.min.asset.php:
--------------------------------------------------------------------------------
1 | array(), 'version' => '48ac6df2f0406f2e2d5d4ea06f5759b5');
--------------------------------------------------------------------------------
/dist/css/highlighting-styles.min.css:
--------------------------------------------------------------------------------
1 | .ep-highlight{background-color:transparent;font-style:italic;font-weight:700}
2 |
--------------------------------------------------------------------------------
/dist/css/instant-results-styles.min.asset.php:
--------------------------------------------------------------------------------
1 | array(), 'version' => '9e7642cfd2537aa60b4d59e8a75cbb59');
--------------------------------------------------------------------------------
/dist/css/ordering-styles.min.asset.php:
--------------------------------------------------------------------------------
1 | array(), 'version' => '1310e7d723dab4632cfce851ad418485');
--------------------------------------------------------------------------------
/dist/css/ordering-styles.min.css:
--------------------------------------------------------------------------------
1 | #ep-ordering{border-left-width:0;border-right-width:0}#ep-ordering .inside{background-color:#f1f1f1;margin:0;padding:0}#ep-ordering .loading,#ep-ordering .pointer-search,#ep-ordering .pointers{background-color:#fff;border-left:1px solid #eee;border-right:1px solid #eee}#ep-ordering .new-post{background:#fff;padding:0 1em}#ep-ordering .loading{padding:1em}#ep-ordering .loading .spinner{float:left;margin-left:0;margin-top:0}#ep-ordering .pointer-type{border:2px solid #0073aa;border-radius:2px;color:#0073aa;display:inline-block;font-size:.75em;font-weight:700;margin-right:8px;padding:1px 2px}#ep-ordering .pointers .pointer,#ep-ordering .pointers .post{padding:1em}#ep-ordering .pointers .pointer:nth-child(odd),#ep-ordering .pointers .post:nth-child(odd){background-color:#f9f9f9}#ep-ordering .pointers .title{color:#0073aa}#ep-ordering .pointers .pointer-actions{float:right}#ep-ordering .pointers .pointer-actions .handle{cursor:move}#ep-ordering .pointers .pointer-actions .delete-pointer{margin-left:10px}#ep-ordering .pointers .next-page-notice{background-color:#fdeeca;padding:1em 0;text-align:center}#ep-ordering .legend{background:#fff;border-bottom:1px solid #eee;padding:1em 0;text-align:center}#ep-ordering .legend-item{display:inline-block;font-size:.875em;font-style:italic;margin:0 .5em}#ep-ordering .pointer-search{margin-top:2em}#ep-ordering .pointer-search .no-results{padding:1em}#ep-ordering .pointer-search .section-title{border-bottom:1px solid #eee;border-top:1px solid #eee;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.04);box-shadow:0 1px 1px rgba(0,0,0,.04);font-weight:700}#ep-ordering .pointer-search .search-wrapper{padding:1em 0}#ep-ordering .pointer-search .input-wrap{padding:0 1em}#ep-ordering .pointer-search .search-pointers{font-size:18px;height:1.7em;line-height:100%;padding:3px 8px}#ep-ordering .pointer-search .pointer-results{padding:1em 0 0}#ep-ordering .pointer-search .pointer-result{padding:10px 2em}#ep-ordering .pointer-search .pointer-result .dashicons{float:right}#ep-ordering .pointer-search .pointer-result:hover{background-color:#f9f9f9}#ep-ordering .add-pointer,#ep-ordering .delete-pointer{cursor:pointer}
2 |
--------------------------------------------------------------------------------
/dist/css/related-posts-block-styles.min.asset.php:
--------------------------------------------------------------------------------
1 | array(), 'version' => '57ed3b8acc523fe9b9e9a53dae101978');
--------------------------------------------------------------------------------
/dist/css/related-posts-block-styles.min.css:
--------------------------------------------------------------------------------
1 | .editor-styles-wrapper .wp-block-elasticpress-related-posts ul,.wp-block-elasticpress-related-posts ul{list-style-type:none;padding:0}.editor-styles-wrapper .wp-block-elasticpress-related-posts ul li a>div{display:inline}
2 |
--------------------------------------------------------------------------------
/dist/css/sync-styles.min.asset.php:
--------------------------------------------------------------------------------
1 | array(), 'version' => '5f82e66765bbe534473746cd12df6189');
--------------------------------------------------------------------------------
/dist/css/synonyms-styles.min.asset.php:
--------------------------------------------------------------------------------
1 | array(), 'version' => 'e1b6c3a9b9ef4fa92b23571f6661577b');
--------------------------------------------------------------------------------
/dist/css/synonyms-styles.min.css:
--------------------------------------------------------------------------------
1 | :root{--ep-synonyms-input-border-color:#ccc;--ep-synonyms-color-black:#1a1e24;--ep-synonyms-color-error:#b52727}html.wp-toolbar{background:transparent}#synonym-root .page-title-action{margin-left:10px}#synonym-root .postbox .hndle{cursor:default}#synonym-root h2{color:#1a1e24;color:var(--ep-synonyms-color-black)}.synonym-editor .postbox{width:100%}.synonym-editor .postbox>.hndle{display:-webkit-box;display:-ms-flexbox;display:flex}.synonym-alternative-editor,.synonym-set-editor{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;display:-webkit-box;display:-ms-flexbox;display:flex}.synonym-alternative-editor .components-form-token-field,.synonym-set-editor .components-form-token-field{-webkit-box-flex:1;-ms-flex:1;flex:1;margin-bottom:.5em}.synonym-alternative-editor .components-form-token-field__label,.synonym-set-editor .components-form-token-field__label{display:none}.synonym-alternative-editor .components-form-token-field__input-container,.synonym-set-editor .components-form-token-field__input-container{border-color:#ccc;border-color:var(--ep-synonyms-input-border-color);-webkit-box-sizing:border-box;box-sizing:border-box}.synonym-alternative-editor .components-form-token-field__token-text,.synonym-set-editor .components-form-token-field__token-text{padding-bottom:1px;padding-top:1px}@media screen and (max-width:782px){.synonym-alternative-editor .components-form-token-field__token-text,.synonym-set-editor .components-form-token-field__token-text{padding-bottom:3px;padding-top:3px}}.synonym-alternative-editor input.components-form-token-field__input[type=text],.synonym-set-editor input.components-form-token-field__input[type=text]{margin-bottom:2px;margin-top:2px}@media screen and (max-width:782px){.synonym-alternative-editor input.components-form-token-field__input[type=text],.synonym-set-editor input.components-form-token-field__input[type=text]{min-height:30px}}.synonym-alternative-editor .components-form-token-field__help,.synonym-set-editor .components-form-token-field__help{margin-top:0}input[type=text].ep-synonyms__input{border:1px solid #ccc;border:1px solid var(--ep-synonyms-input-border-color);margin-bottom:.5em;margin-right:1em;min-height:36px;width:10em}@media screen and (max-width:782px){input[type=text].ep-synonyms__input{min-height:40px}}.synonym-alternatives__primary-heading{width:11em}.synonym-alternatives__input-heading{-webkit-box-flex:1;-ms-flex:1;flex:1}button.synonym__remove{background-color:transparent;border:none;color:#b52727;color:var(--ep-synonyms-color-error);cursor:pointer;margin:0 0 0 10px;min-height:36px;padding:0}@media screen and (max-width:782px){button.synonym__remove{min-height:40px}}button.synonym__remove .dashicons-dismiss{margin:-2px 2px 0 0}.synonym__validation:before{-ms-flex-preferred-size:100%;content:"";flex-basis:100%;height:0}.synonym-solr-editor__validation p,.synonym__validation{color:#b52727;color:var(--ep-synonyms-color-error);font-style:italic}.synonym__validation{margin:0 0 .625em .5em}.synonym-btn-group button.button{margin-right:.625em}
2 |
--------------------------------------------------------------------------------
/dist/js/autosuggest-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('wp-polyfill'), 'version' => '5c609d7ba064036a58c72be668e0515d');
--------------------------------------------------------------------------------
/dist/js/comments-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('wp-polyfill'), 'version' => '1e0e945692ffaa2ddd6377c7ff11fea6');
--------------------------------------------------------------------------------
/dist/js/comments-script.min.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";const e=(e,t)=>{for(;(e=e.parentElement)&&!e.classList.contains(t););return e},t=document.querySelectorAll(".ep-widget-search-comments");let n;t.forEach((e=>{const t=document.createElement("input");t.setAttribute("autocomplete","off"),t.setAttribute("type","search"),t.setAttribute("class","ep-widget-search-comments-input");const n=document.createElement("ul");n.setAttribute("class","ep-widget-search-comments-results"),e.appendChild(t),e.appendChild(n)}));const s=[38,40,13];function i(t){n=void 0;const s=e(t,"ep-widget-search-comments").querySelector(".ep-widget-search-comments-results");for(;s.firstChild;)s.removeChild(s.firstChild)}function o(e){const t=window.epc.minimumLengthToSearch||2;return e?.value?.trim().length>=t}function c(t,n){const s=e(n,"ep-widget-search-comments");t?s.classList.add("ep-widget-search-comments-is-loading"):s.classList.remove("ep-widget-search-comments-is-loading")}const r=((e,t)=>{let n=null;return function(...s){const i=this;window.clearTimeout(n),n=window.setTimeout((()=>{e.apply(i,s)}),t)}})((function(t){if(o(t)){const n=e(t,"ep-widget-search-comments").querySelector("#ep-widget-search-comments-post-type"),s=n?.value?`&post_type=${n.value.trim()}`:"";return c(!0,t),fetch(`${window.epc.restApiEndpoint}?s=${t.value.trim()}${s}`).then((e=>{if(!e.ok)throw e;return e.json()})).then((n=>{0===Object.keys(n).length?t.value.trim()?(t=>{const n=e(t,"ep-widget-search-comments").querySelector(".ep-widget-search-comments-results");let s=``;"undefined"!==typeof window.epCommentWidgetItemNotFoundHTMLFilter&&(s=window.epCommentWidgetItemNotFoundHTMLFilter(s,window.epc.noResultsFoundText,t.value)),n.innerHTML=s})(t):i(t):((t,n)=>{let s="",i="";Object.keys(t).forEach(((e,o)=>{t[e]?.content&&t[e]?.link&&(i=`\n\t\t\t\t\n\t\t\t`,"undefined"!==typeof window.epCommentWidgetItemHTMLFilter&&(i=window.epCommentWidgetItemHTMLFilter(i,t[e],o,n.value)),s+=i)}));const o=e(n,"ep-widget-search-comments").querySelector(".ep-widget-search-comments-results");"undefined"!==typeof window.epCommentWidgetItemsHTMLFilter&&(s=window.epCommentWidgetItemsHTMLFilter(s,n.value)),o.innerHTML=s})(n,t)})).catch((()=>{i(t)})).finally((()=>{c(!1,t)}))}return!1}),500),d=t=>{t.preventDefault();const{target:c,key:d,keyCode:l}=t;if("Escape"===d||"Esc"===d||27===l)return i(c),void c.setAttribute("aria-expanded",!1);s.includes(l)&&""!==c.value?(t=>{if(!s.includes(t.keyCode))return;const i=e(t.target,"ep-widget-search-comments").querySelector(".ep-widget-search-comments-results"),o=i.querySelectorAll(".ep-widget-search-comments-result-item").length,c=i.children,r=n;switch(t.keyCode){case 38:n=n-1<0||"undefined"===typeof n?o-1:n-1;break;case 40:"undefined"===typeof n||n+1>o-1?n=0:n+=1;break;case 13:if(c[n]?.classList.contains("selected")||1===o){const e=n||0;if(c[e]){const t=c[e].querySelector("a")?.getAttribute("href");window.location.href=t}}}"number"===typeof r&&(c[r].classList.remove("selected"),c[r].setAttribute("aria-selected","false")),c[n]?.classList.add("selected"),c[n]?.setAttribute("aria-selected","true")})(t):o(c)?r(c):i(c)};t.forEach((e=>{const t=e.querySelector(".ep-widget-search-comments-input");t.addEventListener("keyup",d),t.addEventListener("keydown",(e=>{38===e.keyCode&&e.preventDefault()})),t.addEventListener("blur",(function(){setTimeout((()=>i(t)),200)}))}))}();
--------------------------------------------------------------------------------
/dist/js/dashboard-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('wp-i18n', 'wp-polyfill'), 'version' => '0fe1d3b8e3b503af840f3315a5007b5d');
--------------------------------------------------------------------------------
/dist/js/facets-block-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-components', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => '3b1c7b5d6ccc4bcd15bd0ae2816b1138');
--------------------------------------------------------------------------------
/dist/js/facets-block-script.min.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";var e={5251:function(e,t,s){var r=s(9196),o=60103;if(60107,"function"===typeof Symbol&&Symbol.for){var n=Symbol.for;o=n("react.element"),n("react.fragment")}var a=r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l=Object.prototype.hasOwnProperty,c={key:!0,ref:!0,__self:!0,__source:!0};function i(e,t,s){var r,n={},i=null,p=null;for(r in void 0!==s&&(i=""+s),void 0!==t.key&&(i=""+t.key),void 0!==t.ref&&(p=t.ref),t)l.call(t,r)&&!c.hasOwnProperty(r)&&(n[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps)void 0===n[r]&&(n[r]=t[r]);return{$$typeof:o,type:e,key:i,ref:p,props:n,_owner:a.current}}t.jsx=i,t.jsxs=i},5893:function(e,t,s){e.exports=s(5251)},9196:function(e){e.exports=window.React}},t={};function s(r){var o=t[r];if(void 0!==o)return o.exports;var n=t[r]={exports:{}};return e[r](n,n.exports,s),n.exports}s.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return s.d(t,{a:t}),t},s.d=function(e,t){for(var r in t)s.o(t,r)&&!s.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){var e=window.wp.blockEditor,t=window.wp.components,r=window.wp.element,o=window.wp.apiFetch,n=s.n(o),a=window.wp.i18n,l=s(5893);var c=s=>{const{attributes:o,setAttributes:c}=s,[i,p]=(0,r.useState)({}),[u,d]=(0,r.useState)(""),[f,_]=(0,r.useState)(!1),{facet:y,orderby:b,order:v}=o,w=(0,e.useBlockProps)(),m=(0,r.useCallback)((async()=>{const e=await n()({path:"/elasticpress/v1/facets/taxonomies"});p(e)}),[p]);return(0,r.useEffect)(m,[m]),(0,r.useEffect)((()=>{_(!0);const e=new URLSearchParams({facet:y,orderby:b,order:v});n()({path:`/elasticpress/v1/facets/block-preview?${e}`}).then((e=>d(e))).finally((()=>_(!1)))}),[y,b,v]),(0,l.jsxs)(r.Fragment,{children:[(0,l.jsx)(e.InspectorControls,{children:(0,l.jsxs)(t.PanelBody,{title:(0,a.__)("Facet Settings","elasticpress"),children:[(0,l.jsx)(t.SelectControl,{label:(0,a.__)("Taxonomy","elasticpress"),value:y,options:[...Object.entries(i).map((([e,t])=>({label:t.label,value:e})))],onChange:e=>c({facet:e})}),(0,l.jsx)(t.RadioControl,{label:(0,a.__)("Order By","elasticpress"),help:(0,a.__)("The field used to order available options","elasticpress"),selected:b,options:[{label:(0,a.__)("Count","elasticpress"),value:"count"},{label:(0,a.__)("Name","elasticpress"),value:"name"}],onChange:e=>c({orderby:e})}),(0,l.jsx)(t.RadioControl,{label:(0,a.__)("Order","elasticpress"),selected:v,options:[{label:(0,a.__)("ASC","elasticpress"),value:"asc"},{label:(0,a.__)("DESC","elasticpress"),value:"desc"}],onChange:e=>c({order:e})})]})}),(0,l.jsxs)("div",{...w,children:[f&&(0,l.jsx)(t.Placeholder,{children:(0,l.jsx)(t.Spinner,{})}),!f&&(0,l.jsx)("div",{dangerouslySetInnerHTML:{__html:u}})]})]})},i=JSON.parse('{"$schema":"https://schemas.wp.org/trunk/block.json","apiVersion":2,"title":"Facet (ElasticPress)","textdomain":"elasticpress","name":"elasticpress/facet","icon":"feedback","category":"widgets","attributes":{"facet":{"type":"string","default":""},"orderby":{"type":"string","default":"count","enum":["count","name"]},"order":{"type":"string","default":"desc","enum":["desc","asc"]}},"supports":{"html":false},"editorScript":"file:/../../../../dist/js/facets-block-script.min.js","style":"file:/../../../../dist/css/facets-styles.min.css"}');const{registerBlockType:p}=wp.blocks;p(i,{edit:c,save:()=>{}})}()}();
--------------------------------------------------------------------------------
/dist/js/facets-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('wp-polyfill'), 'version' => '0ed518d455727a8be493af1cdee55711');
--------------------------------------------------------------------------------
/dist/js/facets-script.min.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";const e=(e,t)=>{let r=null;return function(...c){const s=this;window.clearTimeout(r),r=window.setTimeout((()=>{e.apply(s,c)}),t)}};document.querySelectorAll(".widget_ep-facet, .wp-block-elasticpress-facet").forEach((t=>{if(!t.querySelector(".facet-search"))return;const r=t.querySelector(".terms");t.querySelector(".facet-search").addEventListener("keyup",e((e=>{13!==e.keyCode&&((e,t)=>{const{target:r}=e,c=r.value.toLowerCase();t.querySelectorAll(".term").forEach((e=>{const t=e.getAttribute("data-term-slug");e.getAttribute("data-term-name").includes(c)||t.includes(c)?e.classList.remove("hide"):e.classList.add("hide")}))})(e,r)}),200))}))}();
--------------------------------------------------------------------------------
/dist/js/instant-results-admin-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('react', 'wp-components', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => 'f44a3bc2ae3235e2bd24c5160052fffc');
--------------------------------------------------------------------------------
/dist/js/instant-results-admin-script.min.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";var e={5251:function(e,t,n){var o=n(9196),r=60103;if(t.Fragment=60107,"function"===typeof Symbol&&Symbol.for){var a=Symbol.for;r=a("react.element"),t.Fragment=a("react.fragment")}var s=o.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l=Object.prototype.hasOwnProperty,i={key:!0,ref:!0,__self:!0,__source:!0};function u(e,t,n){var o,a={},u=null,f=null;for(o in void 0!==n&&(u=""+n),void 0!==t.key&&(u=""+t.key),void 0!==t.ref&&(f=t.ref),t)l.call(t,o)&&!i.hasOwnProperty(o)&&(a[o]=t[o]);if(e&&e.defaultProps)for(o in t=e.defaultProps)void 0===a[o]&&(a[o]=t[o]);return{$$typeof:r,type:e,key:u,ref:f,props:a,_owner:s.current}}t.jsx=u,t.jsxs=u},5893:function(e,t,n){e.exports=n(5251)},9196:function(e){e.exports=window.React}},t={};function n(o){var r=t[o];if(void 0!==r)return r.exports;var a=t[o]={exports:{}};return e[o](a,a.exports,n),a.exports}!function(){var e=window.wp.element,t=window.wp.components,o=window.wp.i18n;const{facets:r}=window.epInstantResultsAdmin;var a=n(5893),s=({defaultValue:n,...s})=>{const l=n.split(","),[i,u]=(0,e.useState)(l),f=e=>r[e]?.label,d=e=>Object.keys(r).find((t=>e===r[t].label)),c=(0,e.useMemo)((()=>Object.keys(r).map(f).filter(Boolean)),[]),p=(0,e.useMemo)((()=>i.map(f).filter(Boolean)),[i]);return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(t.FormTokenField,{__experimentalExpandOnFocus:!0,__experimentalShowHowTo:!1,label:(0,o.__)("Select facets","elasticpress"),onChange:e=>{u(e.map(d).filter(Boolean))},suggestions:c,value:p}),(0,a.jsx)("input",{type:"hidden",value:i.join(","),...s})]})};document.addEventListener("DOMContentLoaded",(()=>{const t=document.getElementById("feature_instant_results_facets"),{className:n,dataset:{fieldName:o},id:r,name:l,value:i}=t;(0,e.render)((0,a.jsx)(s,{className:n,"data-field-name":o,defaultValue:i,id:r,name:l}),t.parentElement)}))}()}();
--------------------------------------------------------------------------------
/dist/js/instant-results-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('react', 'react-dom', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives'), 'version' => '2204b560c2a26143c2bdb30213f98a3b');
--------------------------------------------------------------------------------
/dist/js/notice-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('wp-api-fetch', 'wp-dom-ready', 'wp-polyfill'), 'version' => 'c5e60b5e270f694cfbac47b0f56da375');
--------------------------------------------------------------------------------
/dist/js/notice-script.min.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";var n={n:function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,{a:t}),t},d:function(e,t){for(var o in t)n.o(t,o)&&!n.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},o:function(n,e){return Object.prototype.hasOwnProperty.call(n,e)}},e=window.wp.apiFetch,t=n.n(e),o=window.wp.domReady,r=n.n(o);const{epAdmin:c,ajaxurl:i}=window;r()((()=>{const n=document.querySelectorAll(".notice[data-ep-notice]"),e=n=>{if(!n.target.classList.contains("notice-dismiss"))return;const e=new FormData;e.append("action","ep_notice_dismiss"),e.append("notice",n.currentTarget.dataset.epNotice),e.append("nonce",c.nonce),t()({method:"POST",url:i,body:e})};for(const t of n)t.addEventListener("click",e)}))}();
--------------------------------------------------------------------------------
/dist/js/ordering-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('react', 'react-dom', 'wp-api-fetch', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => '7b10fc96fdd0a88cbfedbc154a80b1c2');
--------------------------------------------------------------------------------
/dist/js/related-posts-block-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => '1bb8cd8c4ce338de53fdcd9dfa0a020c');
--------------------------------------------------------------------------------
/dist/js/related-posts-block-script.min.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";var e={5251:function(e,t,s){var r=s(9196),n=60103;if(60107,"function"===typeof Symbol&&Symbol.for){var o=Symbol.for;n=o("react.element"),o("react.fragment")}var i=r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l=Object.prototype.hasOwnProperty,a={key:!0,ref:!0,__self:!0,__source:!0};function p(e,t,s){var r,o={},p=null,c=null;for(r in void 0!==s&&(p=""+s),void 0!==t.key&&(p=""+t.key),void 0!==t.ref&&(c=t.ref),t)l.call(t,r)&&!a.hasOwnProperty(r)&&(o[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps)void 0===o[r]&&(o[r]=t[r]);return{$$typeof:n,type:e,key:p,ref:c,props:o,_owner:i.current}}t.jsx=p,t.jsxs=p},5893:function(e,t,s){e.exports=s(5251)},9196:function(e){e.exports=window.React}},t={};function s(r){var n=t[r];if(void 0!==n)return n.exports;var o=t[r]={exports:{}};return e[r](o,o.exports,s),o.exports}!function(){var e=window.wp.blocks,t=window.wp.i18n,r=window.wp.blockEditor,n=window.wp.components,o=window.wp.element,i=window.wp.url,l=s(5893);class a extends o.Component{constructor(e){super(e),this.state={posts:!1}}componentDidMount(){const{context:{postId:e=0}={}}=this.props;wp.apiFetch({path:(0,i.addQueryArgs)(`/wp/v2/posts/${e}/related`,{number:100})}).then((e=>{this.setState({posts:e})})).catch((()=>{this.setState({posts:!1})}))}render(){const{attributes:{alignment:e,number:s},setAttributes:i,className:a}=this.props,{posts:p}=this.state,c=p.length>s?p.slice(0,s):p;return(0,l.jsxs)(o.Fragment,{children:[(0,l.jsx)(r.BlockControls,{children:(0,l.jsx)(r.AlignmentToolbar,{value:e,onChange:e=>i({alignment:e})})}),(0,l.jsx)(r.InspectorControls,{children:(0,l.jsx)(n.PanelBody,{title:(0,t.__)("Related Post Settings","elasticpress"),children:(0,l.jsx)(n.QueryControls,{numberOfItems:s,onNumberOfItemsChange:e=>i({number:e})})})}),(0,l.jsx)("div",{className:a,children:!1===c||0===c.length?(0,l.jsx)(n.Placeholder,{icon:"admin-post",label:(0,t.__)("Related Posts","elasticpress"),children:!1===p?(0,l.jsx)(n.Spinner,{}):(0,t.__)("No related posts yet.","elasticpress")}):(0,l.jsx)("ul",{style:{textAlign:e},children:c.map((e=>{const s=e.title.rendered.trim();return(0,l.jsx)("li",{children:(0,l.jsx)("a",{href:e.link,children:s?(0,l.jsx)(o.RawHTML,{children:s}):(0,t.__)("(Untitled)","elasticpress")})},e.id)}))})})]})}}var p=a;(0,e.registerBlockType)("elasticpress/related-posts",{title:(0,t.__)("Related Posts (ElasticPress)","elasticpress"),supports:{align:!0},category:"widgets",attributes:{alignment:{type:"string",default:"none"},number:{type:"number",default:5}},usesContext:["postId"],edit:e=>(0,l.jsx)(p,{...e}),save:()=>null})}()}();
--------------------------------------------------------------------------------
/dist/js/settings-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('wp-dom-ready', 'wp-i18n', 'wp-polyfill'), 'version' => 'f21347b966fa44702392dbc013c4a77f');
--------------------------------------------------------------------------------
/dist/js/settings-script.min.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";var e={n:function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,{a:n}),n},d:function(t,n){for(var r in n)e.o(n,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:n[r]})},o:function(e,t){return Object.prototype.hasOwnProperty.call(e,t)}},t=window.wp.domReady,n=e.n(t),r=window.wp.i18n;n()((()=>{const e=document.querySelectorAll(".ep-credentials-tab"),t=document.getElementById("ep_host"),n=t.labels[0],s=t.nextElementSibling,a=document.getElementsByClassName("ep-additional-fields");let o=document.querySelector(".nav-tab-active");const i=()=>o&&"epio"in o.dataset;let c=i()?t.value:"",l=i()?"":t.value;const u=e=>{i()?c=e.currentTarget.value:l=e.currentTarget.value},d=u=>{o=u.currentTarget;for(const t of e)t.classList.toggle("nav-tab-active",t===o);for(const e of a)e.classList.toggle("hidden",!i());n.innerText=i()?(0,r.__)("ElasticPress.io Host URL","elasticpress"):(0,r.__)("Elasticsearch Host URL","elasticpress"),t.disabled||(t.value=i()?c:l,s.innerText=i()?(0,r.__)("Plug in your ElasticPress.io server here!","elasticpress"):(0,r.__)("Plug in your Elasticsearch server here!","elasticpress"))};t&&t.addEventListener("input",u);for(const t of e)t.addEventListener("click",d)}))}();
--------------------------------------------------------------------------------
/dist/js/sites-admin-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('react', 'wp-api-fetch', 'wp-components', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => '30cf86d5c387e97cc70e8b0f16390e0c');
--------------------------------------------------------------------------------
/dist/js/sites-admin-script.min.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";var e={5251:function(e,n,o){var t=o(9196),r=60103;if(60107,"function"===typeof Symbol&&Symbol.for){var a=Symbol.for;r=a("react.element"),a("react.fragment")}var c=t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,i=Object.prototype.hasOwnProperty,s={key:!0,ref:!0,__self:!0,__source:!0};function d(e,n,o){var t,a={},d=null,p=null;for(t in void 0!==o&&(d=""+o),void 0!==n.key&&(d=""+n.key),void 0!==n.ref&&(p=n.ref),n)i.call(n,t)&&!s.hasOwnProperty(t)&&(a[t]=n[t]);if(e&&e.defaultProps)for(t in n=e.defaultProps)void 0===a[t]&&(a[t]=n[t]);return{$$typeof:r,type:e,key:d,ref:p,props:a,_owner:c.current}}n.jsx=d},5893:function(e,n,o){e.exports=o(5251)},9196:function(e){e.exports=window.React}},n={};function o(t){var r=n[t];if(void 0!==r)return r.exports;var a=n[t]={exports:{}};return e[t](a,a.exports,o),a.exports}o.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(n,{a:n}),n},o.d=function(e,n){for(var t in n)o.o(n,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:n[t]})},o.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},function(){var e=window.wp.apiFetch,n=o.n(e),t=window.wp.components,r=window.wp.domReady,a=o.n(r),c=window.wp.element,i=window.wp.i18n,s=o(5893);const{ajaxurl:d,epsa:p}=window,l=({blogId:e,isDefaultChecked:o})=>{const[r,a]=(0,c.useState)(o),[l,f]=(0,c.useState)(!1);return(0,s.jsx)(t.ToggleControl,{checked:r,className:"index-toggle",disabled:l,label:r?(0,i.__)("On","elasticpress"):(0,i.__)("Off","elasticpress"),onChange:async o=>{a(o),f(!0);const t=new FormData;t.append("action","ep_site_admin"),t.append("blog_id",e),t.append("checked",o?"yes":"no"),t.append("nonce",p.nonce),await n()({method:"POST",url:d,body:t}),f(!1)}})};a()((()=>{const e=document.getElementsByClassName("index-toggle");for(const n of e)(0,c.render)((0,s.jsx)(l,{blogId:n.dataset.blogId,isDefaultChecked:n.checked}),n.parentElement)}))}()}();
--------------------------------------------------------------------------------
/dist/js/stats-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('moment', 'wp-polyfill'), 'version' => '8329a3a1a2860cfdaa11bdc69042b7a0');
--------------------------------------------------------------------------------
/dist/js/sync-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('react', 'wp-api-fetch', 'wp-components', 'wp-date', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives'), 'version' => '2013fd50cb46aae737b144d70c78f608');
--------------------------------------------------------------------------------
/dist/js/synonyms-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('react', 'wp-components', 'wp-element', 'wp-polyfill'), 'version' => '6095864ad1b2d9570d7e36bb602234f0');
--------------------------------------------------------------------------------
/dist/js/weighting-script.min.asset.php:
--------------------------------------------------------------------------------
1 | array('wp-dom-ready', 'wp-polyfill'), 'version' => 'cd98315de2a5b6514fa1749bc4e954e0');
--------------------------------------------------------------------------------
/dist/js/weighting-script.min.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";var e={n:function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,{a:n}),n},d:function(t,n){for(var r in n)e.o(n,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:n[r]})},o:function(e,t){return Object.prototype.hasOwnProperty.call(e,t)}},t=window.wp.domReady;e.n(t)()((()=>{const e=e=>{e.labels[0].querySelector(".weighting-value").textContent=e.disabled?"0":e.value},t=t=>{e(t.currentTarget)},n=t=>{const n=t.currentTarget.closest("fieldset").querySelector('input[type="range"]');n.disabled=!t.currentTarget.checked,e(n)},r=document.querySelectorAll(".weighting-settings input[type=range]");for(const e of r)e.addEventListener("input",t);const o=document.querySelectorAll(".weighting-settings .searchable input[type=checkbox]");for(const e of o)e.addEventListener("change",n)}))}();
--------------------------------------------------------------------------------
/images/features-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/ElasticPress/3fb8bb6c40ea99b0c219dc9afdfbca70d1e7b986/images/features-screenshot.png
--------------------------------------------------------------------------------
/images/logo-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/setup-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/ElasticPress/3fb8bb6c40ea99b0c219dc9afdfbca70d1e7b986/images/setup-screenshot.png
--------------------------------------------------------------------------------
/images/sync-in-progress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/ElasticPress/3fb8bb6c40ea99b0c219dc9afdfbca70d1e7b986/images/sync-in-progress.png
--------------------------------------------------------------------------------
/images/warning.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/includes/classes/Feature/RelatedPosts/Widget.php:
--------------------------------------------------------------------------------
1 | esc_html__( 'Show related posts using ElasticPress. This widget will only appear on single post, page, and custom type pages.', 'elasticpress' ) );
30 | parent::__construct( 'ep-related-posts', esc_html__( 'Enterprise Search - Related Posts', 'elasticpress' ), $options );
31 | }
32 |
33 | /**
34 | * Display widget
35 | *
36 | * @param array $args Widget arguments
37 | * @param array $instance Widget instance variables
38 | * @since 2.2
39 | */
40 | public function widget( $args, $instance ) {
41 |
42 | if ( ! is_single() ) {
43 | return;
44 | }
45 |
46 | $related_posts = get_transient( 'ep_related_posts_' . get_the_ID() );
47 |
48 | if ( false === $related_posts ) {
49 | $related_posts = Features::factory()->get_registered_feature( 'related_posts' )->find_related( get_the_ID(), $instance['num_posts'] );
50 |
51 | if ( empty( $related_posts ) ) {
52 | if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
53 | set_transient( 'ep_related_posts_' . get_the_ID(), '', HOUR_IN_SECONDS ); // Let's not spam
54 | }
55 | return;
56 | }
57 |
58 | ob_start();
59 |
60 | echo wp_kses_post( $args['before_widget'] );
61 |
62 | if ( ! empty( $instance['title'] ) ) {
63 | echo wp_kses_post( $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title'] );
64 | }
65 | ?>
66 |
67 |
72 |
73 |
98 |
99 |
102 |
103 |
104 |
105 |
106 |
107 |
110 |
111 |
112 |
113 | slug = 'terms';
28 |
29 | $this->title = esc_html__( 'Terms', 'elasticpress' );
30 |
31 | $this->summary = __( 'Improve WP_Term_Query relevancy and query performance. This feature is only needed if you are using WP_Term_Query directly.', 'elasticpress' );
32 |
33 | $this->docs_url = __( 'https://elasticpress.zendesk.com/hc/en-us/articles/360050447492-Configuring-ElasticPress-via-the-Plugin-Dashboard#terms', 'elasticpress' );
34 |
35 | $this->requires_install_reindex = true;
36 |
37 | parent::__construct();
38 | }
39 |
40 | /**
41 | * Setup search functionality
42 | *
43 | * @since 3.1
44 | */
45 | public function setup() {
46 | Indexables::factory()->register( new Indexable\Term\Term() );
47 |
48 | add_action( 'init', [ $this, 'search_setup' ] );
49 | }
50 |
51 | /**
52 | * Setup search integration
53 | *
54 | * @since 3.1
55 | */
56 | public function search_setup() {
57 | add_filter( 'ep_elasticpress_enabled', [ $this, 'integrate_search_queries' ], 10, 2 );
58 | add_filter( 'ep_term_fuzziness_arg', [ $this, 'set_admin_terms_search_fuzziness' ] );
59 | }
60 |
61 | /**
62 | * Output feature box long text
63 | *
64 | * @since 3.1
65 | */
66 | public function output_feature_box_long() {
67 | ?>
68 |
69 | query_vars['ep_integrate'] ) && ! filter_var( $query->query_vars['ep_integrate'], FILTER_VALIDATE_BOOLEAN ) ) {
86 | $enabled = false;
87 | } elseif ( ! empty( $query->query_vars['search'] ) ) {
88 | $enabled = true;
89 | }
90 |
91 | return $enabled;
92 | }
93 |
94 | /**
95 | * Determine feature reqs status
96 | *
97 | * @since 3.1
98 | * @return FeatureRequirementsStatus
99 | */
100 | public function requirements_status() {
101 | $status = new FeatureRequirementsStatus( 1 );
102 |
103 | return $status;
104 | }
105 |
106 | /**
107 | * Change fuzziness level for terms search in admin
108 | *
109 | * @param {int} $fuzziness Amount of fuzziness to factor into search
110 | * @since 3.6.4
111 | * @return int
112 | */
113 | public function set_admin_terms_search_fuzziness( $fuzziness ) {
114 | if ( is_admin() ) {
115 | $fuzziness = 0;
116 | }
117 | return $fuzziness;
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/includes/classes/Feature/Users/Users.php:
--------------------------------------------------------------------------------
1 | slug = 'users';
28 |
29 | $this->title = esc_html__( 'Users', 'elasticpress' );
30 |
31 | $this->summary = __( 'Improve user search relevancy and query performance.', 'elasticpress' );
32 |
33 | $this->docs_url = __( 'https://elasticpress.zendesk.com/hc/en-us/articles/360050447492-Configuring-ElasticPress-via-the-Plugin-Dashboard#users', 'elasticpress' );
34 |
35 | $this->requires_install_reindex = true;
36 |
37 | parent::__construct();
38 | }
39 |
40 | /**
41 | * Hook search functionality
42 | *
43 | * @since 3.0
44 | */
45 | public function setup() {
46 | Indexables::factory()->register( new Indexable\User\User() );
47 |
48 | add_action( 'init', [ $this, 'search_setup' ] );
49 | }
50 |
51 | /**
52 | * Setup feature on each page load
53 | *
54 | * @since 3.0
55 | */
56 | public function search_setup() {
57 | add_filter( 'ep_elasticpress_enabled', [ $this, 'integrate_search_queries' ], 10, 2 );
58 | }
59 |
60 | /**
61 | * Output feature box long text
62 | *
63 | * @since 3.0
64 | */
65 | public function output_feature_box_long() {
66 | ?>
67 |
68 |
69 | query_vars['ep_integrate'] ) && ! filter_var( $query->query_vars['ep_integrate'], FILTER_VALIDATE_BOOLEAN ) ) {
86 | $enabled = false;
87 | } elseif ( ! empty( $query->query_vars['search'] ) ) {
88 | $enabled = true;
89 | }
90 |
91 | return $enabled;
92 | }
93 |
94 | /**
95 | * Determine feature reqs status
96 | *
97 | * @since 2.2
98 | * @return FeatureRequirementsStatus
99 | */
100 | public function requirements_status() {
101 | $status = new FeatureRequirementsStatus( 1 );
102 |
103 | return $status;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/includes/classes/FeatureRequirementsStatus.php:
--------------------------------------------------------------------------------
1 | code = $code;
29 |
30 | $this->message = $message;
31 | }
32 |
33 | /**
34 | * Returns the status of a feature
35 | *
36 | * 0 is no issues
37 | * 1 is usable but there are warnngs
38 | * 2 is not usable
39 | *
40 | * @var int
41 | * @since 2.2
42 | */
43 | public $code;
44 |
45 | /**
46 | * Optional message to describe status code
47 | *
48 | * @var string|array
49 | * @since 2.2
50 | */
51 | public $message;
52 | }
53 |
--------------------------------------------------------------------------------
/includes/classes/HealthCheck.php:
--------------------------------------------------------------------------------
1 | test_name;
47 | }
48 |
49 | /**
50 | * Checks if the health check is async.
51 | *
52 | * @return bool True when check is async.
53 | */
54 | protected function is_async() {
55 | return ! empty( $this->async );
56 | }
57 |
58 | /**
59 | * Registers the test to WordPress.
60 | */
61 | public function register_test() {
62 | if ( $this->is_async() ) {
63 | add_filter( 'site_status_tests', [ $this, 'add_async_test' ] );
64 |
65 | add_action( 'wp_ajax_health-check-' . $this->get_test_name(), [ $this, 'get_test_result' ] );
66 |
67 | return;
68 | }
69 |
70 | add_filter( 'site_status_tests', [ $this, 'add_direct_test' ] );
71 | }
72 |
73 | /**
74 | * Adds to the direct tests list.
75 | *
76 | * @param array $tests Array with the current tests.
77 | *
78 | * @return array
79 | */
80 | public function add_direct_test( $tests ) {
81 | $tests['direct'][ $this->get_test_name() ] = [
82 | 'test' => [ $this, 'get_test_result' ],
83 | ];
84 |
85 | return $tests;
86 | }
87 |
88 | /**
89 | * Adds to the async tests list.
90 | *
91 | * @param array $tests Array with the current tests.
92 | *
93 | * @return array
94 | */
95 | public function add_async_test( $tests ) {
96 | $tests['async'][ $this->get_test_name() ] = [
97 | 'test' => $this->get_test_name(),
98 | ];
99 |
100 | return $tests;
101 | }
102 |
103 | /**
104 | * Gets the result of test.
105 | *
106 | * @return array|void
107 | */
108 | public function get_test_result() {
109 | $result = $this->run();
110 |
111 | if ( $this->is_async() ) {
112 | wp_send_json_success( $result );
113 | } else {
114 | return $result;
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/includes/classes/HealthCheck/HealthCheckElasticsearch.php:
--------------------------------------------------------------------------------
1 | test_name = 'elasticpress-health-check-elasticsearch';
29 | $this->async = true;
30 | }
31 |
32 | /**
33 | * Runs the test.
34 | *
35 | * @return array Data about the result of the test.
36 | */
37 | public function run() {
38 | $result = [
39 | 'label' => esc_html__( 'Your site can connect to Elasticsearch.', 'elasticpress' ),
40 | 'status' => 'good',
41 | 'badge' => [
42 | 'label' => esc_html__( 'ElasticPress', 'elasticpress' ),
43 | 'color' => 'green',
44 | ],
45 | 'description' => esc_html__( 'You can have a fast and flexible search and query engine for WordPress using ElasticPress.', 'elasticpress' ),
46 | 'actions' => '',
47 | 'test' => $this->test_name,
48 | ];
49 |
50 | $host = Utils\get_host();
51 |
52 | $elasticpress_settings_url = defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ? admin_url( 'network/admin.php?page=elasticpress-settings' ) : admin_url( 'admin.php?page=elasticpress-settings' );
53 |
54 | if ( empty( $host ) ) {
55 | $result['label'] = esc_html__( 'Your site could not connect to Elasticsearch', 'elasticpress' );
56 | $result['status'] = 'critical';
57 | $result['badge']['color'] = 'red';
58 | $result['description'] = esc_html__( 'The Elasticsearch host is not set.', 'elasticpress' );
59 | $result['actions'] = sprintf(
60 | '%s
',
61 | esc_url( $elasticpress_settings_url ),
62 | esc_html__( 'Add a host', 'elasticpress' )
63 | );
64 | } elseif ( ! Elasticsearch::factory()->get_elasticsearch_version( true ) ) {
65 | $result['label'] = esc_html__( 'Your site could not connect to Elasticsearch', 'elasticpress' );
66 | $result['status'] = 'critical';
67 | $result['badge']['color'] = 'red';
68 | $result['actions'] = sprintf(
69 | '%s
',
70 | esc_url( $elasticpress_settings_url ),
71 | esc_html__( 'Update your settings', 'elasticpress' )
72 | );
73 |
74 | if ( Utils\is_epio() ) {
75 | $result['description'] = esc_html__( 'Check if your credentials to ElasticPress.io host are correct.', 'elasticpress' );
76 | } else {
77 | $result['description'] = esc_html__( 'Check if your Elasticsearch host URL is correct and you have the right access to the host.', 'elasticpress' );
78 | }
79 | }
80 |
81 | return $result;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/includes/classes/Indexable/User/SyncManager.php:
--------------------------------------------------------------------------------
1 | get_elasticsearch_version() ) {
31 | return;
32 | }
33 |
34 | add_action( 'delete_user', [ $this, 'action_delete_user' ] );
35 | add_action( 'wpmu_delete_user', [ $this, 'action_delete_user' ] );
36 | add_action( 'profile_update', [ $this, 'action_sync_on_update' ] );
37 | add_action( 'user_register', [ $this, 'action_sync_on_update' ] );
38 | add_action( 'updated_user_meta', [ $this, 'action_queue_meta_sync' ], 10, 4 );
39 | add_action( 'added_user_meta', [ $this, 'action_queue_meta_sync' ], 10, 4 );
40 | add_action( 'deleted_user_meta', [ $this, 'action_queue_meta_sync' ], 10, 4 );
41 |
42 | // @todo Handle deleted meta
43 | }
44 |
45 | /**
46 | * Dummy implementation of site unsetup method (for now)
47 | */
48 | public function tear_down() {}
49 |
50 | /**
51 | * When whitelisted meta is updated/added/deleted, queue the object for reindex
52 | *
53 | * @param int $meta_id Meta id.
54 | * @param int|array $object_id Object id.
55 | * @param string $meta_key Meta key.
56 | * @param string $meta_value Meta value.
57 | * @since 2.0
58 | */
59 | public function action_queue_meta_sync( $meta_id, $object_id, $meta_key, $meta_value ) {
60 | if ( $this->kill_sync() ) {
61 | return;
62 | }
63 |
64 | $indexable = Indexables::factory()->get( 'user' );
65 |
66 | $this->add_to_queue( $object_id );
67 | }
68 |
69 | /**
70 | * Delete ES user when WP user is deleted
71 | *
72 | * @param int $user_id User ID
73 | * @since 3.0
74 | */
75 | public function action_delete_user( $user_id ) {
76 | if ( $this->kill_sync() ) {
77 | return;
78 | }
79 |
80 | if ( ! current_user_can( 'edit_user', $user_id ) ) {
81 | return;
82 | }
83 |
84 | Indexables::factory()->get( 'user' )->delete( $user_id, false );
85 |
86 | $this->remove_from_queue( $user_id ); // VIP: Remove from queue, same as other SyncManager indexable classes
87 | }
88 |
89 | /**
90 | * Sync ES index with what happened to the user being saved
91 | *
92 | * @param int $user_id User id.
93 | * @since 3.0
94 | */
95 | public function action_sync_on_update( $user_id ) {
96 | if ( $this->kill_sync() ) {
97 | return;
98 | }
99 |
100 | if ( ! current_user_can( 'edit_user', $user_id ) ) {
101 | return;
102 | }
103 |
104 | /**
105 | * Filter whether to kill sync for a particular user
106 | *
107 | * @hook ep_user_sync_kill
108 | * @param {bool} $kill True means dont sync
109 | * @param {int} $user_id User ID
110 | * @since 3.0
111 | * @return {bool} New kill value
112 | */
113 | if ( apply_filters( 'ep_user_sync_kill', false, $user_id ) ) {
114 | return;
115 | }
116 |
117 | /**
118 | * Fires before adding user to sync queue
119 | *
120 | * @hook ep_sync_user_on_transition
121 | * @param {int} $user_id User ID
122 | * @since 3.0
123 | */
124 | do_action( 'ep_sync_user_on_transition', $user_id );
125 |
126 | $this->add_to_queue( $user_id );
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/includes/classes/Indexables.php:
--------------------------------------------------------------------------------
1 | registered_indexables[ $indexable->slug ] = $indexable;
36 | }
37 |
38 | /**
39 | * Get an indexable instance given a slug
40 | *
41 | * @param string $slug Indexable type slug.
42 | * @since 3.0
43 | * @return Indexable|boolean
44 | */
45 | public function get( $slug ) {
46 | return ( ! empty( $this->registered_indexables[ $slug ] ) ) ? $this->registered_indexables[ $slug ] : false;
47 | }
48 |
49 | /**
50 | * Get all indexable instances
51 | *
52 | * @param boolean $global If true or false, will only get Indexables with that global property.
53 | * @param boolean $slug_only True returns an array of only string slugs.
54 | * @since 3.0
55 | * @return array
56 | */
57 | public function get_all( $global = null, $slug_only = false ) {
58 | $indexables = [];
59 |
60 | foreach ( $this->registered_indexables as $slug => $indexable ) {
61 | if ( null === $global ) {
62 | if ( $slug_only ) {
63 | $indexables[] = $slug;
64 | } else {
65 | $indexables[] = $indexable;
66 | }
67 | } else {
68 | if ( $global === $indexable->global ) {
69 | if ( $slug_only ) {
70 | $indexables[] = $slug;
71 | } else {
72 | $indexables[] = $indexable;
73 | }
74 | }
75 | }
76 | }
77 |
78 | return $indexables;
79 | }
80 |
81 | /**
82 | * Return singleton instance of class
83 | *
84 | * @return object
85 | */
86 | public static function factory() {
87 | static $instance = false;
88 |
89 | if ( ! $instance ) {
90 | $instance = new self();
91 | }
92 |
93 | return $instance;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/includes/classes/Installer.php:
--------------------------------------------------------------------------------
1 | get_current_screen() ) {
49 | // translators: Site Name
50 | return sprintf( esc_html__( 'ElasticPress Setup ‹ %s — WordPress', 'elasticpress' ), esc_html( get_bloginfo( 'name' ) ) );
51 | }
52 |
53 | return $admin_title;
54 | }
55 |
56 | /**
57 | * Determine current install status
58 | *
59 | * @since 3.0
60 | */
61 | public function calculate_install_status() {
62 | $skip_install = Utils\get_option( 'ep_skip_install', false );
63 |
64 | if ( $skip_install ) {
65 | $this->install_status = true;
66 |
67 | return;
68 | }
69 |
70 | $last_sync = Utils\get_option( 'ep_last_sync', false );
71 | if ( ! empty( $last_sync ) ) {
72 | $this->install_status = true;
73 |
74 | return;
75 | }
76 |
77 | $host = Utils\get_host();
78 |
79 | if ( empty( $host ) && empty( $_POST['ep_host'] ) ) { // phpcs:ignore
80 | $this->install_status = 2;
81 |
82 | return;
83 | }
84 |
85 | $this->install_status = 3;
86 |
87 | $this->maybe_set_features();
88 | }
89 |
90 | /**
91 | * Get installation status
92 | *
93 | * false - not installed
94 | * 2 - On step two of install
95 | * 3 - On step three of install
96 | * true - Install complete
97 | *
98 | * @return bool|int
99 | */
100 | public function get_install_status() {
101 | /**
102 | * Filter install status
103 | *
104 | * @hook ep_install_status
105 | * @param {string} $install_status Current install status
106 | * @return {string} New install status
107 | * @since 3.0
108 | */
109 | return apply_filters( 'ep_install_status', $this->install_status );
110 | }
111 |
112 | /**
113 | * Check if it should use the features selected during the install to update the settings.
114 | */
115 | public function maybe_set_features() {
116 | if ( empty( $_POST['ep_install_page_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['ep_install_page_nonce'] ), 'ep_install_page' ) ) {
117 | return;
118 | }
119 |
120 | if ( ! isset( $_POST['features'] ) || ! is_array( $_POST['features'] ) ) {
121 | return;
122 | }
123 |
124 | $registered_features = \ElasticPress\Features::factory()->registered_features;
125 | $activation_features = wp_list_filter( $registered_features, array( 'available_during_installation' => true ) );
126 |
127 | foreach ( $activation_features as $slug => $feature ) {
128 | if ( in_array( $slug, $_POST['features'], true ) ) {
129 | \ElasticPress\Features::factory()->activate_feature( $slug );
130 | } else {
131 | \ElasticPress\Features::factory()->deactivate_feature( $slug );
132 | }
133 | }
134 |
135 | $this->install_status = 4;
136 | }
137 |
138 | /**
139 | * Return singleton instance of class
140 | *
141 | * @return self
142 | * @since 3.0
143 | */
144 | public static function factory() {
145 | static $instance = false;
146 |
147 | if ( ! $instance ) {
148 | $instance = new self();
149 | $instance->setup();
150 | }
151 |
152 | return $instance;
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/includes/health-check.php:
--------------------------------------------------------------------------------
1 | register_test();
21 | }
22 |
--------------------------------------------------------------------------------
/includes/partials/dashboard-page.php:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
102 |
--------------------------------------------------------------------------------
/includes/partials/header.php:
--------------------------------------------------------------------------------
1 | get_current_screen();
18 | ?>
19 |
20 |
43 |
--------------------------------------------------------------------------------
/includes/partials/stats-page.php:
--------------------------------------------------------------------------------
1 | build_stats();
28 |
29 | $index_health = Stats::factory()->get_health();
30 | $totals = Stats::factory()->get_totals();
31 | ?>
32 |
33 |
34 |
99 |
--------------------------------------------------------------------------------
/includes/partials/sync-page.php:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
--------------------------------------------------------------------------------