connection->get( 'ask_on_search', true ) ) { // Above the results, where somebody who has just typed a question // will see it. Two hooks because there are two kinds of theme, and // the first attempt used neither of the right ones: // // - A **block** theme's search template opens with a `query-title` // block ("Search results for …"). Appending after it puts the // panel in the one place every block theme has, and it is there // even when the search found nothing — which is exactly when // somebody wants to ask a question instead. // - A **classic** theme has no blocks, so `loop_start` catches its // `search.php`. It does not fire when there are no results, // which is a limit worth knowing rather than a hook worth // forcing. // // Whichever fires first wins; `panel()` renders once per request. add_filter( 'render_block', array( $this, 'after_search_title' ), 10, 2 ); add_action( 'loop_start', array( $this, 'before_loop' ) ); } } /** * Whether Ask can work here at all. */ private function available(): bool { return $this->connection->is_connected() && (bool) $this->connection->get( 'ask_enabled', true ); } /** * `[sharelyai_ask]`. * * @param array|string $atts Shortcode attributes. * * @return string */ public function shortcode( $atts = array() ): string { $atts = shortcode_atts( array( 'question' => '', 'placeholder' => '', ), is_array( $atts ) ? $atts : array(), self::SHORTCODE ); // An author who wrote `question="…"` wants it answered; that is the only // reason to put it in the shortcode. $question = trim( (string) $atts['question'] ); $ask_now = '' !== $question; return $this->panel( $question, (string) $atts['placeholder'], '', $ask_now ); } /** * The panel after a block theme's "Search results for …" heading. * * @param string $content The rendered block. * @param array $block The block, parsed. * * @return string */ public function after_search_title( $content, $block ): string { $content = (string) $content; if ( ! is_search() || ! is_array( $block ) ) { return $content; } if ( 'core/query-title' !== ( $block['blockName'] ?? '' ) ) { return $content; } // Filled in, not asked. The setting this honours says "Show it above // the search results", which is what it now does. return $content . $this->panel( get_search_query() ); } /** * The panel above a classic theme's search loop. * * @param \WP_Query $query The query starting its loop. */ public function before_loop( $query ): void { if ( ! $query instanceof \WP_Query || ! $query->is_search() || ! $query->is_main_query() || is_admin() ) { return; } // Only a theme that never fired the archive-description filter gets it // here; `panel()` returns nothing the second time it is asked. echo wp_kses_post( $this->panel( get_search_query() ) ); } /** * The markup, once. * * @param string $question What the box starts with. Filled in, not asked * — see $ask_now. * @param string $placeholder Placeholder for the box. * @param string $wrapper Attributes for the outer element, already * escaped — what `get_block_wrapper_attributes()` * returns when a block is what is rendering this. * Empty everywhere else: a shortcode has no block * supports to carry. * @param bool $ask_now Answer it on load, without the visitor asking. * Only ever true because an author said so: the * block's "Ask this straight away" field, or the * shortcode's `question` attribute. A question * that merely arrived from a search is filled in * and left alone — see the note below. * * @return string */ public function panel( string $question = '', string $placeholder = '', string $wrapper = '', bool $ask_now = false ): string { if ( $this->rendered || ! $this->available() ) { return ''; } $this->rendered = true; $this->enqueue(); wp_enqueue_script( self::SCRIPT ); wp_enqueue_style( self::STYLE ); // Core's Search block styles the box; see Blocks::THEME_STYLE. A block // theme ships that stylesheet per-block, so a page with this panel and // no Search block on it has to ask for it. if ( wp_style_is( Blocks::THEME_STYLE, 'registered' ) ) { wp_enqueue_style( Blocks::THEME_STYLE ); } $placeholder = '' !== $placeholder ? $placeholder : __( 'Ask a question about this site…', 'sharelyai' ); // Unique per panel. One hard-coded id meant that a page with the block // and the shortcode on it had two boxes whose labels both pointed at // the first one, and a screen reader could not tell them apart. $input_id = wp_unique_id( 'sai-ask-input-' ); $wrapper = '' !== $wrapper ? $wrapper : 'class="sai-ask"'; // The box is answered by script, so this form is never really // submitted — except when the script has not arrived, or failed, and // somebody presses Enter. A form with no action and no fields submits // to the current URL with an empty query string, which on a site with // plain permalinks is the home page: the visitor asks a question and // is thrown off the page. Naming the page it is on, and carrying the // fields that identify it, makes that keystroke a reload instead. // // The input deliberately has no `name`. Giving it one would put the // question in the URL, and on the theme's own search page that would // replace `s` and stop the page being a search at all. list( $action, $carried ) = Blocks::target(); ob_start(); ?>
data-sharelyai-ask data-token-url="" data-question="" >
__( 'Thinking…', 'sharelyai' ), 'unavailable' => __( 'Answers are not available right now. The search results below still work.', 'sharelyai' ), 'sources' => __( 'Sources', 'sharelyai' ), 'stop' => __( 'Stop', 'sharelyai' ), 'ask' => __( 'Ask', 'sharelyai' ), ) ); } if ( ! wp_style_is( self::STYLE, 'registered' ) ) { wp_register_style( self::STYLE, PLUGIN_URL . 'assets/css/ask.css', array(), Assets::version( 'assets/css/ask.css' ) ); } } }