diff --git a/routes/css.cpp b/routes/css.cpp index 2bd4b41..9a6f206 100644 --- a/routes/css.cpp +++ b/routes/css.cpp @@ -95,6 +95,14 @@ void css_route(const httplib::Request& req, httplib::Response& res) { padding-bottom: 1em; } + /* SEARCH RESULTS PAGE */ + .searchsuggestions { + text-align: left; + display: inline-block; + margin-top: .5em; + margin-bottom: 0; + } + /* ERROR PAGE */ .error { text-align: center; diff --git a/routes/tags.cpp b/routes/tags.cpp index 10df63e..3fa34de 100644 --- a/routes/tags.cpp +++ b/routes/tags.cpp @@ -4,10 +4,14 @@ #include "../pixivclient.h" static inline Element generate_header(const httplib::Request& req, const Config& config, - const SearchResults& search_results, const std::string& query, const std::vector& tags, const std::string& order); + const SearchResults& search_results, const std::string& query, const std::vector& tags, const std::string& order, + const std::vector& search_suggestions); +static inline Element generate_search_suggestions(const httplib::Request& req, const Config& config, + const std::vector& tags, const std::vector& search_suggestions, bool open_by_default); static std::string tags_to_string(const std::unordered_map& tag_translations, const std::vector& tags); static inline std::vector split(const std::string& str, char c); +static inline std::string join(const std::vector& items, char c); void tags_route(const httplib::Request& req, httplib::Response& res, const Config& config, PixivClient& pixiv_client) { std::string query = blankie::murl::unescape(req.matches.str(1)); @@ -29,9 +33,17 @@ void tags_route(const httplib::Request& req, httplib::Response& res, const Confi serve_error(req, res, config, "500: Internal server error", "Failed to search for illusts", e.what()); return; } + std::vector search_suggestions; + try { + search_suggestions = pixiv_client.get_search_suggestions(tags.back()); + } catch (const std::exception& e) { + res.status = 500; + serve_error(req, res, config, "500: Internal server error", "Failed to get search suggestions", e.what()); + return; + } Element body("body", { - generate_header(req, config, search_results, query, tags, order), + generate_header(req, config, search_results, query, tags, order, search_suggestions), Element("br"), generate_illusts_pager(req, config, search_results.illusts, page, "illusts") }); @@ -41,7 +53,8 @@ void tags_route(const httplib::Request& req, httplib::Response& res, const Confi static inline Element generate_header(const httplib::Request& req, const Config& config, - const SearchResults& search_results, const std::string& query, const std::vector& tags, const std::string& order) { + const SearchResults& search_results, const std::string& query, const std::vector& tags, const std::string& order, + const std::vector& search_suggestions) { auto sort_element = [&](const char* title, const char* new_order) { std::string url = get_origin(req, config) + "/tags/" + blankie::murl::escape(query) + "/illustrations?order=" + new_order; Element ret("a", {{"href", std::move(url)}}, {title}); @@ -56,9 +69,13 @@ static inline Element generate_header(const httplib::Request& req, const Config& Element("input", {{"name", "q"}, {"required", ""}, {"value", query}}, {}), " ", Element("button", {"Search for illustrations"}) - }), - Element("br") + }) }); + if (!search_suggestions.empty()) { + header.nodes.push_back(generate_search_suggestions(req, config, tags, search_suggestions, search_results.illusts.total_illusts == 0)); + } + header.nodes.push_back(Element("br")); + if (search_results.illusts.total_illusts != 1) { header.nodes.push_back("There are "); header.nodes.push_back(std::to_string(search_results.illusts.total_illusts)); @@ -78,6 +95,39 @@ static inline Element generate_header(const httplib::Request& req, const Config& return header; } +static inline Element generate_search_suggestions(const httplib::Request& req, const Config& config, + const std::vector& tags, const std::vector& search_suggestions, bool open_by_default) { + std::vector ul_nodes; + ul_nodes.reserve(search_suggestions.size()); + for (const SearchSuggestion& search_suggestion : search_suggestions) { + std::string text = search_suggestion.tag; + if (search_suggestion.english_tag) { + text += " ("; + text += *search_suggestion.english_tag; + text += ')'; + } + + std::vector new_tags = tags; + new_tags.pop_back(); + new_tags.push_back(search_suggestion.tag); + std::string url = get_origin(req, config) + "/tags/" + blankie::murl::escape(join(new_tags, ' ')); + + ul_nodes.push_back(Element("li", { + Element("a", {{"href", std::move(url)}}, {std::move(text)}) + })); + } + + Element details("details", { + Element("summary", {"Search suggestions"}), + Element("ul", {{"class", "searchsuggestions"}}, ul_nodes) + }); + if (open_by_default) { + details.attributes.push_back({"open", ""}); + } + + return details; +} + static std::string tags_to_string(const std::unordered_map& tag_translations, const std::vector& tags) { @@ -112,3 +162,16 @@ static inline std::vector split(const std::string& str, char c) { return ret; } + +static inline std::string join(const std::vector& items, char c) { + std::string ret; + + for (size_t i = 0; i < items.size(); i++) { + if (i) { + ret += c; + } + ret += items[i]; + } + + return ret; +}