summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--assets/css/search.css91
-rw-r--r--locales/en-US.json69
-rw-r--r--src/invidious/frontend/search_filters.cr135
3 files changed, 264 insertions, 31 deletions
diff --git a/assets/css/search.css b/assets/css/search.css
new file mode 100644
index 00000000..ad2b0b16
--- /dev/null
+++ b/assets/css/search.css
@@ -0,0 +1,91 @@
+summary {
+ /* This should hide the marker */
+ display: block;
+
+ font-size: 1.17em;
+ font-weight: bold;
+ margin: 0 auto 10px auto;
+}
+
+summary::-webkit-details-marker,
+summary::marker { display: none; }
+
+summary:before {
+ border-radius: 5px;
+ content: "[ + ]";
+ margin: -2px 10px 0 10px;
+ padding: 1px 0 3px 0;
+ text-align: center;
+ width: 40px;
+}
+
+details[open] > summary:before { content: "[ ‒ ]"; }
+
+
+#filters-box {
+ background: #373737;
+ padding: 10px 20px 20px 10px;
+ margin: 10px 15px;
+}
+#filters-flex {
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: row;
+ align-items: flex-start;
+ align-content: flex-start;
+ justify-content: flex-start;
+}
+
+
+fieldset, legend {
+ display: contents !important;
+ border: none !important;
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+
+.filter-column {
+ display: inline-block;
+ display: inline-flex;
+ width: max-content;
+ min-width: max-content;
+ max-width: 16em;
+ margin: 15px;
+ flex-grow: 2;
+ flex-basis: auto;
+ flex-direction: column;
+}
+.filter-name, .filter-options {
+ display: block;
+ padding: 5px 10px;
+ margin: 0;
+ text-align: start;
+}
+
+/* TODO: move that to the main file */
+.underlined {
+ border-bottom: 1px solid;
+ margin-bottom: 20px;
+}
+
+
+.filter-options div { margin: 6px 0; }
+.filter-options div * { vertical-align: middle; }
+.filter-options label { margin: 0 10px; }
+
+
+#filters-apply { text-align: end; }
+
+
+@media only screen and (max-width: 800px) {
+ summary { font-size: 1.30em; }
+ #filters-box {
+ margin: 10px 0 0 0;
+ padding: 0;
+ }
+ #filters-apply {
+ text-align: center;
+ padding: 15px;
+ }
+}
diff --git a/locales/en-US.json b/locales/en-US.json
index a78d8062..03df88b6 100644
--- a/locales/en-US.json
+++ b/locales/en-US.json
@@ -404,37 +404,44 @@
"Videos": "Videos",
"Playlists": "Playlists",
"Community": "Community",
- "relevance": "Relevance",
- "rating": "Rating",
- "date": "Upload date",
- "views": "View count",
- "content_type": "Type",
- "duration": "Duration",
- "features": "Features",
- "sort": "Sort By",
- "hour": "Last Hour",
- "today": "Today",
- "week": "This week",
- "month": "This month",
- "year": "This year",
- "video": "Video",
- "channel": "Channel",
- "playlist": "Playlist",
- "movie": "Movie",
- "show": "Show",
- "short": "Short (< 4 minutes)",
- "long": "Long (> 20 minutes)",
- "hd": "HD",
- "subtitles": "Subtitles/CC",
- "creative_commons": "Creative Commons",
- "3d": "3D",
- "live": "Live",
- "4k": "4K",
- "location": "Location",
- "hdr": "HDR",
- "purchased": "Purchased",
- "360": "360°",
- "filter": "Filter",
+ "search_filters_title": "Filters",
+ "search_filters_date_label": "Upload date",
+ "search_filters_date_option_none": "Any date",
+ "search_filters_date_option_hour": "Last Hour",
+ "search_filters_date_option_today": "Today",
+ "search_filters_date_option_week": "This week",
+ "search_filters_date_option_month": "This month",
+ "search_filters_date_option_year": "This year",
+ "search_filters_type_label": "Type",
+ "search_filters_type_option_all": "Any type",
+ "search_filters_type_option_video": "Video",
+ "search_filters_type_option_channel": "Channel",
+ "search_filters_type_option_playlist": "Playlist",
+ "search_filters_type_option_movie": "Movie",
+ "search_filters_type_option_show": "Show",
+ "search_filters_duration_label": "Duration",
+ "search_filters_duration_option_none": "Any duration",
+ "search_filters_duration_option_short": "Short (< 4 minutes)",
+ "search_filters_duration_option_medium": "Medium (4 - 20 minutes)",
+ "search_filters_duration_option_long": "Long (> 20 minutes)",
+ "search_filters_features_label": "Features",
+ "search_filters_features_option_live": "Live",
+ "search_filters_features_option_four_k": "4K",
+ "search_filters_features_option_hd": "HD",
+ "search_filters_features_option_subtitles": "Subtitles/CC",
+ "search_filters_features_option_c_commons": "Creative Commons",
+ "search_filters_features_option_three_sixty": "360°",
+ "search_filters_features_option_vr180": "VR180",
+ "search_filters_features_option_three_d": "3D",
+ "search_filters_features_option_hdr": "HDR",
+ "search_filters_features_option_location": "Location",
+ "search_filters_features_option_purchased": "Purchased",
+ "search_filters_sort_label": "Sort By",
+ "search_filters_sort_option_relevance": "Relevance",
+ "search_filters_sort_option_rating": "Rating",
+ "search_filters_sort_option_date": "Upload Date",
+ "search_filters_sort_option_views": "View count",
+ "search_filters_apply_button": "Apply selected filters",
"Current version: ": "Current version: ",
"next_steps_error_message": "After which you should try to: ",
"next_steps_error_message_refresh": "Refresh",
diff --git a/src/invidious/frontend/search_filters.cr b/src/invidious/frontend/search_filters.cr
new file mode 100644
index 00000000..68f27b4f
--- /dev/null
+++ b/src/invidious/frontend/search_filters.cr
@@ -0,0 +1,135 @@
+module Invidious::Frontend::SearchFilters
+ extend self
+
+ # Generate the search filters collapsable widget.
+ def generate(filters : Search::Filters, query : String, page : Int, locale : String) : String
+ return String.build(8000) do |str|
+ str << "<div id='filters'>\n"
+ str << "\t<details id='filters-collapse'>"
+ str << "\t\t<summary>" << translate(locale, "search_filters_title") << "</summary>\n"
+
+ str << "\t\t<div id='filters-box'><form action='/search' method='get'>\n"
+
+ str << "\t\t\t<input type='hidden' name='q' value='" << HTML.escape(query) << "'>\n"
+ str << "\t\t\t<input type='hidden' name='page' value='" << page << "'>\n"
+
+ str << "\t\t\t<div id='filters-flex'>"
+
+ filter_wrapper(date)
+ filter_wrapper(type)
+ filter_wrapper(duration)
+ filter_wrapper(features)
+ filter_wrapper(sort)
+
+ str << "\t\t\t</div>\n"
+
+ str << "\t\t\t<div id='filters-apply'>"
+ str << "<button type='submit' class=\"pure-button pure-button-primary\">"
+ str << translate(locale, "search_filters_apply_button")
+ str << "</button></div>\n"
+
+ str << "\t\t</form></div>\n"
+
+ str << "\t</details>\n"
+ str << "</div>\n"
+ end
+ end
+
+ # Generate wrapper HTML (`<div>`, filter name, etc...) around the
+ # `<input>` elements of a search filter
+ macro filter_wrapper(name)
+ str << "\t\t\t\t<div class=\"filter-column\"><fieldset>\n"
+
+ str << "\t\t\t\t\t<legend><div class=\"filter-name underlined\">"
+ str << translate(locale, "search_filters_{{name}}_label")
+ str << "</div></legend>\n"
+
+ str << "\t\t\t\t\t<div class=\"filter-options\">\n"
+ make_{{name}}_filter_options(str, filters.{{name}}, locale)
+ str << "\t\t\t\t\t</div>"
+
+ str << "\t\t\t\t</fieldset></div>\n"
+ end
+
+ # Generates the HTML for the list of radio buttons of the "date" search filter
+ def make_date_filter_options(str : String::Builder, value : Search::Filters::Date, locale : String)
+ {% for value in Invidious::Search::Filters::Date.constants %}
+ {% date = value.underscore %}
+
+ str << "\t\t\t\t\t\t<div>"
+ str << "<input type='radio' name='date' id='filter-date-{{date}}' value='{{date}}'"
+ str << " checked" if value.{{date}}?
+ str << '>'
+
+ str << "<label for='filter-date-{{date}}'>"
+ str << translate(locale, "search_filters_date_option_{{date}}")
+ str << "</label></div>\n"
+ {% end %}
+ end
+
+ # Generates the HTML for the list of radio buttons of the "type" search filter
+ def make_type_filter_options(str : String::Builder, value : Search::Filters::Type, locale : String)
+ {% for value in Invidious::Search::Filters::Type.constants %}
+ {% type = value.underscore %}
+
+ str << "\t\t\t\t\t\t<div>"
+ str << "<input type='radio' name='type' id='filter-type-{{type}}' value='{{type}}'"
+ str << " checked" if value.{{type}}?
+ str << '>'
+
+ str << "<label for='filter-type-{{type}}'>"
+ str << translate(locale, "search_filters_type_option_{{type}}")
+ str << "</label></div>\n"
+ {% end %}
+ end
+
+ # Generates the HTML for the list of radio buttons of the "duration" search filter
+ def make_duration_filter_options(str : String::Builder, value : Search::Filters::Duration, locale : String)
+ {% for value in Invidious::Search::Filters::Duration.constants %}
+ {% duration = value.underscore %}
+
+ str << "\t\t\t\t\t\t<div>"
+ str << "<input type='radio' name='duration' id='filter-duration-{{duration}}' value='{{duration}}'"
+ str << " checked" if value.{{duration}}?
+ str << '>'
+
+ str << "<label for='filter-duration-{{duration}}'>"
+ str << translate(locale, "search_filters_duration_option_{{duration}}")
+ str << "</label></div>\n"
+ {% end %}
+ end
+
+ # Generates the HTML for the list of checkboxes of the "features" search filter
+ def make_features_filter_options(str : String::Builder, value : Search::Filters::Features, locale : String)
+ {% for value in Invidious::Search::Filters::Features.constants %}
+ {% if value.stringify != "All" && value.stringify != "None" %}
+ {% feature = value.underscore %}
+
+ str << "\t\t\t\t\t\t<div>"
+ str << "<input type='checkbox' name='features' id='filter-features-{{feature}}' value='{{feature}}'"
+ str << " checked" if value.{{feature}}?
+ str << '>'
+
+ str << "<label for='filter-feature-{{feature}}'>"
+ str << translate(locale, "search_filters_features_option_{{feature}}")
+ str << "</label></div>\n"
+ {% end %}
+ {% end %}
+ end
+
+ # Generates the HTML for the list of radio buttons of the "sort" search filter
+ def make_sort_filter_options(str : String::Builder, value : Search::Filters::Sort, locale : String)
+ {% for value in Invidious::Search::Filters::Sort.constants %}
+ {% sort = value.underscore %}
+
+ str << "\t\t\t\t\t\t<div>"
+ str << "<input type='radio' name='sort' id='filter-sort-{{sort}}' value='{{sort}}'"
+ str << " checked" if value.{{sort}}?
+ str << '>'
+
+ str << "<label for='filter-sort-{{sort}}'>"
+ str << translate(locale, "search_filters_sort_option_{{sort}}")
+ str << "</label></div>\n"
+ {% end %}
+ end
+end