updaeted a lot`
This commit is contained in:
parent
54328654a2
commit
5806060775
|
@ -6,7 +6,7 @@ rice-box.go
|
||||||
/filebrowser
|
/filebrowser
|
||||||
/filebrowser.exe
|
/filebrowser.exe
|
||||||
/dist
|
/dist
|
||||||
/src/backend/vendor/*
|
/src/backend/vendor
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
FROM node:14.21-slim as nbuild
|
FROM node:slim as nbuild
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY ./src/frontend ./
|
COPY ./src/frontend/package*.json ./
|
||||||
RUN npm i
|
RUN npm i
|
||||||
|
COPY ./src/frontend/ ./
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM golang:alpine as base
|
FROM golang:alpine as base
|
||||||
|
|
|
@ -8,12 +8,17 @@ import (
|
||||||
var searchHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
var searchHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
response := []map[string]interface{}{}
|
response := []map[string]interface{}{}
|
||||||
query := r.URL.Query().Get("query")
|
query := r.URL.Query().Get("query")
|
||||||
files, dirs := search.SearchAllIndexes(query, r.URL.Path)
|
files, dirs, fileTypes := search.SearchAllIndexes(query, r.URL.Path)
|
||||||
for _,v := range(files){
|
for _,path := range(files){
|
||||||
response = append(response, map[string]interface{}{
|
f := fileTypes[path]
|
||||||
"dir": false,
|
responseObj := map[string]interface{}{
|
||||||
"path": v,
|
"dir" : false,
|
||||||
})
|
"path" : path,
|
||||||
|
}
|
||||||
|
for _,filterType := range(search.FilterableTypes) {
|
||||||
|
if f[filterType] { responseObj[filterType] = f[filterType] }
|
||||||
|
}
|
||||||
|
response = append(response,responseObj)
|
||||||
}
|
}
|
||||||
for _,v := range(dirs){
|
for _,v := range(dirs){
|
||||||
response = append(response, map[string]interface{}{
|
response = append(response, map[string]interface{}{
|
||||||
|
|
|
@ -11,6 +11,8 @@ var documentTypes = []string{
|
||||||
".word",
|
".word",
|
||||||
".pdf",
|
".pdf",
|
||||||
".txt",
|
".txt",
|
||||||
|
".doc",
|
||||||
|
".docx",
|
||||||
}
|
}
|
||||||
|
|
||||||
var compressedFile = []string{
|
var compressedFile = []string{
|
||||||
|
@ -45,7 +47,6 @@ func ParseSearch(value string) *searchOptions {
|
||||||
if len(filterType) == 1 {
|
if len(filterType) == 1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch filterType[1] {
|
switch filterType[1] {
|
||||||
case "image":
|
case "image":
|
||||||
opts.Conditions["image"] = true
|
opts.Conditions["image"] = true
|
||||||
|
@ -55,8 +56,8 @@ func ParseSearch(value string) *searchOptions {
|
||||||
opts.Conditions["video"] = true
|
opts.Conditions["video"] = true
|
||||||
case "doc":
|
case "doc":
|
||||||
opts.Conditions["doc"] = true
|
opts.Conditions["doc"] = true
|
||||||
case "zip":
|
case "archive":
|
||||||
opts.Conditions["zip"] = true
|
opts.Conditions["archive"] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rootPath string = "/srv"
|
rootPath string = "/srv"
|
||||||
indexes map[string][]string
|
indexes = map[string][]string{}
|
||||||
|
FilterableTypes = []string{"audio","image","video","doc","archive"}
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
lastIndexed time.Time
|
lastIndexed time.Time
|
||||||
)
|
)
|
||||||
|
@ -21,6 +22,8 @@ var (
|
||||||
func InitializeIndex(intervalMinutes uint32) {
|
func InitializeIndex(intervalMinutes uint32) {
|
||||||
// Initialize the indexes map
|
// Initialize the indexes map
|
||||||
indexes = make(map[string][]string)
|
indexes = make(map[string][]string)
|
||||||
|
indexes["dirs"] = []string{}
|
||||||
|
indexes["files"] = []string{}
|
||||||
var numFiles, numDirs int
|
var numFiles, numDirs int
|
||||||
log.Println("Indexing files...")
|
log.Println("Indexing files...")
|
||||||
lastIndexedStart := time.Now()
|
lastIndexedStart := time.Now()
|
||||||
|
@ -79,78 +82,80 @@ func indexFiles(path string, numFiles *int, numDirs *int) (int, int, error) {
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if file.IsDir() {
|
if file.IsDir() {
|
||||||
*numDirs++
|
*numDirs++
|
||||||
indexFiles(path+"/"+file.Name(), numFiles, numDirs)
|
addToIndex(path, file.Name(), true)
|
||||||
|
indexFiles(path+"/"+file.Name(), numFiles, numDirs) // recursive
|
||||||
|
} else {
|
||||||
|
*numFiles++
|
||||||
|
addToIndex(path, file.Name(), false)
|
||||||
}
|
}
|
||||||
*numFiles++
|
|
||||||
addToIndex(path, file.Name())
|
|
||||||
}
|
}
|
||||||
return *numFiles, *numDirs, nil
|
return *numFiles, *numDirs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addToIndex(path string, fileName string) {
|
func addToIndex(path string, fileName string, isDir bool) {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
path = strings.TrimPrefix(path, rootPath+"/")
|
path = strings.TrimPrefix(path, rootPath+"/")
|
||||||
path = strings.TrimSuffix(path, "/")
|
path = strings.TrimSuffix(path, "/")
|
||||||
|
adjustedPath := path + "/" + fileName
|
||||||
if path == rootPath {
|
if path == rootPath {
|
||||||
path = "/"
|
adjustedPath = fileName
|
||||||
}
|
}
|
||||||
info, exists := indexes[path]
|
if isDir {
|
||||||
if !exists {
|
indexes["dirs"] = append(indexes["dirs"], adjustedPath)
|
||||||
info = []string{}
|
}else{
|
||||||
|
indexes["files"] = append(indexes["files"], adjustedPath)
|
||||||
}
|
}
|
||||||
info = append(info, fileName)
|
|
||||||
indexes[path] = info
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SearchAllIndexes(search string, scope string) ([]string, []string) {
|
func SearchAllIndexes(search string, scope string) ([]string, []string, map[string]map[string]bool) {
|
||||||
searchOptions := ParseSearch(search)
|
searchOptions := ParseSearch(search)
|
||||||
mutex.RLock()
|
mutex.RLock()
|
||||||
defer mutex.RUnlock()
|
defer mutex.RUnlock()
|
||||||
|
fileListTypes := make(map[string]map[string]bool)
|
||||||
var matchingFiles []string
|
var matchingFiles []string
|
||||||
var matchingDirs []string
|
var matchingDirs []string
|
||||||
maximum := 100
|
maximum := 125
|
||||||
count := 0
|
|
||||||
for _, searchTerm := range searchOptions.Terms {
|
for _, searchTerm := range searchOptions.Terms {
|
||||||
if searchTerm == "" {
|
if searchTerm == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
count := 0
|
||||||
// Iterate over the indexes
|
// Iterate over the indexes
|
||||||
for dirName, v := range indexes {
|
for _, dirName := range indexes["dirs"] {
|
||||||
if count > maximum {
|
if count > maximum {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
searchItems := v
|
|
||||||
// Iterate over the path names
|
|
||||||
for _, pathName := range searchItems {
|
|
||||||
if count > maximum {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if dirName != "/" {
|
|
||||||
pathName = dirName + "/" + pathName
|
|
||||||
}
|
|
||||||
// Check if the path name contains the search term
|
|
||||||
if !containsSearchTerm(pathName, searchTerm, searchOptions.Conditions) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pathName = scopedPathNameFilter(pathName, scope)
|
|
||||||
if pathName == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
count++
|
|
||||||
matchingFiles = append(matchingFiles, pathName)
|
|
||||||
}
|
|
||||||
// Check if the path name contains the search term
|
|
||||||
if !containsSearchTerm(dirName, searchTerm, searchOptions.Conditions) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pathName := scopedPathNameFilter(dirName, scope)
|
pathName := scopedPathNameFilter(dirName, scope)
|
||||||
if pathName == "" {
|
if pathName == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
matches, _ := containsSearchTerm(pathName, searchTerm, searchOptions.Conditions)
|
||||||
|
if !matches {
|
||||||
|
continue
|
||||||
|
}
|
||||||
count++
|
count++
|
||||||
matchingDirs = append(matchingDirs, pathName)
|
matchingDirs = append(matchingDirs, pathName)
|
||||||
}
|
}
|
||||||
|
count = 0
|
||||||
|
for _, fileName := range indexes["files"] {
|
||||||
|
if count > maximum {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pathName := scopedPathNameFilter(fileName, scope)
|
||||||
|
if pathName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check if the path name contains the search term
|
||||||
|
matches, fileType := containsSearchTerm(pathName, searchTerm, searchOptions.Conditions)
|
||||||
|
if !matches {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matchingFiles = append(matchingFiles, pathName)
|
||||||
|
fileListTypes[pathName] = fileType
|
||||||
|
count++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Sort the strings based on the number of elements after splitting by "/"
|
// Sort the strings based on the number of elements after splitting by "/"
|
||||||
sort.Slice(matchingFiles, func(i, j int) bool {
|
sort.Slice(matchingFiles, func(i, j int) bool {
|
||||||
|
@ -164,7 +169,7 @@ func SearchAllIndexes(search string, scope string) ([]string, []string) {
|
||||||
parts2 := strings.Split(matchingDirs[j], "/")
|
parts2 := strings.Split(matchingDirs[j], "/")
|
||||||
return len(parts1) < len(parts2)
|
return len(parts1) < len(parts2)
|
||||||
})
|
})
|
||||||
return matchingFiles, matchingDirs
|
return matchingFiles, matchingDirs, fileListTypes
|
||||||
}
|
}
|
||||||
|
|
||||||
func scopedPathNameFilter(pathName string, scope string) string {
|
func scopedPathNameFilter(pathName string, scope string) string {
|
||||||
|
@ -177,51 +182,51 @@ func scopedPathNameFilter(pathName string, scope string) string {
|
||||||
return pathName
|
return pathName
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsSearchTerm(pathName string, searchTerm string, conditions map[string]bool) bool {
|
func containsSearchTerm(pathName string, searchTerm string, conditions map[string]bool) (bool, map[string]bool) {
|
||||||
path := getLastPathComponent(pathName)
|
path := getLastPathComponent(pathName)
|
||||||
|
fileTypes := map[string]bool{}
|
||||||
|
matchesCondition := false
|
||||||
|
extension := filepath.Ext(strings.ToLower(path))
|
||||||
|
mimetype := mime.TypeByExtension(extension)
|
||||||
|
filterTypes := FilterableTypes
|
||||||
|
fileTypes["audio"] = strings.HasPrefix(mimetype, "audio")
|
||||||
|
fileTypes["image"] = strings.HasPrefix(mimetype, "image")
|
||||||
|
fileTypes["video"] = strings.HasPrefix(mimetype, "video")
|
||||||
|
fileTypes["doc"] = isDoc(extension)
|
||||||
|
fileTypes["archive"] = isArchive(extension)
|
||||||
|
anyFilter := false
|
||||||
|
for _,t := range(filterTypes){
|
||||||
|
if conditions[t] {
|
||||||
|
anyFilter = true
|
||||||
|
matchesCondition = fileTypes[t]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !anyFilter {
|
||||||
|
matchesCondition = true
|
||||||
|
}
|
||||||
if !conditions["exact"] {
|
if !conditions["exact"] {
|
||||||
path = strings.ToLower(path)
|
path = strings.ToLower(path)
|
||||||
searchTerm = strings.ToLower(searchTerm)
|
searchTerm = strings.ToLower(searchTerm)
|
||||||
}
|
}
|
||||||
matchesCondition := true
|
return strings.Contains(path, searchTerm) && matchesCondition, fileTypes
|
||||||
if conditions["audio"] {
|
}
|
||||||
extension := filepath.Ext(path)
|
|
||||||
mimetype := mime.TypeByExtension(extension)
|
func isDoc(extension string) bool {
|
||||||
matchesCondition = strings.HasPrefix(mimetype, "audio")
|
for _, typefile := range documentTypes {
|
||||||
}
|
if extension == typefile {
|
||||||
if conditions["video"] {
|
return true
|
||||||
extension := filepath.Ext(path)
|
|
||||||
mimetype := mime.TypeByExtension(extension)
|
|
||||||
matchesCondition = strings.HasPrefix(mimetype, "video")
|
|
||||||
}
|
|
||||||
if conditions["image"] {
|
|
||||||
extension := filepath.Ext(path)
|
|
||||||
mimetype := mime.TypeByExtension(extension)
|
|
||||||
matchesCondition = strings.HasPrefix(mimetype, "image")
|
|
||||||
}
|
|
||||||
if conditions["doc"] {
|
|
||||||
extension := filepath.Ext(path)
|
|
||||||
for _, typefile := range documentTypes {
|
|
||||||
if extension == typefile {
|
|
||||||
matchesCondition = true
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
matchesCondition = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if conditions["zip"] {
|
return false
|
||||||
extension := filepath.Ext(path)
|
}
|
||||||
for _, typefile := range compressedFile {
|
|
||||||
if extension == typefile {
|
func isArchive(extension string) bool {
|
||||||
matchesCondition = true
|
for _, typefile := range compressedFile {
|
||||||
continue
|
if extension == typefile {
|
||||||
} else {
|
return true
|
||||||
matchesCondition = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return strings.Contains(path, searchTerm) && matchesCondition
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLastPathComponent(path string) string {
|
func getLastPathComponent(path string) string {
|
||||||
|
|
|
@ -24,7 +24,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="result" ref="result">
|
<div id="result" ref="result">
|
||||||
<div>
|
<div id="result-list">
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
<template v-if="isEmpty">
|
<template v-if="isEmpty">
|
||||||
<p>{{ text }}</p>
|
<p>{{ text }}</p>
|
||||||
|
|
||||||
|
@ -48,11 +50,16 @@
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<ul v-show="filteredResults.length > 0">
|
<ul v-show="filteredResults.length > 0">
|
||||||
<li v-for="(s, k) in filteredResults" :key="k">
|
<div>Searching Scope : {{ path }}</div>
|
||||||
<router-link @click.native="close" :to="s.url">
|
<li v-for="(s, k) in filteredResults" :key="k" @click="navigate(s.url,$event)" style="cursor: pointer;">
|
||||||
<i v-if="s.dir" class="material-icons">folder</i>
|
<router-link :to="s.url">
|
||||||
<i v-else class="material-icons">insert_drive_file</i>
|
<i v-if="s.dir" class="material-icons folder-icons"> folder </i>
|
||||||
<span>./{{ s.path }}</span>
|
<i v-else-if="s.audio" class="material-icons audio-icons"> volume_up </i>
|
||||||
|
<i v-else-if="s.image" class="material-icons image-icons"> photo </i>
|
||||||
|
<i v-else-if="s.video" class="material-icons video-icons"> movie </i>
|
||||||
|
<i v-else-if="s.archive" class="material-icons archive-icons"> archive </i>
|
||||||
|
<i v-else class="material-icons file-icons"> insert_drive_file </i>
|
||||||
|
{{ s.path }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -92,7 +99,6 @@ export default {
|
||||||
watch: {
|
watch: {
|
||||||
show(val, old) {
|
show(val, old) {
|
||||||
this.active = val === "search";
|
this.active = val === "search";
|
||||||
|
|
||||||
if (old === "search" && !this.active) {
|
if (old === "search" && !this.active) {
|
||||||
if (this.reload) {
|
if (this.reload) {
|
||||||
this.setReload(true);
|
this.setReload(true);
|
||||||
|
@ -155,8 +161,13 @@ export default {
|
||||||
close(event) {
|
close(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
console.log("closing")
|
||||||
this.closeHovers();
|
this.closeHovers();
|
||||||
},
|
},
|
||||||
|
navigate(url, event) {
|
||||||
|
this.close(event); // pass the event object to the close method
|
||||||
|
this.$router.push(url);
|
||||||
|
},
|
||||||
keyup(event) {
|
keyup(event) {
|
||||||
if (event.keyCode === 27) {
|
if (event.keyCode === 27) {
|
||||||
this.close(event);
|
this.close(event);
|
||||||
|
|
|
@ -82,7 +82,7 @@ header .menu-button {
|
||||||
}
|
}
|
||||||
|
|
||||||
#search #input {
|
#search #input {
|
||||||
background-color: #f5f5f5;
|
background-color: rgba(100, 100, 100, 0.2);
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0em 0.75em;
|
padding: 0em 0.75em;
|
||||||
|
@ -93,9 +93,9 @@ header .menu-button {
|
||||||
}
|
}
|
||||||
|
|
||||||
#search.active #input {
|
#search.active #input {
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.075);
|
border-bottom: 3px solid rgba(0, 0, 0, 0.075);
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
background-color: #fff;
|
backdrop-filter: blur(6px);
|
||||||
height: 4em;
|
height: 4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,11 +103,6 @@ header .menu-button {
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search.active i,
|
|
||||||
#search.active input {
|
|
||||||
color: #212121;
|
|
||||||
}
|
|
||||||
|
|
||||||
#search #input>.action,
|
#search #input>.action,
|
||||||
#search #input>i {
|
#search #input>i {
|
||||||
margin-right: 0.3em;
|
margin-right: 0.3em;
|
||||||
|
@ -121,20 +116,35 @@ header .menu-button {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#result-list {
|
||||||
|
width: 60em;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0.5em;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
#search #result {
|
#search #result {
|
||||||
visibility: visible;
|
overflow: hidden;
|
||||||
max-height: none;
|
background: white;
|
||||||
background-color: #f8f8f8;
|
display: flex;
|
||||||
|
top: -4em;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: rgba(0, 0, 0, 0.6);
|
color: rgba(0, 0, 0, 0.6);
|
||||||
height: 0;
|
height: 0;
|
||||||
transition: .1s ease height, .1s ease padding;
|
transition: .2s ease height, .2s ease padding;
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 800px) {
|
||||||
|
#search #result {
|
||||||
|
background: linear-gradient(to right, white 15%,lightgray 25%,lightgray 75%,white 85%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
body.rtl #search #result {
|
body.rtl #search #result {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
@ -156,12 +166,12 @@ body.rtl #search #result ul>* {
|
||||||
|
|
||||||
#search.active #result {
|
#search.active #result {
|
||||||
padding: .5em;
|
padding: .5em;
|
||||||
height: calc(100% - 4em);
|
height: 100%
|
||||||
}
|
}
|
||||||
|
|
||||||
#search ul {
|
#search ul {
|
||||||
|
margin-top: 1em;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,11 +179,6 @@ body.rtl #search #result ul>* {
|
||||||
margin-bottom: .5em;
|
margin-bottom: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search #result>div {
|
|
||||||
max-width: 45em;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#search #result #renew {
|
#search #result #renew {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -186,8 +191,20 @@ body.rtl #search #result ul>* {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search.active #result i {
|
.folder-icons {
|
||||||
color: #ccc;
|
color: var(--icon-blue);
|
||||||
|
}
|
||||||
|
.video-icons {
|
||||||
|
color: lightskyblue;
|
||||||
|
}
|
||||||
|
.image-icons {
|
||||||
|
color: lightcoral;
|
||||||
|
}
|
||||||
|
.archive-icons {
|
||||||
|
color: tan;
|
||||||
|
}
|
||||||
|
.audio-icons {
|
||||||
|
color: plum;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search.active #result>p>i {
|
#search.active #result>p>i {
|
||||||
|
|
Loading…
Reference in New Issue