583 lines
No EOL
19 KiB
HTML
583 lines
No EOL
19 KiB
HTML
<!DOCTYPE html5>
|
|
<html>
|
|
<head>
|
|
{% import "photo_card.html" as photo_card %}
|
|
{% import "header.html" as header %}
|
|
<title>Search</title>
|
|
<meta charset="UTF-8">
|
|
<link rel="stylesheet" href="/static/common.css">
|
|
<script src="/static/common.js"></script>
|
|
|
|
<style>
|
|
form
|
|
{
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
|
|
width: 100%;
|
|
}
|
|
#error_message_area
|
|
{
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
.search_warning
|
|
{
|
|
align-self: center;
|
|
|
|
padding: 2px;
|
|
|
|
background-color: #f00;
|
|
|
|
color: #fff;
|
|
}
|
|
#left, #right
|
|
{
|
|
/* Keep the prev-next buttons from scraping the floor */
|
|
/*padding-bottom: 30px;*/
|
|
}
|
|
#left
|
|
{
|
|
flex: 1;
|
|
|
|
padding: 8px;
|
|
max-width: 300px;
|
|
min-width: 300px;
|
|
width: 300px;
|
|
|
|
background-color: rgba(0, 0, 0, 0.1);
|
|
|
|
word-wrap: break-word;
|
|
}
|
|
#right
|
|
{
|
|
flex: 1;
|
|
|
|
padding: 8px;
|
|
|
|
width: auto;
|
|
}
|
|
@media screen and (max-width: 800px) {
|
|
#content_body
|
|
{
|
|
flex-direction: column-reverse;
|
|
}
|
|
#left
|
|
{
|
|
max-width: none;
|
|
width: initial;
|
|
}
|
|
}
|
|
.prev_next_holder
|
|
{
|
|
display: flex;
|
|
flex-direction: row;
|
|
}
|
|
.prev_page
|
|
{
|
|
margin-right: 10px;
|
|
}
|
|
.next_page
|
|
{
|
|
margin-left: 10px;
|
|
}
|
|
.prev_page, .next_page
|
|
{
|
|
display: flex;
|
|
flex: 1;
|
|
justify-content: center;
|
|
|
|
border: 1px solid black;
|
|
|
|
background-color: #ffffd4;
|
|
|
|
font-size: 20;
|
|
}
|
|
|
|
.search_builder_tagger,
|
|
#search_builder_orderby_ul
|
|
{
|
|
margin: 0;
|
|
}
|
|
</style>
|
|
|
|
{% macro prev_next_buttons() %}
|
|
{% if prev_page_url or next_page_url %}
|
|
<div class="prev_next_holder">
|
|
{% if prev_page_url %}
|
|
<a class="prev_page" href="{{prev_page_url}}">Previous</a>
|
|
{% else %}
|
|
<a class="prev_page"><br></a>
|
|
{% endif %}
|
|
{% if next_page_url %}
|
|
<a class="next_page" href="{{next_page_url}}">Next</a>
|
|
{% else %}
|
|
<a class="next_page"><br></a>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
{% endmacro %}
|
|
|
|
{% macro create_orderby_li(selected_column, selected_sorter) %}
|
|
<li class="search_builder_orderby_li">
|
|
<select>
|
|
<option value="created" {%if selected_column=="created"%}selected{%endif%} >Creation date</option>
|
|
<option value="area" {%if selected_column=="area"%}selected{%endif%} >Area</option>
|
|
<option value="width" {%if selected_column=="width"%}selected{%endif%} >Width</option>
|
|
<option value="height" {%if selected_column=="height"%}selected{%endif%} >Height</option>
|
|
<option value="ratio" {%if selected_column=="ratio"%}selected{%endif%} >Aspect Ratio</option>
|
|
<option value="bytes" {%if selected_column=="bytes"%}selected{%endif%} >File size</option>
|
|
<option value="duration" {%if selected_column=="duration"%}selected{%endif%} >Duration</option>
|
|
<option value="tagged_at" {%if selected_column=="tagged_at"%}selected{%endif%}>Recently tagged</option>
|
|
<option value="random" {%if selected_column=="random"%}selected{%endif%} >Random</option>
|
|
</select>
|
|
<select>
|
|
<option value="desc" {%if selected_sorter=="desc"%}selected{%endif%} >Descending</option>
|
|
<option value="asc" {%if selected_sorter=="asc"%}selected{%endif%} >Ascending</option>
|
|
</select>
|
|
<button class="remove_tag_button_perm" onclick="orderby_remove_hook(this);"></button>
|
|
</li>
|
|
{% endmacro %}
|
|
</head>
|
|
|
|
|
|
<body>
|
|
{{header.make_header(session=session)}}
|
|
<div id="error_message_area">
|
|
{% for warning in warnings %}
|
|
<span class="search_warning">{{warning}}</span>
|
|
{% endfor %}
|
|
</div>
|
|
<div id="content_body">
|
|
<div id="left">
|
|
{% for tagtype in ["musts", "mays", "forbids"] %}
|
|
<div id="search_builder_{{tagtype}}" {% if search_kwargs["tag_expression"]%}style="display:none"{%endif%}>
|
|
<span>Tag {{tagtype}}:</span>
|
|
<ul class="search_builder_tagger">
|
|
{% set key="tag_" + tagtype %}
|
|
{% if search_kwargs[key] %}
|
|
{% for tagname in search_kwargs[key] %}
|
|
<li class="search_builder_{{tagtype}}_inputted">
|
|
<span class="tag_object">{{tagname}}</span><!--
|
|
--><button class="remove_tag_button"
|
|
onclick="remove_searchtag(this, '{{tagname}}', inputted_{{tagtype}});"></button>
|
|
</li>
|
|
{% endfor %}
|
|
{% endif %}
|
|
<li><input id="search_builder_{{tagtype}}_input" type="text"></li>
|
|
</ul>
|
|
</div>
|
|
{% endfor %}
|
|
<div id="search_builder_expression" {% if not search_kwargs["tag_expression"]%}style="display:none"{%endif%}>
|
|
<span>Tag Expression:</span>
|
|
<input id="search_builder_expression_input" name="tag_expression" type="text"
|
|
{% if search_kwargs["tag_expression"] %}
|
|
value="{{search_kwargs["tag_expression"]}}"
|
|
{% endif %}
|
|
>
|
|
</div>
|
|
<div id="search_builder_orderby">
|
|
<span>Order by</span>
|
|
<ul id="search_builder_orderby_ul">
|
|
{% if "orderby" in search_kwargs and search_kwargs["orderby"] %}
|
|
{% for orderby in search_kwargs["orderby"] %}
|
|
{% set column, sorter=orderby.split("-") %}
|
|
{{ create_orderby_li(selected_column=column, selected_sorter=sorter) }}
|
|
{% endfor %}
|
|
{% else %}
|
|
{{ create_orderby_li(selected_column=0, selected_sorter=0) }}
|
|
{% endif %}
|
|
<li id="search_builder_orderby_newrow"><button class="add_tag_button" onclick="add_new_orderby()">+</button></li>
|
|
</ul>
|
|
</div>
|
|
<br>
|
|
<form id="search_builder_form" action="" onsubmit="return submit_search();">
|
|
|
|
<span>Min-max values</span>
|
|
<input type="text" class="basic_param"
|
|
value="{%if search_kwargs['area']%}{{search_kwargs['area']}}{%endif%}"
|
|
name="area" placeholder="Area: 1m-2m">
|
|
|
|
<input type="text" class="basic_param"
|
|
value="{%if search_kwargs['width']%}{{search_kwargs['width']}}{%endif%}"
|
|
name="width" placeholder="Width: 1k-2k">
|
|
|
|
<input type="text" class="basic_param"
|
|
value="{%if search_kwargs['height']%}{{search_kwargs['height']}}{%endif%}"
|
|
name="height" placeholder="Height: 1k-2k">
|
|
|
|
<input type="text" class="basic_param"
|
|
value="{%if search_kwargs['ratio']%}{{search_kwargs['ratio']}}{%endif%}"
|
|
name="ratio" placeholder="Aspect Ratio: 1.7-2">
|
|
|
|
<input type="text" class="basic_param"
|
|
value="{%if search_kwargs['bytes']%}{{search_kwargs['bytes']}}{%endif%}"
|
|
name="bytes" placeholder="File Size: 1mb-2mb">
|
|
|
|
<input type="text" class="basic_param"
|
|
value="{%if search_kwargs['duration']%}{{search_kwargs['duration']}}{%endif%}"
|
|
name="duration" placeholder="Duration: 10:00-20:00">
|
|
|
|
<br>
|
|
|
|
<span>Other filters</span>
|
|
<input type="text" class="basic_param"
|
|
value="{%if search_kwargs['filename']%}{{search_kwargs['filename']}}{%endif%}"
|
|
name="filename" placeholder="Filename">
|
|
|
|
<input type="text" class="basic_param"
|
|
value="{%if search_kwargs['mimetype']%}{{search_kwargs['mimetype']}}{%endif%}"
|
|
name="mimetype" placeholder="Mimetype(s)">
|
|
|
|
<input type="text" class="basic_param"
|
|
value="{%if search_kwargs['extension']%}{{search_kwargs['extension']}}{%endif%}"
|
|
name="extension" placeholder="Extension(s)">
|
|
|
|
<input type="text" class="basic_param"
|
|
value="{%if search_kwargs['extension_not']%}{{search_kwargs['extension_not']}}{%endif%}"
|
|
name="extension_not" placeholder="Forbid extension(s)">
|
|
|
|
<select name="limit" class="basic_param">
|
|
{% set limit_options = [20, 50, 100] %}
|
|
{% if search_kwargs['limit'] not in limit_options %}
|
|
{% do limit_options.append(search_kwargs['limit']) %}
|
|
{% do limit_options.sort() %}
|
|
{% endif %}
|
|
{% for limit_option in limit_options %}
|
|
<option
|
|
value="{{limit_option}}"
|
|
{{"selected" if search_kwargs['limit'] == limit_option else ""}}
|
|
>
|
|
{{limit_option}} items
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
<select name="has_tags" class="basic_param">
|
|
<option value="" {%if search_kwargs['has_tags'] == None %}selected{%endif%}>Tagged and untagged</option>
|
|
<option value="yes"{%if search_kwargs['has_tags'] == True %}selected{%endif%}>Tagged only</option>
|
|
<option value="no" {%if search_kwargs['has_tags'] == False %}selected{%endif%}>Untagged only</option>
|
|
</select>
|
|
<select name="view" class="basic_param">
|
|
<option value="grid" {%if search_kwargs['view'] == "grid" %}selected{%endif%}>Grid</option>
|
|
<option value="list" {%if search_kwargs['view'] == "list" %}selected{%endif%}>List</option>
|
|
</select>
|
|
<button type="submit" id="search_go_button" value="">Search</button>
|
|
</form>
|
|
{% if total_tags %}
|
|
<span>Tags on this page (click to join query):</span>
|
|
<ul>
|
|
{% for tag in total_tags %}
|
|
<li><a href="javascript:void(0)" title="{{tag.description}}" class="tag_object tags_on_this_page">{{tag.name}}</a></li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% endif %}
|
|
</div>
|
|
<div id="right">
|
|
<p>You got {{photos|length}} items</p>
|
|
{{prev_next_buttons()}}
|
|
<div id="search_results_holder">
|
|
{% for photo in photos %}
|
|
{{photo_card.create_photo_card(photo, view=search_kwargs["view"])}}
|
|
{% endfor %}
|
|
</div>
|
|
|
|
{{prev_next_buttons()}}
|
|
</div>
|
|
</div>
|
|
</body>
|
|
|
|
|
|
<script type="text/javascript">
|
|
/*
|
|
These are defined so that we know we don't need to include them in the
|
|
constructed search URL, keeping it more tidy.
|
|
*/
|
|
PARAM_DEFAULTS = {
|
|
'limit': 50,
|
|
'view': 'grid',
|
|
}
|
|
function add_searchtag(box, value, inputted_list, li_class)
|
|
{
|
|
/*
|
|
Called by hitting Enter within a must/may/forbid field. Checks whether the
|
|
tag exists and adds it to the query.
|
|
*/
|
|
if (box.offsetParent === null)
|
|
{
|
|
// The box is hidden probably because we're in Expression mode.
|
|
return;
|
|
}
|
|
console.log("adding " + value);
|
|
var already_have = false;
|
|
// We're going to be doing some in-place splicing to remove,
|
|
// so make a duplicate for iterating
|
|
existing_tags = Array.from(inputted_list);
|
|
for (var index = 0; index < existing_tags.length; index += 1)
|
|
{
|
|
existing_tag = existing_tags[index];
|
|
if (existing_tag == value)
|
|
{
|
|
already_have = true;
|
|
}
|
|
else if (existing_tag.startsWith(value + ".") || value.startsWith(existing_tag + "."))
|
|
{
|
|
remove_searchtag(box, existing_tag, inputted_list);
|
|
}
|
|
}
|
|
if (!already_have)
|
|
{
|
|
inputted_list.push(value);
|
|
var new_li = document.createElement("li");
|
|
new_li.className = li_class;
|
|
|
|
var new_span = document.createElement("span");
|
|
new_span.className = "tag_object";
|
|
new_span.innerHTML = value;
|
|
|
|
var new_delbutton = document.createElement("button")
|
|
new_delbutton.className = "remove_tag_button";
|
|
new_delbutton.onclick = function(){remove_searchtag(new_delbutton, value, inputted_list)};
|
|
|
|
new_li.appendChild(new_span);
|
|
new_li.appendChild(new_delbutton);
|
|
|
|
box_li = box.parentElement;
|
|
ul = box_li.parentElement;
|
|
ul.insertBefore(new_li, box_li);
|
|
}
|
|
}
|
|
function remove_searchtag(li_member, value, inputted_list)
|
|
{
|
|
/*
|
|
Given a member of the same tag type as the one we intend to remove,
|
|
find the tag of interest and remove it from both the DOM and the
|
|
inputted_list.
|
|
|
|
Sorry for the roundabout technique.
|
|
*/
|
|
console.log("removing " + value);
|
|
var li = li_member.parentElement;
|
|
var ul = li.parentElement;
|
|
var lis = ul.children;
|
|
//console.log(lis);
|
|
for (var index = 0; index < lis.length; index += 1)
|
|
{
|
|
li = lis[index];
|
|
var span = li.children[0];
|
|
if (span.tagName != "SPAN")
|
|
{continue}
|
|
|
|
var tagname = span.innerHTML;
|
|
if (tagname != value)
|
|
{continue}
|
|
|
|
ul.removeChild(li);
|
|
splice_at = inputted_list.indexOf(tagname);
|
|
if (splice_at == -1)
|
|
{continue}
|
|
|
|
inputted_list.splice(splice_at, 1);
|
|
}
|
|
}
|
|
function add_new_orderby()
|
|
{
|
|
/* Called by the green + button */
|
|
var ul = document.getElementById("search_builder_orderby_ul");
|
|
if (ul.children.length >= 9)
|
|
{
|
|
/* 9 because there are only 9 sortable properties */
|
|
return;
|
|
}
|
|
var li = ul.children;
|
|
li = li[li.length - 2];
|
|
var clone_children = true;
|
|
var new_li = li.cloneNode(clone_children)
|
|
var button = document.getElementById("search_builder_orderby_newrow");
|
|
ul.insertBefore(new_li, button);
|
|
}
|
|
function orderby_remove_hook(button)
|
|
{
|
|
/* Called by the red button next to orderby dropdowns */
|
|
var li = button.parentElement;
|
|
var ul = li.parentElement;
|
|
// 2 because keep 1 row and the adder button
|
|
if (ul.children.length>2)
|
|
{
|
|
/* You can't remove the only one left */
|
|
ul.removeChild(li);
|
|
}
|
|
}
|
|
function simplify_tagnames(tags)
|
|
{
|
|
var new_tags = [];
|
|
for (var index = 0; index < tags.length; index += 1)
|
|
{
|
|
var tag = tags[index];
|
|
tag = tag.split(".");
|
|
tag = tag[tag.length - 1];
|
|
new_tags.push(tag);
|
|
}
|
|
return new_tags;
|
|
}
|
|
function submit_search()
|
|
{
|
|
/*
|
|
Gather up all the form data and tags and compose the search URL
|
|
*/
|
|
var url = window.location.origin + "/search";
|
|
var parameters = [];
|
|
var has_tag_params = false;
|
|
var musts = simplify_tagnames(inputted_musts).join(",");
|
|
if (musts) {parameters.push("tag_musts=" + musts); has_tag_params=true;}
|
|
|
|
var mays = simplify_tagnames(inputted_mays).join(",");
|
|
if (mays) {parameters.push("tag_mays=" + mays); has_tag_params=true;}
|
|
|
|
var forbids = simplify_tagnames(inputted_forbids).join(",");
|
|
if (forbids) {parameters.push("tag_forbids=" + forbids); has_tag_params=true;}
|
|
|
|
var expression = document.getElementsByName("tag_expression")[0].value;
|
|
if (expression)
|
|
{
|
|
//expression = expression.replace(new RegExp(" ", 'g'), "-");
|
|
parameters.push("tag_expression=" + expression);
|
|
has_tag_params=true;
|
|
}
|
|
|
|
var basic_inputs = document.getElementsByClassName("basic_param");
|
|
for (var index = 0; index < basic_inputs.length; index += 1)
|
|
{
|
|
var boxname = basic_inputs[index].name;
|
|
var box = document.getElementsByName(boxname)[0];
|
|
var value = box.value;
|
|
value = value.split("&").join("%26");
|
|
console.log(value);
|
|
if (PARAM_DEFAULTS[boxname] == value)
|
|
{
|
|
// Don't clutter url with default values.
|
|
continue;
|
|
}
|
|
if (boxname == "has_tags" && has_tag_params && value == "no")
|
|
{
|
|
/*
|
|
The user wants untagged only, but has tags in the search boxes?
|
|
Override to "tagged or untagged" and let the tag searcher handle it.
|
|
*/
|
|
value = "";
|
|
}
|
|
if (value == "")
|
|
{
|
|
continue;
|
|
}
|
|
parameters.push(boxname + "=" + value);
|
|
}
|
|
|
|
orderby_rows = document.getElementsByClassName("search_builder_orderby_li");
|
|
orderby_params = [];
|
|
for (var index = 0; index < orderby_rows.length; index += 1)
|
|
{
|
|
var row = orderby_rows[index];
|
|
var column = row.children[0].value;
|
|
var sorter = row.children[1].value;
|
|
orderby_params.push(column + "-" + sorter);
|
|
}
|
|
orderby_params = orderby_params.join(",");
|
|
if (orderby_params && orderby_params != "created-desc")
|
|
{
|
|
// Don't clutter url with default of created-desc
|
|
parameters.push("orderby=" + orderby_params);
|
|
}
|
|
|
|
if (parameters.length > 0)
|
|
{
|
|
parameters = parameters.join("&");
|
|
parameters = "?" + parameters;
|
|
url = url + parameters;
|
|
}
|
|
console.log(url);
|
|
window.location.href = url;
|
|
return false;
|
|
}
|
|
function tags_on_this_page_hook()
|
|
{
|
|
/*
|
|
This is hooked onto the tag objects listed under "Found on this page".
|
|
Clicking them will add it to your current search query under Musts
|
|
*/
|
|
add_searchtag(
|
|
input_musts,
|
|
this.innerHTML,
|
|
inputted_musts,
|
|
"search_builder_musts_inputted"
|
|
);
|
|
return false;
|
|
}
|
|
function tag_input_hook(box, inputted_list, li_class)
|
|
{
|
|
/*
|
|
Assigned to the input boxes for musts, mays, forbids.
|
|
Hitting Enter will add the resovled tag to the search form.
|
|
*/
|
|
if (event.keyCode != 13)
|
|
{return;}
|
|
|
|
if (!box.value)
|
|
{return;}
|
|
|
|
var value = box.value.toLocaleLowerCase();
|
|
value = value.split(".");
|
|
value = value[value.length-1];
|
|
value = value.split("+")[0];
|
|
value = value.replace(new RegExp(" ", 'g'), "_");
|
|
value = value.replace(new RegExp("-", 'g'), "_");
|
|
if (!(value in QUALNAME_MAP))
|
|
{
|
|
return;
|
|
}
|
|
value = QUALNAME_MAP[value];
|
|
console.log(inputted_list);
|
|
add_searchtag(box, value, inputted_list, li_class)
|
|
box.value = "";
|
|
}
|
|
|
|
|
|
QUALNAME_MAP = {{qualname_map|safe}};
|
|
var input_musts = document.getElementById("search_builder_musts_input");
|
|
var input_mays = document.getElementById("search_builder_mays_input");
|
|
var input_forbids = document.getElementById("search_builder_forbids_input");
|
|
var input_expression = document.getElementById("search_builder_expression_input");
|
|
|
|
/* Prefix the form with the parameters from last search */
|
|
var inputted_musts = [];
|
|
var inputted_mays = [];
|
|
var inputted_forbids = [];
|
|
{% for tagtype in ["musts", "mays", "forbids"] %}
|
|
{% set key="tag_" + tagtype %}
|
|
{% if search_kwargs[key] %}
|
|
{% for tagname in search_kwargs[key] %}
|
|
inputted_{{tagtype}}.push("{{tagname}}");
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
/* Assign the click handler to "Tags on this page" results. */
|
|
var found_on_page = document.getElementsByClassName("tags_on_this_page");
|
|
for (var index = 0; index < found_on_page.length; index += 1)
|
|
{
|
|
var tag_object = found_on_page[index];
|
|
if (tag_object.tagName != "A")
|
|
{continue}
|
|
|
|
tag_object.onclick = tags_on_this_page_hook;
|
|
|
|
}
|
|
|
|
input_musts.onkeydown = function(){tag_input_hook(this, inputted_musts, "search_builder_musts_inputted")};
|
|
input_mays.onkeydown = function(){tag_input_hook(this, inputted_mays, "search_builder_mays_inputted")};
|
|
input_forbids.onkeydown = function(){tag_input_hook(this, inputted_forbids, "search_builder_forbids_inputted")};
|
|
bind_box_to_button(input_expression, document.getElementById("search_go_button"));
|
|
</script>
|
|
</html> |