diff --git a/config.py b/config.py index 0ca843e..4af3922 100644 --- a/config.py +++ b/config.py @@ -5,5 +5,6 @@ default_options = { "TextFileContentLenght": "16384", "MimeGuesser": "extension", # extension, content "CheckSumCalculators": "", # md5, sha1, sha256 - } + +index_every = 50000 diff --git a/crawler.py b/crawler.py index 5879e16..88f398a 100644 --- a/crawler.py +++ b/crawler.py @@ -10,7 +10,7 @@ from search import Search from thumbnail import ThumbnailGenerator from storage import Directory import shutil - +import config class RunningTask: @@ -26,9 +26,11 @@ class RunningTask: class Crawler: - def __init__(self, enabled_parsers: list, mime_guesser: MimeGuesser=ContentMimeGuesser()): + def __init__(self, enabled_parsers: list, mime_guesser: MimeGuesser=ContentMimeGuesser(), indexer=None, dir_id=0): self.documents = [] self.enabled_parsers = enabled_parsers + self.indexer = indexer + self.dir_id = dir_id for parser in self.enabled_parsers: if parser.is_default: @@ -44,6 +46,8 @@ class Crawler: def crawl(self, root_dir: str, counter: Value=None): + document_counter = 0 + for root, dirs, files in os.walk(root_dir): for filename in files: @@ -53,6 +57,13 @@ class Crawler: parser = self.ext_map.get(mime, self.default_parser) + document_counter += 1 + if document_counter >= config.index_every: + document_counter = 0 + + self.indexer.index(self.documents, self.dir_id) + self.documents.clear() + try: if counter: counter.value += 1 @@ -64,6 +75,9 @@ class Crawler: except FileNotFoundError: continue # File was deleted + if self.indexer is not None: + self.indexer.index(self.documents, self.dir_id) + def countFiles(self, root_dir: str): count = 0 @@ -123,11 +137,9 @@ class TaskManager: MediaFileParser(chksum_calcs), TextFileParser(chksum_calcs, int(directory.get_option("TextFileContentLenght"))), PictureFileParser(chksum_calcs)], - mime_guesser) + mime_guesser, self.indexer, directory.id) c.crawl(directory.path, counter) - # todo: create indexer inside the crawler and index every X files - Indexer("changeme").index(c.documents, directory.id) done.value = 1 def execute_thumbnails(self, directory: Directory, total_files: Value, counter: Value, done: Value): diff --git a/indexer.py b/indexer.py index 9f40fa9..8a01d81 100644 --- a/indexer.py +++ b/indexer.py @@ -61,17 +61,26 @@ class Indexer: self.es.indices.create(index=self.index_name) self.es.indices.close(index=self.index_name) - self.es.indices.put_settings(body='{"analysis":{"tokenizer":{"path_tokenizer":{"type":"path_hierarchy"}}}}', index=self.index_name) - self.es.indices.put_settings(body='{"analysis":{"tokenizer":{"my_nGram_tokenizer":{"type":"nGram","min_gram":3,"max_gram":4}}}}') - self.es.indices.put_settings(body='{"analysis":{"analyzer":{"path_analyser":{"tokenizer":"path_tokenizer"}}}}') - self.es.indices.put_settings(body='{"analysis":{"analyzer":{"my_nGram":{"tokenizer":"my_nGram_tokenizer"}}}}') + self.es.indices.put_settings(body='{"analysis":{"tokenizer":{"path_tokenizer":{"type":"path_hierarchy"}}}}', + index=self.index_name) + self.es.indices.put_settings(body='{"analysis":{"tokenizer":{"my_nGram_tokenizer":{"type":"nGram","min_gram":3,"max_gram":4}}}}', + index=self.index_name) + self.es.indices.put_settings(body='{"analysis":{"analyzer":{"path_analyser":{"tokenizer":"path_tokenizer"}}}}', + index=self.index_name) + self.es.indices.put_settings(body='{"analysis":{"analyzer":{"my_nGram":{"tokenizer":"my_nGram_tokenizer", "filter": ["lowercase"]}}}}', + index=self.index_name) self.es.indices.put_mapping(body='{"properties": {' '"path": {"type": "text", "analyzer": "path_analyser", "copy_to": "suggest-path"},' '"suggest-path": {"type": "completion", "analyzer": "keyword"},' '"mime": {"type": "keyword"},' '"directory": {"type": "keyword"},' - '"name": {"analyzer": "my_nGram", "type": "text"}' + '"name": {"analyzer": "my_nGram", "type": "text"},' + '"album": {"analyzer": "my_nGram", "type": "text"},' + '"artist": {"analyzer": "my_nGram", "type": "text"},' + '"title": {"analyzer": "my_nGram", "type": "text"},' + '"genre": {"analyzer": "my_nGram", "type": "text"},' + '"album_artist": {"analyzer": "my_nGram", "type": "text"}' '}}', doc_type="file", index=self.index_name) self.es.indices.open(index=self.index_name) diff --git a/parsing.py b/parsing.py index 9a1391d..90b4e75 100644 --- a/parsing.py +++ b/parsing.py @@ -5,6 +5,7 @@ import mimetypes import subprocess import json import chardet +import html from PIL import Image class MimeGuesser: @@ -150,55 +151,55 @@ class MediaFileParser(GenericFileParser): super().__init__(checksum_calculators) self.mime_types = [ - "video/3gpp", - "video/mp4", - "video/mpeg", - "video/ogg", - "video/quicktime", - "video/webm", - "video/x-flv", - "video/x-mng", - "video/x-ms-asf", - "video/x-ms-wmv", - "video/x-msvideo", - "audio/basic", - "auido/L24", - "audio/mid", - "audio/mpeg", - "audio/mp4", - "audio/x-aiff", - "audio/ogg", - "audio/vorbis" - "audio/x-realaudio", - "audio/x-wav" + "video/3gpp", "video/mp4", "video/mpeg", "video/ogg", "video/quicktime", + "video/webm", "video/x-flv", "video/x-mng", "video/x-ms-asf", + "video/x-ms-wmv", "video/x-msvideo", "audio/basic", "auido/L24", + "audio/mid", "audio/mpeg", "audio/mp4", "audio/x-aiff", + "audio/ogg", "audio/vorbis" "audio/x-realaudio", "audio/x-wav", + "audio/flac", "audio/x-monkeys-audio", "audio/wav", "audio/wave", + "audio/x-wav", "audio/x-ms-wma" ] def parse(self, full_path: str): info = super().parse(full_path) - print("video/audio : " + full_path) + p = subprocess.Popen(["ffprobe", "-v", "quiet", "-print_format", "json=c=1", "-show_format", full_path], + stdout=subprocess.PIPE) + out, err = p.communicate() - result = subprocess.run(["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", full_path], - stdout=subprocess.PIPE) + try: + metadata = json.loads(out.decode("utf-8")) - metadata = json.loads(result.stdout.decode("utf-8")) + if "format" in metadata: - if "format" in metadata: + if "bit_rate" in metadata["format"]: + info["bit_rate"] = int(metadata["format"]["bit_rate"]) - if "bit_rate" in metadata["format"]: - info["bit_rate"] = int(metadata["format"]["bit_rate"]) + if "nb_streams" in metadata["format"]: + info["nb_streams"] = int(metadata["format"]["nb_streams"]) - if "nb_streams" in metadata["format"]: - info["nb_streams"] = int(metadata["format"]["nb_streams"]) + if "duration" in metadata["format"]: + info["duration"] = float(metadata["format"]["duration"]) - if "duration" in metadata["format"]: - info["duration"] = float(metadata["format"]["duration"]) + if "format_name" in metadata["format"]: + info["format_name"] = metadata["format"]["format_name"] - if "format_name" in metadata["format"]: - info["format_name"] = metadata["format"]["format_name"] + if "format_long_name" in metadata["format"]: + info["format_long_name"] = metadata["format"]["format_long_name"] - if "format_long_name" in metadata["format"]: - info["format_long_name"] = metadata["format"]["format_long_name"] + if "tags" in metadata["format"]: + if "genre" in metadata["format"]["tags"]: + info["genre"] = metadata["format"]["tags"]["genre"] + if "title" in metadata["format"]["tags"]: + info["title"] = metadata["format"]["tags"]["title"] + if "album" in metadata["format"]["tags"]: + info["album"] = metadata["format"]["tags"]["album"] + if "album_artist" in metadata["format"]["tags"]: + info["album_artist"] = metadata["format"]["tags"]["album_artist"] + + except json.decoder.JSONDecodeError: + print("json decode error:" + full_path) + pass return info @@ -211,60 +212,25 @@ class PictureFileParser(GenericFileParser): super().__init__(checksum_calculators) self.mime_types = [ - "image/bmp", - "image/cgm", - "image/cis-cod", - "image/g3fax", - "image/gif", - "image/ief", - "image/jpeg", - "image/ktx", - "image/pipeg", - "image/pjpeg", - "image/png", - "image/prs.btif", - "image/svg+xml", - "image/tiff", - "image/vnd.adobe.photoshop", - "image/vnd.dece.graphic", - "image/vnd.djvu", - "image/vnd.dvb.subtitle", - "image/vnd.dwg", - "image/vnd.dxf", - "image/vnd.fastbidsheet", - "image/vnd.fpx", - "image/vnd.fst", - "image/vnd.fujixerox.edmics-mmr", - "image/vnd.fujixerox.edmics-rlc", - "image/vnd.ms-modi", - "image/vnd.net-fpx", - "image/vnd.wap.wbmp", - "image/vnd.xiff", - "image/webp", - "image/x-citrix-jpeg", - "image/x-citrix-png", - "image/x-cmu-raster", - "image/x-cmx", - "image/x-freehand", - "image/x-icon", - "image/x-pcx", - "image/x-pict", - "image/x-png", - "image/x-portable-anymap", - "image/x-portable-bitmap", - "image/x-portable-graymap", - "image/x-portable-pixmap", - "image/x-rgb", - "image/x-xbitmap", - "image/x-xpixmap", - "image/x-xwindowdump" + "image/bmp", "image/cgm", "image/cis-cod", "image/g3fax", "image/gif", + "image/ief", "image/jpeg", "image/ktx", "image/pipeg", "image/pjpeg", + "image/png", "image/prs.btif", "image/svg+xml", "image/tiff", + "image/vnd.adobe.photoshop", "image/vnd.dece.graphic", "image/vnd.djvu", + "image/vnd.dvb.subtitle", "image/vnd.dwg", "image/vnd.dxf", + "image/vnd.fastbidsheet", "image/vnd.fpx", "image/vnd.fst", + "image/vnd.fujixerox.edmics-mmr", "image/vnd.fujixerox.edmics-rlc", + "image/vnd.ms-modi", "image/vnd.net-fpx", "image/vnd.wap.wbmp", + "image/vnd.xiff", "image/webp", "image/x-citrix-jpeg", "image/x-citrix-png", + "image/x-cmu-raster", "image/x-cmx", "image/x-icon", + "image/x-pcx", "image/x-pict", "image/x-png", "image/x-portable-bitmap", + "image/x-portable-graymap", "image/x-portable-pixmap", + "image/x-rgb", "image/x-xbitmap", "image/x-xpixmap", "image/x-xwindowdump" ] def parse(self, full_path: str): info = super().parse(full_path) - print("picture") try: with open(full_path, "rb") as image_file: @@ -274,8 +240,7 @@ class PictureFileParser(GenericFileParser): info["format"] = image.format info["width"] = image.width info["height"] = image.height - except OSError as e: - print(e.strerror) + except (OSError, ValueError) as e: pass return info @@ -290,58 +255,40 @@ class TextFileParser(GenericFileParser): self.content_lenght = content_lenght self.mime_types = [ - "text/asp", - "text/css", - "text/ecmascript", - "text/html", - "text/javascript", - "text/mcf", - "text/pascal", - "text/plain", - "text/richtext", - "text/scriplet", - "text/sgml", - "text/tab-separated-values", - "text/uri-list", - "text/vnd.abc", - "text/vnd.fmi.flexstor", - "text/vnd.rn-realtext", - "text/vnd.wap.wml", - "text/vnd.wap.wmlscript", - "text/webviewhtml", - "text/x-asm", - "text/x-audiosoft-intra", - "text/x-c", - "text/x-component", - "text/x-fortran", - "text/x-h", - "text/x-java-source", - "text/x-la-asf", - "text/x-m", - "text/x-pascal", - "text/x-script", - "text/x-script.csh", - "text/x-script.elisp", - "text/x-script.guile", - "text/x-script.ksh", - "text/x-script.lisp", - "text/x-script.perl", - "text/x-script.perl-module", - "text/x-script.phyton", - "text/x-script.rexx", - "text/x-script.scheme", - "text/x-script.sh", - "text/x-script.tcl", - "text/x-script.tcsh", - "text/x-script.zsh", - "text/x-server-parsed-html", - "text/x-setext", - "text/x-sgml", - "text/x-speech", - "text/x-uil", - "text/x-uuencode", - "text/x-vcalendar", - "text/xml" + "text/asp", "text/css", "text/ecmascript", "text/html", "text/javascript", + "text/mcf", "text/pascal", "text/plain", "text/richtext", "text/scriplet", + "text/sgml", "text/tab-separated-values", "text/uri-list", "text/vnd.abc", + "text/vnd.fmi.flexstor", "text/vnd.rn-realtext", "text/vnd.wap.wml", + "text/vnd.wap.wmlscript", "text/webviewhtml", "text/x-asm", "text/x-audiosoft-intra", + "text/x-c", "text/x-component", "text/x-fortran", "text/x-h", "text/x-java-source", + "text/x-la-asf", "text/x-m", "text/x-pascal", "text/x-script", + "text/x-script.csh", "text/x-script.elisp", "text/x-script.guile", + "text/x-script.ksh", "text/x-script.lisp", "text/x-script.perl", + "text/x-script.perl-module", "text/x-script.phyton", "text/x-script.rexx", + "text/x-script.scheme", "text/x-script.sh", "text/x-script.tcl", + "text/x-script.tcsh", "text/x-script.zsh", "text/x-server-parsed-html", + "text/x-setext", "text/x-sgml", "text/x-speech", "text/x-uil", + "text/x-uuencode", "text/x-vcalendar", "text/xml" + ] + + self.encodings = [ + 'ascii', 'big5', 'big5hkscs', 'cp037', 'cp273', 'cp424', 'cp437', + 'cp500', 'cp720', 'cp737', 'cp775', 'cp850', 'cp852', 'cp855', + 'cp856', 'cp857', 'cp858', 'cp860', 'cp861', 'cp862', 'cp863', + 'cp864', 'cp865', 'cp866', 'cp869', 'cp874', 'cp875', 'cp932', + 'cp949', 'cp950', 'cp1006', 'cp1026', 'cp1125', 'cp1140', + 'cp1250', 'cp1251', 'cp1252', 'cp1253', 'cp1254', 'cp1255', + 'cp1256', 'cp1257', 'cp1258', 'cp65001', 'euc_jp', 'euc_jis_2004', + 'euc_jisx0213', 'euc_kr', 'gb2312', 'gbk', 'gb18030', 'hz', 'iso2022_jp', + 'iso2022_jp_1', 'iso2022_jp_2', 'iso2022_jp_2004', 'iso2022_jp_3', + 'iso2022_jp_ext', 'iso2022_kr', 'latin_1', 'iso8859_2', 'iso8859_3', + 'iso8859_4', 'iso8859_5', 'iso8859_6', 'iso8859_7', 'iso8859_8', + 'iso8859_9', 'iso8859_10', 'iso8859_11', 'iso8859_13', 'iso8859_14', + 'iso8859_15', 'iso8859_16', 'johab', 'koi8_r', 'koi8_t', 'koi8_u', + 'kz1048', 'mac_cyrillic', 'mac_greek', 'mac_iceland', 'mac_latin2', + 'mac_roman', 'mac_turkish', 'ptcp154', 'shift_jis', 'shift_jis_2004', + 'shift_jisx0213', 'utf_32', 'utf_32_be', 'utf_32_le', 'utf_16', 'utf_16_be', + 'utf_16_le', 'utf_7', 'utf_8', 'utf_8_sig' ] def parse(self, full_path: str): @@ -355,12 +302,11 @@ class TextFileParser(GenericFileParser): chardet.detect(raw_content) encoding = chardet.detect(raw_content)["encoding"] - if encoding is not None: - - print(full_path) - print(encoding) + if encoding is not None and encoding in self.encodings: info["encoding"] = encoding - info["content"] = raw_content.decode(encoding, "ignore") + content = raw_content.decode(encoding, "ignore") + + info["content"] = html.escape(content) return info diff --git a/search.py b/search.py index c33a209..77a1783 100644 --- a/search.py +++ b/search.py @@ -22,7 +22,7 @@ class Search: def get_all_documents(self, dir_id: int): return helpers.scan(client=self.es, - query={"_source": {"includes": ["path", "name"]}, + query={"_source": {"includes": ["path", "name", "mime", "extension"]}, "query": {"term": {"directory": dir_id}}}, index=self.index_name) @@ -58,7 +58,8 @@ class Search: page = self.es.search(body={"query": {"multi_match": { "query": query, - "fields": ["name", "content"] + "fields": ["name", "content", "album", "artist", "title", "genre", "album_artist"], + "operator": "and" }}, "sort": [ "_score" @@ -74,10 +75,14 @@ class Search: "prefix": query, "completion": { "field": "suggest-path", - "skip_duplicates": True + "skip_duplicates": True, + "size": 4000 } } }, + "aggs": { + "total_size": {"sum": {"field": "size"}} + }, "size": 40}, index=self.index_name, scroll="3m") return page diff --git a/spec/TextFileParser_spec.py b/spec/TextFileParser_spec.py index e803e1a..f72005c 100644 --- a/spec/TextFileParser_spec.py +++ b/spec/TextFileParser_spec.py @@ -6,10 +6,10 @@ class TextFileParserTest(TestCase): def test_parse_csv(self): - parser = TextFileParser([], 12345) + parser = TextFileParser([], 1234) info = parser.parse("test_files/text.csv") self.assertTrue(info["content"].startswith("rosbagTimestamp,header,seq,stamp,secs,nsecs,")) - self.assertEqual(len(info["content"]), 12345) + self.assertEqual(len(info["content"]), 1309) # Size is larger because of html escaping self.assertEqual(info["encoding"], "ascii") diff --git a/spec/ThumbnailGenerator_spec.py b/spec/ThumbnailGenerator_spec.py index 60c21ef..ac37031 100644 --- a/spec/ThumbnailGenerator_spec.py +++ b/spec/ThumbnailGenerator_spec.py @@ -11,7 +11,7 @@ class ThumbnailGeneratorTest(TestCase): generator = ThumbnailGenerator(300) # Original image is 420x315 - generator.generate("test_folder/sample_1.jpg", "test_thumb1.jpg") + generator.generate("test_folder/sample_1.jpg", "test_thumb1.jpg", "image/JPEG") img = Image.open("test_thumb1.jpg") width, height = img.size diff --git a/static/css/auto-complete.css b/static/css/auto-complete.css new file mode 100644 index 0000000..4261b1d --- /dev/null +++ b/static/css/auto-complete.css @@ -0,0 +1,9 @@ +.autocomplete-suggestions { + text-align: left; cursor: default; border: 1px solid #ccc; border-top: 0; background: #fff; box-shadow: -1px 1px 3px rgba(0,0,0,.1); + + /* core styles should not be changed */ + position: absolute; display: none; z-index: 9999; max-height: 254px; overflow: hidden; overflow-y: auto; box-sizing: border-box; +} +.autocomplete-suggestion { position: relative; padding: 0 .6em; line-height: 23px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 1.02em; color: #333; } +.autocomplete-suggestion b { font-weight: normal; color: #1f8dd6; } +.autocomplete-suggestion.selected { background: #f0f0f0; } diff --git a/static/css/bootstrap-slider.min.css b/static/css/bootstrap-slider.min.css new file mode 100644 index 0000000..1cf68b5 --- /dev/null +++ b/static/css/bootstrap-slider.min.css @@ -0,0 +1,41 @@ +/*! ======================================================= + VERSION 10.0.0 +========================================================= */ +/*! ========================================================= + * bootstrap-slider.js + * + * Maintainers: + * Kyle Kemp + * - Twitter: @seiyria + * - Github: seiyria + * Rohit Kalkur + * - Twitter: @Rovolutionary + * - Github: rovolution + * + * ========================================================= + * + * bootstrap-slider is released under the MIT License + * Copyright (c) 2017 Kyle Kemp, Rohit Kalkur, and contributors + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * ========================================================= */.slider{display:inline-block;vertical-align:middle;position:relative}.slider.slider-horizontal{width:210px;height:20px}.slider.slider-horizontal .slider-track{height:10px;width:100%;margin-top:-5px;top:50%;left:0}.slider.slider-horizontal .slider-selection,.slider.slider-horizontal .slider-track-low,.slider.slider-horizontal .slider-track-high{height:100%;top:0;bottom:0}.slider.slider-horizontal .slider-tick,.slider.slider-horizontal .slider-handle{margin-left:-10px}.slider.slider-horizontal .slider-tick.triangle,.slider.slider-horizontal .slider-handle.triangle{position:relative;top:50%;-ms-transform:translateY(-50%);transform:translateY(-50%);border-width:0 10px 10px 10px;width:0;height:0;border-bottom-color:#2e6da4;margin-top:0}.slider.slider-horizontal .slider-tick-container{white-space:nowrap;position:absolute;top:0;left:0;width:100%}.slider.slider-horizontal .slider-tick-label-container{white-space:nowrap;margin-top:20px}.slider.slider-horizontal .slider-tick-label-container .slider-tick-label{padding-top:4px;display:inline-block;text-align:center}.slider.slider-horizontal .tooltip{-ms-transform:translateX(-50%);transform:translateX(-50%)}.slider.slider-horizontal.slider-rtl .slider-track{left:initial;right:0}.slider.slider-horizontal.slider-rtl .slider-tick,.slider.slider-horizontal.slider-rtl .slider-handle{margin-left:initial;margin-right:-10px}.slider.slider-horizontal.slider-rtl .slider-tick-container{left:initial;right:0}.slider.slider-horizontal.slider-rtl .tooltip{-ms-transform:translateX(50%);transform:translateX(50%)}.slider.slider-vertical{height:210px;width:20px}.slider.slider-vertical .slider-track{width:10px;height:100%;left:25%;top:0}.slider.slider-vertical .slider-selection{width:100%;left:0;top:0;bottom:0}.slider.slider-vertical .slider-track-low,.slider.slider-vertical .slider-track-high{width:100%;left:0;right:0}.slider.slider-vertical .slider-tick,.slider.slider-vertical .slider-handle{margin-top:-10px}.slider.slider-vertical .slider-tick.triangle,.slider.slider-vertical .slider-handle.triangle{border-width:10px 0 10px 10px;width:1px;height:1px;border-left-color:#2e6da4;border-right-color:#2e6da4;margin-left:0;margin-right:0}.slider.slider-vertical .slider-tick-label-container{white-space:nowrap}.slider.slider-vertical .slider-tick-label-container .slider-tick-label{padding-left:4px}.slider.slider-vertical .tooltip{-ms-transform:translateY(-50%);transform:translateY(-50%)}.slider.slider-vertical.slider-rtl .slider-track{left:initial;right:25%}.slider.slider-vertical.slider-rtl .slider-selection{left:initial;right:0}.slider.slider-vertical.slider-rtl .slider-tick.triangle,.slider.slider-vertical.slider-rtl .slider-handle.triangle{border-width:10px 10px 10px 0}.slider.slider-vertical.slider-rtl .slider-tick-label-container .slider-tick-label{padding-left:initial;padding-right:4px}.slider.slider-disabled .slider-handle{background-image:-webkit-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:-o-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:linear-gradient(to bottom,#dfdfdf 0,#bebebe 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdfdfdf',endColorstr='#ffbebebe',GradientType=0)}.slider.slider-disabled .slider-track{background-image:-webkit-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:-o-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:linear-gradient(to bottom,#e5e5e5 0,#e9e9e9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5',endColorstr='#ffe9e9e9',GradientType=0);cursor:not-allowed}.slider input{display:none}.slider .tooltip.top{margin-top:-36px}.slider .tooltip-inner{white-space:nowrap;max-width:none}.slider .hide{display:none}.slider-track{position:absolute;cursor:pointer;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#f9f9f9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);border-radius:4px}.slider-selection{position:absolute;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-selection.tick-slider-selection{background-image:-webkit-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:-o-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:linear-gradient(to bottom,#8ac1ef 0,#82b3de 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8ac1ef',endColorstr='#ff82b3de',GradientType=0)}.slider-track-low,.slider-track-high{position:absolute;background:transparent;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-handle{position:absolute;top:0;width:20px;height:20px;background-color:#337ab7;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7',endColorstr='#ff2e6da4',GradientType=0);filter:none;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);border:0 solid transparent}.slider-handle.round{border-radius:50%}.slider-handle.triangle{background:transparent none}.slider-handle.custom{background:transparent none}.slider-handle.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick{position:absolute;width:20px;height:20px;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;filter:none;opacity:.8;border:0 solid transparent}.slider-tick.round{border-radius:50%}.slider-tick.triangle{background:transparent none}.slider-tick.custom{background:transparent none}.slider-tick.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick.in-selection{background-image:-webkit-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:-o-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:linear-gradient(to bottom,#8ac1ef 0,#82b3de 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8ac1ef',endColorstr='#ff82b3de',GradientType=0);opacity:1} \ No newline at end of file diff --git a/static/js/bootstrap.min.js b/static/css/bootstrap.min.js similarity index 100% rename from static/js/bootstrap.min.js rename to static/css/bootstrap.min.js diff --git a/static/js/auto-complete.min.js b/static/js/auto-complete.min.js new file mode 100644 index 0000000..2d0f037 --- /dev/null +++ b/static/js/auto-complete.min.js @@ -0,0 +1,3 @@ +// JavaScript autoComplete v1.0.4 +// https://github.com/Pixabay/JavaScript-autoComplete +var autoComplete=function(){function e(e){function t(e,t){return e.classList?e.classList.contains(t):new RegExp("\\b"+t+"\\b").test(e.className)}function o(e,t,o){e.attachEvent?e.attachEvent("on"+t,o):e.addEventListener(t,o)}function s(e,t,o){e.detachEvent?e.detachEvent("on"+t,o):e.removeEventListener(t,o)}function n(e,s,n,l){o(l||document,s,function(o){for(var s,l=o.target||o.srcElement;l&&!(s=t(l,e));)l=l.parentElement;s&&n.call(l,o)})}if(document.querySelector){var l={selector:0,source:0,minChars:3,delay:150,offsetLeft:0,offsetTop:1,cache:1,menuClass:"",renderItem:function(e,t){t=t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&");var o=new RegExp("("+t.split(" ").join("|")+")","gi");return'