PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.5
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.5
4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 All 203 releases
betterdocs / react-src / admin / components / Header.js

Header.js in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.5, at react-src/admin/components/Header.js

267 lines 14.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { applyFilters } from "@wordpress/hooks";
2 import { __, sprintf } from "@wordpress/i18n";
3 import React from "react";
4 import Select from "react-select";
5 import KnowledgeBaseFilter from "../dashboard/components/KnowledgeBaseFilter";
6 import { useLocation, useSearchParams } from "react-router-dom";
7
8 import { Link } from "react-router-dom";
9
10 import { IconAutorenew, Logo } from "../../icons/index";
11
12 import { makeRouteURL, history } from "../../utils/history";
13
14 import DarkModeToggle from "./DarkModeToggle";
15
16 // FAQ Builder header "order" options. Rendered with react-select (instead of a
17 // native <select>) so the menu sits cleanly below the control — matching the
18 // Knowledge Base filter — instead of overlapping the search field.
19 const FAQ_ORDER_OPTIONS = [
20 { value: "default", label: __("Default Order", "betterdocs") },
21 { value: "most_recent", label: __("Most Recent", "betterdocs") },
22 { value: "least_recent", label: __("Least Recent", "betterdocs") },
23 { value: "a_to_z", label: __("A → Z", "betterdocs") },
24 { value: "z_to_a", label: __("Z → A", "betterdocs") },
25 { value: "most_questions", label: __("Most Questions", "betterdocs") },
26 ];
27
28 const CLASSIC_UI_URLS = {
29 "betterdocs-admin": "edit.php?post_type=docs&amp;bdocs_view=classic",
30 "betterdocs-faq": "edit-tags.php?taxonomy=betterdocs_faq_category&post_type=betterdocs_faq",
31 "betterdocs-glossaries": "edit-tags.php?taxonomy=glossaries&post_type=docs",
32 "betterdocs-doc-categories": "edit-tags.php?taxonomy=doc_category&post_type=docs",
33 "betterdocs-doc-tags": "edit-tags.php?taxonomy=doc_tag&post_type=docs",
34 };
35
36 const Header = ({
37 page,
38 filter,
39 trash,
40 postStatus,
41 docs,
42 mode,
43 faqSearchKeyword,
44 handleFAQSearch,
45 faqOrder,
46 onFaqOrderChange,
47 docCategoriesKeyword,
48 handleDocCategoriesSearch,
49 docTagsKeyword,
50 handleDocTagsSearch,
51 }) => {
52 const [mkbFilter, setMkbFilterValue] = filter;
53 const [isLoading, totalItems] = docs;
54 const location = useLocation();
55 const [searchParams] = useSearchParams();
56 const faqTab = searchParams.get("faq_tab") || "builder";
57 const faqSubtab = searchParams.get("faq_subtab") || "groups";
58 const isFaqBuilderTab = page === "betterdocs-faq" && faqTab === "builder";
59 // The WooCommerce "Product FAQ Groups" sub-tab lists FAQ groups just like the
60 // General builder, so it gets the same search / order / Classic UI controls.
61 const isWooGroupsTab =
62 page === "betterdocs-faq" &&
63 faqTab === "woocommerce" &&
64 faqSubtab === "groups";
65 const showFaqGroupControls = isFaqBuilderTab || isWooGroupsTab;
66 const wcActive = Boolean(window.betterdocs_admin?.is_woocommerce_active);
67
68 const routeClass = page ? `route-${page.replace(/^betterdocs-/, '')}` : '';
69
70 // FAQ search shows only on the groups tabs (General builder / Woo groups),
71 // not on the WooCommerce product-assignment sub-tab. Glossaries is absent
72 // here on purpose: it moved to Pro, which renders its own field into the
73 // `betterdocs_admin_header_search` slot below.
74 const hasSearch = showFaqGroupControls
75 || page === "betterdocs-doc-categories" || page === "betterdocs-doc-tags";
76
77 const searchValue = page === "betterdocs-faq" ? faqSearchKeyword
78 : page === "betterdocs-doc-categories" ? docCategoriesKeyword
79 : page === "betterdocs-doc-tags" ? docTagsKeyword
80 : "";
81
82 const searchHandler = page === "betterdocs-faq" ? handleFAQSearch
83 : page === "betterdocs-doc-categories" ? handleDocCategoriesSearch
84 : page === "betterdocs-doc-tags" ? handleDocTagsSearch
85 : null;
86
87 const searchPlaceholder = page === "betterdocs-faq" ? __("Search groups & questions…", "betterdocs")
88 : page === "betterdocs-doc-categories" ? __("Search categories…", "betterdocs")
89 : page === "betterdocs-doc-tags" ? __("Search tags…", "betterdocs")
90 : "";
91
92 // Allow Pro/add-ons to register Classic-UI links for their own React pages
93 // (e.g. the Knowledge Base admin page).
94 const classicUIUrls = applyFilters("betterdocs_classic_ui_urls", CLASSIC_UI_URLS, page);
95 let classicUIUrl = classicUIUrls[page] || null;
96 // FAQ: only show Classic UI on the groups tabs, and point the WooCommerce
97 // groups tab at the product FAQ category taxonomy.
98 if (page === "betterdocs-faq") {
99 classicUIUrl = showFaqGroupControls
100 ? `edit-tags.php?taxonomy=${isWooGroupsTab ? "betterdocs_product_faq_category" : "betterdocs_faq_category"}&post_type=betterdocs_faq`
101 : null;
102 }
103
104 return (
105 <section className={`betterdocs-dashboard-header${routeClass ? ` ${routeClass}` : ''}`}>
106 <div className="betterdocs-dashboard-header-top">
107 <div className="betterdocs-dashboard-container">
108 <div className="betterdocs-row betterdocs-logo-action">
109 <div className="betterdocs-col betterdocs-col-xs-6 betterdocs-dashboard-header-left betterdocs-dashboard-header-items">
110 <div className="betterdocs-dashboard-logo">
111 <Logo />
112 </div>
113 </div>
114 <div className="betterdocs-col betterdocs-col-xs-6 betterdocs-dashboard-header-right betterdocs-dashboard-header-items">
115 {/* Per-page "create" action (Add New Docs/FAQ/Category/…)
116 — the Dashboard's Add FAQ/Docs buttons are registered
117 here too via betterdocs_admin_header_action (Routes.js).
118 The mode switcher lives in the container row below (same
119 spot on every screen), not in this sticky logo bar. */}
120 {applyFilters("betterdocs_admin_header_action", null, page)}
121 </div>
122 </div>
123 </div>
124 </div>
125 <div className="betterdocs-dashboard-container">
126 {/* Analytics, Content Intelligence and MCP render their own
127 per-route header into this container, so skip the default row
128 for them. Each portals the dark-mode switcher back in with its
129 own row — dropping this row without it would take the screen's
130 only global control with it (analytics/components/Chrome.js,
131 content-intelligence/index.js, mcp/components/McpHeader.js). */}
132 {page !== "betterdocs-analytics" && page !== "betterdocs-content-iq" && page !== "betterdocs-mcp" && (
133 <div className="betterdocs-row">
134 <div className="betterdocs-col betterdocs-header-left betterdocs-col-xs-6">
135 <h2 className="betterdocs-dashboard-header-title">
136 {applyFilters(
137 "betterdocs_admin_header_title",
138 __("All Docs", "betterdocs"),
139 page,
140 )}
141 </h2>
142 {/* Per-page count badge rendered via filter */}
143 {applyFilters("betterdocs_admin_header_count", null, page)}
144 {/* The General FAQ / WooCommerce tab strip only makes sense
145 when WooCommerce is active — without it there is nothing to
146 switch to, so render no tabs and show the builder directly. */}
147 {page === "betterdocs-faq" && wcActive && (
148 <div className="betterdocs-faq-page-tabs" role="tablist">
149 <button
150 type="button"
151 role="tab"
152 aria-selected={faqTab === "builder"}
153 className={`betterdocs-faq-page-tab${faqTab === "builder" ? " active" : ""}`}
154 onClick={() =>
155 history.push(makeRouteURL("betterdocs-faq", { faq_tab: "builder" }))
156 }
157 >
158 {__("General FAQ", "betterdocs")}
159 </button>
160 <button
161 type="button"
162 role="tab"
163 aria-selected={faqTab === "woocommerce"}
164 className={`betterdocs-faq-page-tab${faqTab === "woocommerce" ? " active" : ""}`}
165 onClick={() =>
166 history.push(makeRouteURL("betterdocs-faq", { faq_tab: "woocommerce" }))
167 }
168 >
169 {__("WooCommerce", "betterdocs")}
170 </button>
171 </div>
172 )}
173 {(page == 'betterdocs-admin' && !isLoading && totalItems > 0) && (
174 <>
175 <p className="trash-line"></p>
176 <Link className="header-trash" to={makeRouteURL("betterdocs-admin", {
177 mode: "list",
178 post_status: "trash"
179 })}> {sprintf(__("Trash (%d)", "betterdocs"), totalItems)} </Link>
180 </>
181 )}
182 </div>
183 <div className="betterdocs-col betterdocs-header-right betterdocs-col-xs-6 betterdocs-d-flex betterdocs-justify-end">
184 {/* Multiple-KB filter (Pro) — styled like the FAQ order select, in the header-right control group. */}
185 {betterdocs_admin?.multiple_kb == 1 && applyFilters(
186 "betterdocs_kb_filter_section",
187 '',
188 [KnowledgeBaseFilter, mkbFilter, setMkbFilterValue, page, postStatus],
189 )}
190 {hasSearch && (
191 <div className="betterdocs-header-search-field">
192 <input
193 type="text"
194 placeholder={searchPlaceholder}
195 value={searchValue}
196 onChange={searchHandler}
197 className="search-input"
198 />
199 <span className="betterdocs-header-search-icon">
200 <svg xmlns="http://www.w3.org/2000/svg" width="38" viewBox="0 0 50 50"><path d="M21 3C11.602 3 4 10.602 4 20s7.602 17 17 17c3.355 0 6.46-.984 9.094-2.656l12.281 12.281 4.25-4.25L34.5 30.281C36.68 27.421 38 23.88 38 20c0-9.398-7.602-17-17-17m0 4c7.2 0 13 5.8 13 13s-5.8 13-13 13S8 27.2 8 20 13.8 7 21 7" /></svg>
201 </span>
202 </div>
203 )}
204 {/* Header search slot for Pro/add-on React pages (e.g. Knowledge Base). */}
205 {!hasSearch && applyFilters("betterdocs_admin_header_search", null, page)}
206 {showFaqGroupControls && onFaqOrderChange && (
207 <div className="betterdocs-dropdown-wrapper">
208 <span className="betterdocs-dropdown-icon" aria-hidden="true">
209 <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
210 <path d="M2 4h12M4 8h8M6 12h4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/>
211 </svg>
212 </span>
213 <Select
214 className="betterdocs-dropdown-select"
215 classNamePrefix="betterdocs-dropdown"
216 value={FAQ_ORDER_OPTIONS.find(
217 (opt) => opt.value === (faqOrder || "default")
218 )}
219 options={FAQ_ORDER_OPTIONS}
220 onChange={(opt) => onFaqOrderChange(opt?.value || "default")}
221 isSearchable={false}
222 isClearable={false}
223 menuPlacement="bottom"
224 />
225 </div>
226 )}
227 {classicUIUrl && (
228 <div className="betterdocs-header-button">
229 <a
230 href={classicUIUrl}
231 className="betterdocs-dashboard-header-action betterdocs-classic-ui-btn"
232 >
233 <span><IconAutorenew /></span>{" "}
234 {__("Classic UI", "betterdocs")}
235 </a>
236 </div>
237 )}
238 <ul className="betterdocs-dashboard-header-action-list betterdocs-text-align-right">
239 {/* Mode (dark/light) switcher — same spot on every
240 screen. Page-specific extras (e.g. the All Docs
241 grid/list toggle) are prepended via the filter.
242 The Dashboard's Add FAQ/Docs buttons live in the
243 sticky logo bar above (betterdocs_admin_header_action),
244 not here. Analytics never reaches this row — the whole
245 default row is skipped for it above. */}
246 {applyFilters(
247 "betterdocs_admin_header_action_icons",
248 [
249 <li key="dark-mode-toggle">
250 <DarkModeToggle key="dark-mode-button" />
251 </li>
252 ],
253 page,
254 location,
255 mode
256 )}
257 </ul>
258 </div>
259 </div>
260 )}
261 </div>
262 </section>
263 );
264 };
265
266 export default Header;
267