Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
李少辉-开发者
gitlab-foss
提交
2b8457d6
G
gitlab-foss
项目概览
李少辉-开发者
/
gitlab-foss
通知
15
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
G
gitlab-foss
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
2b8457d6
编写于
1月 05, 2016
作者:
J
Jacob Schatz
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
initial dropdown integration with gitlab
上级
6f4ea679
变更
7
隐藏空白更改
内联
并排
Showing
7 changed file
with
1278 addition
and
10 deletion
+1278
-10
app/assets/javascripts/multiawesome.js
app/assets/javascripts/multiawesome.js
+583
-0
app/assets/stylesheets/multiawesome.css
app/assets/stylesheets/multiawesome.css
+178
-0
app/controllers/projects/milestones_controller.rb
app/controllers/projects/milestones_controller.rb
+4
-0
app/helpers/milestones_helper.rb
app/helpers/milestones_helper.rb
+6
-7
app/helpers/selects_helper.rb
app/helpers/selects_helper.rb
+35
-0
app/views/shared/issuable/_filter.html.haml
app/views/shared/issuable/_filter.html.haml
+1
-3
vendor/assets/javascripts/sifter.js
vendor/assets/javascripts/sifter.js
+471
-0
未找到文件。
app/assets/javascripts/multiawesome.js
0 → 100644
浏览文件 @
2b8457d6
//= require sifter
/**
* multiawesome.js (v0.12.1)
* Copyright (c) 2015 Jacob Schatz & contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
* @author Jacob Schatz <jschatz@gitlab.com>
*/
/*jshint curly:false */
/*jshint browser:true */
(
function
(
$
)
{
'
use strict
'
;
var
defaults
=
{
data
:
[],
title
:
''
,
tip
:
''
,
name
:
'
multi-awesome
'
,
header
:
[],
multiple
:
false
,
alwaysPrefixWithSearch
:
false
,
placeholder
:
'
Filter...
'
,
onChange
:
function
(){},
always
:
[],
dataObject
:
{
label
:
'
label
'
,
data
:
'
data
'
,
category
:
'
category
'
,
subtitle
:
'
subtitle
'
,
image
:
'
image
'
},
minSearchLength
:
2
};
$
.
fn
.
multiawesome
=
function
(
options
)
{
var
MultiAwesome
=
{
extraMenuContainerTemplate
:
'
<li data-extra-menu-container class="dropdown-multi-menu-extras-container"></li>
'
,
searchTemplate
:
'
<li class="dropdown-multi-menu-search-container"><div class="input-with-icon"><i class="fa fa-search"></i><input type="text" id="multiawesome-search-input" /></div></li>
'
,
itemContainerTemplate
:
'
<li class="dropdown-multi-menu-selections"><ul></ul></li>
'
,
itemTemplate
:
'
<li><a href="#" class="item" data-item-selectable tabIndex="-1"><input type="checkbox" name="{{name}}" value="{{data}}"/>{{label}}</a></li>
'
,
seperatorTemplate
:
'
<li role="separator" class="divider"></li>
'
,
headerTemplate
:
'
<li class="dropdown-multi-menu-header"><div class="dropdown-multi-menu-header-area"><ul class="dropdown-multi-menu-header-list">{{header}}</ul></div></li>
'
,
headerItemTemplate
:
'
<li class="dropdown-multi-menu-header-item"><a href="#" data-header-selectable class="header-item" tabIndex="-1"><input name="{{name}}" type="checkbox" value="{{data}}" />{{headeritem}}</a></li>
'
,
titleTemplate
:
'
<li class="dropdown-multi-menu-title"><div class="dropdown-multi-menu-title-area"><a href="#" data-back-button class="dropdown-multi-menu-back-button"></a><h3 class="dropdown-multi-menu-title-text">{{title}}</h3></div></li>
'
,
categoryContainerTemplate
:
'
<li class="dropdown-multi-menu-category"><ul></ul></li>
'
,
categoryItemTemplate
:
'
<li><a href="#" class="category" data-category-selectable tabIndex="-1"><input type="checkbox" value="{{category}}"/>{{category}}</a></li>
'
,
tipTemplate
:
'
<li class="dropdown-multi-menu-tip"><div class="dropdown-multi-menu-tip-area"><p>{{tip}}</p></div></li>
'
,
subtitleTemplate
:
'
<p class="dropdown-multi-menu-subtitle">{{subtitle}}</p>
'
,
imageTemplate
:
'
<div class="dropdown-multi-menu-image" style="background-image:url(
\'
{{image}}
\'
);"></div>
'
};
return
this
.
each
(
function
()
{
var
self
=
this
,
categories
=
[],
$self
=
$
(
self
),
$form
=
$
(
self
).
closest
(
'
form
'
),
$itemContainer
,
$itemContainerUL
,
$extraMenuContainer
,
categoriesAppended
=
false
,
categoriesSet
=
false
,
selectedItems
=
[],
selectedCategories
=
[],
$searchInput
,
$extraMenus
,
$addedMenu
,
$backButton
,
$currentMenu
,
toHideForExtraMenus
=
[],
sifter
,
// Merge the options and defaults into the settings.
// No need to check for undefined
settings
=
$
.
extend
(
true
,
{},
defaults
,
options
,
$self
.
data
());
if
(
self
.
tagName
!==
'
UL
'
)
{
return
;
}
var
prepareDropdown
=
function
()
{
var
$searchTemplate
=
$
(
MultiAwesome
.
searchTemplate
);
toHideForExtraMenus
.
push
(
$searchTemplate
);
$self
.
prepend
(
$searchTemplate
);
$searchInput
=
$searchTemplate
.
find
(
'
input
'
);
if
(
settings
.
placeholder
)
{
$searchInput
.
prop
(
'
placeholder
'
,
settings
.
placeholder
);
}
};
var
attachListeners
=
function
()
{
$self
.
on
(
'
click
'
,
'
[data-category-selectable]
'
,
dropdownCategoryLinkClicked
);
$self
.
on
(
'
click
'
,
'
[data-item-selectable], [data-header-selectable]
'
,
dropdownSelectionLinkClicked
);
$self
.
on
(
'
click
'
,
dropdownClickedAnywhere
);
$self
.
on
(
'
click
'
,
$backButton
,
backButtonClicked
);
$searchInput
.
on
(
'
keydown keyup update
'
,
inputSearched
);
};
var
parseSearchResults
=
function
(
results
)
{
var
finalData
=
[];
results
.
forEach
(
function
(
result
)
{
finalData
.
push
(
settings
.
data
[
result
.
id
]);
});
renderData
(
finalData
);
};
var
addCategories
=
function
()
{
var
$categoryContainer
=
$
(
MultiAwesome
.
categoryContainerTemplate
);
var
$categoryContainerUL
=
$categoryContainer
.
find
(
'
ul
'
);
if
(
categories
.
length
)
{
var
$seperatorTemplate
=
$
(
MultiAwesome
.
seperatorTemplate
);
toHideForExtraMenus
.
push
(
$seperatorTemplate
);
$self
.
prepend
(
$seperatorTemplate
);
categories
.
forEach
(
function
(
category
)
{
$categoryContainerUL
.
prepend
(
MultiAwesome
.
categoryItemTemplate
.
replace
(
/
\{\{
category
\}\}
/g
,
category
)
);
});
$self
.
prepend
(
$categoryContainer
);
}
};
var
addTitle
=
function
()
{
var
titleTemplate
;
if
(
settings
.
title
)
{
var
$seperatorTemplate
=
$
(
MultiAwesome
.
seperatorTemplate
);
var
$titleTemplate
=
$
(
MultiAwesome
.
titleTemplate
);
$self
.
prepend
(
$seperatorTemplate
);
$self
.
prepend
(
MultiAwesome
.
titleTemplate
.
replace
(
/
\{\{
title
\}\}
/g
,
settings
.
title
)
);
$backButton
=
$self
.
find
(
'
[data-back-button]
'
);
}
};
var
addExtrasContainer
=
function
()
{
$extraMenuContainer
=
$
(
MultiAwesome
.
extraMenuContainerTemplate
);
$self
.
prepend
(
$extraMenuContainer
);
};
var
addHeader
=
function
()
{
var
$headerTemplate
,
headerList
=
[];
// if we have some header data
if
(
settings
.
header
.
length
)
{
settings
.
header
.
forEach
(
function
(
item
)
{
headerList
.
push
(
MultiAwesome
.
headerItemTemplate
.
replace
(
/
\{\{
headeritem
\}\}
/g
,
item
[
settings
.
dataObject
.
label
])
.
replace
(
/
\{\{
data
\}\}
/g
,
item
[
settings
.
dataObject
.
data
])
.
replace
(
/
\{\{
name
\}\}
/g
,
'
_
'
+
settings
.
name
)
);
});
var
$seperatorTemplate
=
$
(
MultiAwesome
.
seperatorTemplate
);
toHideForExtraMenus
.
push
(
$seperatorTemplate
);
$self
.
prepend
(
$seperatorTemplate
);
$headerTemplate
=
$
(
MultiAwesome
.
headerTemplate
.
replace
(
/
\{\{
header
\}\}
/g
,
headerList
.
join
(
''
)));
toHideForExtraMenus
.
push
(
$headerTemplate
);
$self
.
prepend
(
$headerTemplate
);
}
};
var
addData
=
function
(
callback
)
{
function
parseDataWhenReady
()
{
$itemContainer
=
$
(
MultiAwesome
.
itemContainerTemplate
);
toHideForExtraMenus
.
push
(
$itemContainer
);
$itemContainerUL
=
$itemContainer
.
find
(
'
ul
'
);
sifter
=
new
Sifter
(
settings
.
data
);
renderData
(
settings
.
data
,
callback
);
}
if
(
settings
.
data
)
{
if
(
typeof
settings
.
data
===
'
string
'
)
{
$
.
getJSON
(
settings
.
data
,
function
(
data
)
{
settings
.
data
=
data
;
parseDataWhenReady
();
});
}
else
if
(
typeof
settings
.
data
===
'
object
'
)
{
parseDataWhenReady
();
}
else
{
$
.
error
(
'
Data must be a string or array
'
);
}
}
};
var
renderData
=
function
(
data
,
callback
)
{
selectedItems
=
[];
$itemContainerUL
.
empty
();
var
emptyObj
=
{},
skipMatch
=
false
,
searchInputVal
=
[],
o
,
tempAlwaysData
=
[],
tempItemTemplate
,
$itemTemplate
;
if
(
!
data
.
length
)
{
emptyObj
[
settings
.
dataObject
.
label
]
=
'
No matches found
'
;
emptyObj
[
settings
.
dataObject
.
data
]
=
'
dropdown-multi-menu-selectable:false
'
;
emptyObj
[
settings
.
dataObject
.
category
]
=
''
;
emptyObj
.
selectable
=
false
;
data
.
push
(
emptyObj
);
skipMatch
=
true
;
}
if
(
settings
.
alwaysPrefixWithSearch
&&
$searchInput
)
{
searchInputVal
=
$searchInput
.
val
();
tempAlwaysData
=
settings
.
always
.
map
(
function
(
item
){
// copy, don't alter real object.
o
=
$
.
extend
({},
item
);
if
(
searchInputVal
.
length
){
o
.
label
=
'
<strong>"
'
+
searchInputVal
+
'
"</strong>
'
+
'
'
+
o
.
label
;
}
o
.
always
=
true
;
return
o
;
});
}
else
{
tempAlwaysData
=
settings
.
always
;
}
data
=
data
.
concat
(
tempAlwaysData
);
data
.
forEach
(
function
(
item
)
{
tempItemTemplate
=
MultiAwesome
.
itemTemplate
;
if
(
!
categoriesSet
&&
!
skipMatch
)
{
var
addCategory
=
item
[
settings
.
dataObject
.
category
];
if
(
item
.
hasOwnProperty
(
settings
.
dataObject
.
category
)
&&
categories
.
indexOf
(
addCategory
)
===
-
1
)
{
categories
.
push
(
addCategory
);
}
}
else
{
// only do this if the categories are already set... they won't search categories on the first time.
if
(
selectedCategories
.
length
&&
selectedCategories
.
indexOf
(
item
[
settings
.
dataObject
.
category
])
===
-
1
&&
!
skipMatch
)
{
return
;
}
}
$itemTemplate
=
$
(
MultiAwesome
.
itemTemplate
);
if
(
item
.
hasOwnProperty
(
'
selectable
'
)
&&
!
item
.
selectable
)
{
tempItemTemplate
=
$itemTemplate
.
find
(
'
a
'
)
.
addClass
(
'
disabled
'
)
.
find
(
'
input[type="checkbox"]
'
)
.
prop
(
'
disabled
'
,
'
disabled
'
)
// back to the anchor tag
.
end
()
// back to the li
.
end
()
.
get
(
0
)
.
outerHTML
;
}
if
(
item
.
hasOwnProperty
(
settings
.
dataObject
.
subtitle
)
&&
item
[
settings
.
dataObject
.
subtitle
].
length
)
{
tempItemTemplate
=
$
(
tempItemTemplate
)
.
find
(
'
a
'
)
.
addClass
(
'
dropdown-multi-menu-item-with-subtitle
'
)
.
append
(
MultiAwesome
.
subtitleTemplate
.
replace
(
/
\{\{
subtitle
\}\}
/g
,
item
[
settings
.
dataObject
.
subtitle
])
)
.
end
()
.
get
(
0
)
.
outerHTML
;
}
if
(
item
.
hasOwnProperty
(
settings
.
dataObject
.
image
)
&&
item
[
settings
.
dataObject
.
image
].
length
)
{
tempItemTemplate
=
$
(
tempItemTemplate
)
.
find
(
'
a
'
)
.
addClass
(
'
dropdown-multi-menu-with-image
'
)
.
prepend
(
MultiAwesome
.
imageTemplate
.
replace
(
/
\{\{
image
\}\}
/g
,
item
[
settings
.
dataObject
.
image
])
)
.
end
()
.
get
(
0
)
.
outerHTML
;
}
$itemContainerUL
.
append
(
tempItemTemplate
.
replace
(
/
\{\{
data
\}\}
/g
,
item
[
settings
.
dataObject
.
data
])
.
replace
(
/
\{\{
label
\}\}
/g
,
item
[
settings
.
dataObject
.
label
])
.
replace
(
/
\{\{
name
\}\}
/g
,
'
_
'
+
settings
.
name
)
);
});
if
(
!
categoriesSet
&&
!
categoriesAppended
)
{
$self
.
append
(
$itemContainer
);
categoriesAppended
=
true
;
}
if
(
categories
.
length
){
categoriesSet
=
true
;
}
if
(
callback
)
{
callback
();
}
};
var
addToForm
=
function
(
val
)
{
$form
.
prepend
(
'
<input type="hidden" name="
'
+
settings
.
name
+
'
" value="
'
+
val
+
'
" />
'
);
};
var
removeFromForm
=
function
(
val
)
{
$form
.
find
(
'
input[name="
'
+
settings
.
name
+
'
"][value="
'
+
val
+
'
"]
'
)
.
remove
();
};
var
getExtraMenus
=
function
()
{
$extraMenus
=
$self
// get parent button group
.
closest
(
'
div.button-group
'
)
// get the extra menu divs
.
find
(
'
[data-extra-menu]
'
);
};
/* * * * * * * * * * * * * * * */
/* listeners
/* * * * * * * * * * * * * * * */
var
backButtonClicked
=
function
()
{
toHideForExtraMenus
.
forEach
(
function
(
menuSection
){
menuSection
.
show
();
});
$extraMenuContainer
.
empty
();
$backButton
.
hide
();
return
false
;
};
var
inputSearched
=
function
()
{
//remove current hidden inputs
$
(
'
input[type="hidden"][name="
'
+
settings
.
name
+
'
"]
'
).
remove
();
if
(
$searchInput
.
val
().
length
>
settings
.
minSearchLength
)
{
var
results
=
sifter
.
search
(
$searchInput
.
val
(),
{
fields
:
[
settings
.
dataObject
.
label
],
sort
:
[{
field
:
settings
.
dataObject
.
label
,
direction
:
'
asc
'
}]
});
parseSearchResults
(
results
.
items
);
}
else
{
renderData
(
settings
.
data
);
}
};
var
moveToExtraMenu
=
function
(
$menu
)
{
var
$cloneMenu
=
$menu
.
clone
();
toHideForExtraMenus
.
forEach
(
function
(
menuSection
){
menuSection
.
hide
();
});
$currentMenu
=
$menu
;
$extraMenuContainer
.
append
(
$cloneMenu
);
$cloneMenu
.
show
();
$backButton
.
show
();
};
var
isExtraMenuValue
=
function
(
val
)
{
var
isMatch
=
false
;
$extraMenus
.
each
(
function
()
{
var
$this
=
$
(
this
);
if
(
val
===
$this
.
data
(
'
menu-target-value
'
))
{
isMatch
=
true
;
moveToExtraMenu
(
$this
);
return
;
}
});
return
isMatch
;
};
var
dropdownClickedAnywhere
=
function
(
e
)
{};
var
dropdownCategoryLinkClicked
=
function
(
e
)
{
var
$target
=
$
(
e
.
currentTarget
),
$inp
=
$target
.
find
(
'
input
'
),
val
=
$inp
.
val
(),
i
=
selectedCategories
.
indexOf
(
val
);
e
.
preventDefault
();
// if the checkbox is disabled.
if
(
$inp
.
prop
(
'
disabled
'
)
)
{
return
false
;
}
if
(
i
>
-
1
)
{
var
spliced
=
selectedCategories
.
splice
(
i
,
1
);
$target
.
removeClass
(
'
selected
'
);
setTimeout
(
function
()
{
$inp
.
prop
(
'
checked
'
,
false
);
},
0
);
}
else
{
selectedCategories
.
push
(
val
);
$target
.
addClass
(
'
selected
'
);
setTimeout
(
function
()
{
$inp
.
prop
(
'
checked
'
,
true
);
},
0
);
}
inputSearched
();
$
(
e
.
target
).
blur
();
return
false
;
};
var
findItemWithData
=
function
(
searchData
,
id
)
{
var
item
=
{};
for
(
var
i
=
searchData
.
length
-
1
;
i
>=
0
;
i
--
)
{
item
=
searchData
[
i
];
if
(
item
.
hasOwnProperty
(
settings
.
dataObject
.
data
)
&&
item
[
settings
.
dataObject
.
data
]
==
id
)
{
return
item
;
}
}
return
undefined
;
};
var
dropdownSelectionLinkClicked
=
function
(
e
)
{
var
$target
=
$
(
e
.
currentTarget
),
$inp
=
$target
.
find
(
'
input
'
),
findItemInData
,
val
=
$inp
.
val
(),
i
=
selectedItems
.
indexOf
(
val
);
e
.
preventDefault
();
if
(
$inp
.
prop
(
'
disabled
'
)
)
{
return
false
;
}
if
(
isExtraMenuValue
(
val
)
)
{
return
false
;
}
findItemInData
=
findItemWithData
(
settings
.
data
,
val
);
if
(
typeof
findItemInData
===
'
undefined
'
)
{
findItemInData
=
findItemWithData
(
settings
.
always
,
val
);
}
if
(
typeof
findItemInData
===
'
undefined
'
)
{
findItemInData
=
findItemWithData
(
settings
.
header
,
val
);
}
if
(
typeof
findItemInData
===
'
undefined
'
&&
val
===
'
dropdown-multi-menu-selectable:false
'
)
{
// don't close the dropdown.
return
false
;
}
if
(
typeof
findItemInData
!==
'
undefined
'
&&
findItemInData
.
hasOwnProperty
(
'
selectable
'
)
&&
findItemInData
.
selectable
===
false
)
{
// don't close the dropdown
return
false
;
}
if
(
findItemInData
.
hasOwnProperty
(
'
href
'
)
)
{
window
.
location
.
href
=
findItemInData
.
href
;
return
;
}
if
(
findItemInData
.
hasOwnProperty
(
'
selectable
'
)
&&
!
findItemWithData
.
selectable
)
{
return
;
}
if
(
i
>
-
1
)
{
var
spliced
=
selectedItems
.
splice
(
i
,
1
);
if
(
settings
.
multiple
)
{
removeFromForm
(
spliced
);
}
else
{
$form
.
find
(
'
input[name="
'
+
settings
.
name
+
'
"]
'
)
.
remove
();
}
$target
.
removeClass
(
'
selected
'
);
setTimeout
(
function
()
{
$inp
.
prop
(
'
checked
'
,
false
);
},
0
);
}
else
{
if
(
!
settings
.
multiple
)
{
selectedItems
=
[];
$form
.
find
(
'
input[name="
'
+
settings
.
name
+
'
"]
'
)
.
remove
();
$form
.
find
(
'
input[name="_
'
+
settings
.
name
+
'
"]
'
)
.
parent
()
.
removeClass
(
'
selected
'
);
}
selectedItems
.
push
(
val
);
addToForm
(
val
);
$target
.
addClass
(
'
selected
'
);
setTimeout
(
function
()
{
$inp
.
prop
(
'
checked
'
,
true
);
},
0
);
}
settings
.
onChange
({
"
changed
"
:
findItemInData
,
"
selected
"
:
selectedItems
});
// close the dropdown if single selection
// otherwise don't close the dropdown
if
(
settings
.
multiple
)
{
$
(
e
.
target
).
blur
();
return
false
;
}
else
{
var
button
=
$self
.
siblings
(
'
.dropdown-toggle
'
).
first
();
button
.
contents
()
.
each
(
function
(){
if
(
this
.
nodeType
===
3
&&
this
.
nodeValue
.
trim
()
)
{
this
.
textContent
=
$target
.
text
();
}
});
}
};
var
addTip
=
function
()
{
var
$tipTemplate
;
if
(
settings
.
tip
)
{
var
$seperatorTemplate
=
$
(
MultiAwesome
.
seperatorTemplate
);
toHideForExtraMenus
.
push
(
$seperatorTemplate
);
$self
.
append
(
$seperatorTemplate
);
$tipTemplate
=
$
(
MultiAwesome
.
tipTemplate
.
replace
(
/
\{\{
tip
\}\}
/g
,
settings
.
tip
));
toHideForExtraMenus
.
push
(
$tipTemplate
);
$self
.
append
(
$tipTemplate
);
}
};
/* * * * * * * * * * * * * * * */
/* setup
/* * * * * * * * * * * * * * * */
var
shouldInit
=
function
()
{
if
(
!
$self
.
hasClass
(
'
initialized
'
))
{
$self
.
addClass
(
'
initialized
'
);
return
true
;
}
return
false
;
};
var
setup
=
function
()
{
if
(
!
shouldInit
()){
return
;
}
addData
(
function
(){
addHeader
();
addCategories
();
prepareDropdown
();
addExtrasContainer
();
addTitle
();
attachListeners
();
addTip
();
getExtraMenus
();
});
};
setup
();
});
};
$
(
function
(){
$
(
'
[data-multi-awesome]
'
).
each
(
function
(){
$
(
this
).
multiawesome
();
});
});
})(
jQuery
);
\ No newline at end of file
app/assets/stylesheets/multiawesome.css
0 → 100644
浏览文件 @
2b8457d6
.open
>
.dropdown-menu
{
max-width
:
320px
;
border-radius
:
0px
;
}
.dropdown-menu
input
[
type
=
'text'
]
{
margin
:
0
5px
;
width
:
309px
;
height
:
35px
;
padding-left
:
6px
;
}
.dropdown-menu
input
[
type
=
'checkbox'
]
{
margin
:
0
5px
;
display
:
none
;
}
.dropdown-menu
>
li
>
a
{
padding-left
:
0
;
}
.dropdown-menu
.dropdown-multi-menu-title-area
h3
{
font-size
:
15px
;
text-align
:
center
;
margin-top
:
5px
;
}
.dropdown-menu
.dropdown-multi-menu-title-area
h3
::after
{
content
:
""
;
background
:
url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAACGUlEQVQ4jZ2VMXbiMBCGf83KKcxzntOmI1VMEyj2BLpD9gRbwQVygeUCpMsdcgKfQaYJqeIu1RYGC/stNjNbJOaBY8hu/lrz69PoH0mJCBaLhVEArqMoxhf0vFgYARBFUawWT08my7KxiMD3/fhmOLz/H7PE2ruyLL8rpRCG4b0WEQjzeVXXxq3X59ba8zAMZ/1+Pz9l9PLyEiyXy0lRFGZb18bTOhYR6GgwiOdJcu3Wa9R1bZxz5r1mespwuVxOnHO/mBla69jv9R6jwSAmALgZDu9934+JCMyMoiiMtfYuTdOgbZSmaWCtvSuKwjAziOigVbpZGIbhDACKojCnSLMsOyTz/bipPTB879nUWgvnnNkjxdnZ2TMAbDab6zbZaDQ62FCjpS5SIgIAMDOOkTVSItLZ9CRJxnmez5gZzRqlFIgIQRBMhkfiRZ1uADzPeyUiiMiOTERARPA87/VY3VHDr+pDDxtVVXXJzLtjAm9HZmZUVXV5rO5DD9M0DbIsmzSXQkQ4dSntifpA2JWzdmxO5XRnuE92KmddOd0n3Rl+NgGNPpsoAoB5koy7yLpenH6/n49Go2l79udJMgaAbz9ub02+Wv2s69poreNer/cQhuHs4uJi0zbbV1mWcxHJt9sttnVtmPmPc+63VkpBEa205z36vh8fm4C2rq6ucgDTxFqUZbkiolwplavmCwDenvB/MWtr/wv5CwCanfXE6iK0AAAAAElFTkSuQmCC')
;
background-repeat
:
no-repeat
;
background-size
:
11px
;
width
:
12px
;
height
:
12px
;
display
:
inline-block
;
position
:
absolute
;
right
:
13px
;
top
:
13px
;
cursor
:
pointer
;
}
.dropdown-menu
.dropdown-multi-menu-selections
{
max-height
:
150px
;
overflow-y
:
scroll
;
margin-top
:
15px
;
}
.dropdown-menu
.dropdown-multi-menu-category
ul
{
padding
:
0
5px
;
list-style
:
none
;
max-height
:
65px
;
overflow-y
:
scroll
;
}
.dropdown-menu
ul
.dropdown-multi-menu-header-list
{
padding
:
0
5px
;
list-style
:
none
;
max-height
:
75px
;
overflow-y
:
scroll
;
}
.dropdown-menu
.dropdown-multi-menu-selections
ul
{
padding
:
0
5px
;
list-style
:
none
;
}
.dropdown-menu
.dropdown-multi-menu-selections
li
,
.dropdown-menu
.dropdown-multi-menu-header-list
li
{
padding
:
7px
5px
;
}
.dropdown-menu
.dropdown-multi-menu-selections
li
.dropdown-multi-menu-list-striped
:nth-child
(
odd
)
{
background
:
#F5F5F5
;
}
.dropdown-menu
.dropdown-multi-menu-selections
li
a
.disabled
{
cursor
:
default
;
}
.dropdown-menu
.dropdown-multi-menu-selections
.dropdown-multi-menu-subtitle
{
font-size
:
12px
;
color
:
#9E9E9E
;
}
[
data-extra-menu
]
{
display
:
none
;
}
.dropdown-multi-menu-back-button
{
width
:
12px
;
height
:
12px
;
display
:
none
;
background
:
url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACDUlEQVRIibWWMZLaMBSGn2QBGS0MMJMUTCq7CdAEF9kLqNsqJ8gFlgvQUS0XIOcgF3CXagvMVvY2UKXF9srIYCQ5FQxkIdhL8kqN/H/vvV9PMsqyDPKG73ksA4BOp+Pk/Ybk3Thz3UGSJF8QQuB7HrRzQi4C5vN5LYqivhCCKSlZiRCnSNUXAVEU9eM4ftBaAyHEoTc3k063e32LDjPXWgPGGCilzude73te8b8CXmVOqdNoNMZFxE8CFotFLQzDw8wfKaWObdujouInAWEY7jMvlUoTSqnTK9iWk4A/MzcMA64VPwIEQdBfrVYPSqlr9F4Fevb92yAI7tM0bSutb7MsA4QQIISAEOKUy2WfEPIrj9huPjDGL5Vy2e90uw7hnH/lnH/DGAPCeL8xyzJI05RtNhuWd7B2+wzDgHeVyuTZ9zl+S9lFgtRqtR9Syo//o0Wf2u1HtFucTqeDQ5MJIVCtVvv/7BQ1m80xQgiEEExKya4RPYy9B6Zpctu2R5RSB2MMSikQQrDZbHZ/DcAYDodHC+v1+klrzZVSIKW8226375fL5YdWq/XzLQB07gi6rjs4ddmZpsmLAM7epvV6fQwAe0/iON75UujSOzsHlmUdeaK1BiEEeyroycUX7bASJSUTQrx4nufnffgvAizL4gAwmrkuJEnygjHmCKHcPpw1+VR4nscQQO4/CgCA3yx5RbfFRth/AAAAAElFTkSuQmCC')
;
float
:
left
;
background-size
:
12px
;
background-repeat
:
no-repeat
;
margin-top
:
3px
;
margin-left
:
10px
;
margin-right
:
-10px
;
}
.dropdown-menu
.dropdown-multi-menu-selections
ul
a
,
.dropdown-menu
.dropdown-multi-menu-category
ul
a
,
.dropdown-menu
.dropdown-multi-menu-header-item
a
{
text-decoration
:
none
;
color
:
#333
;
display
:
inline-block
;
width
:
285px
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
overflow
:
hidden
;
padding-left
:
30px
;
vertical-align
:
middle
;
}
.dropdown-menu
.dropdown-multi-menu-selections
ul
a
:focus
,
.dropdown-menu
.dropdown-multi-menu-category
ul
a
:focus
,
.dropdown-menu
.dropdown-multi-menu-header-item
a
:focus
{
border
:
none
;
outline
:
none
;
}
.dropdown-menu
.dropdown-multi-menu-selections
ul
a
.selected
,
.dropdown-menu
.dropdown-multi-menu-category
ul
a
.selected
,
.dropdown-menu
.dropdown-multi-menu-header
ul
a
.selected
{
background
:
url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAYCAMAAADat72NAAABtlBMVEX////b29vc29vc3NzZ2dna2dnb2trY19fZ2NjV1dXW1tbT09PU09PV1NTR0dHS0dHS0tLPz8/Qz8/Q0NDNzc3Ozc3Ly8vMy8vMzMzJycnKycnKysrIyMjJyMjKyMjFxcXGxcXGxsbHxcXHxsbEw8PExMTFxMTBwcHBwsLCwcG/v7+9vb2+vr6/vr67u7u8vLy5ubm6ubm7ubm3t7e4t7e4uLi1tbW3tra0s7OysbGysrKzsbGvr6+wr6+urq6vrq6sq6utrKypqamrqqqsqqqop6epp6eqqKimpaWnpaWnpqako6OkpKSlo6OioaGjoqKkoqKgn5+goKChn5+hoKCenp6fnp6cm5ucnJydm5udnJyenJx+fX1/fHx/fX2Afn6Af3+Bf3+BgICCgICCgYGDgYGDgoKEgoKFgoKFg4OGhISGhYWHhYWIh4eJh4eJiIiJiYmKiIiLiYmLioqMioqMi4uNi4uNjIyOjIyPjY2Qjo6Qj4+SkJCSkZGTkZGTkpKUk5OVk5OVlJSWlZWXlZWYlpaYl5eYmJiZmJiZmZmamJibmZmbmpqcm5uenp6fn5+op6erqqouFWZ5AAAAXHRSTlMADw8PFhYWHR0kJCwsLDMzMzo6OkJCSUlJUFBQV1dXX19fX19mZmZtbW11fHx8g4OKioqSkpKZmaCoqKivr7a2vb3FxcXMzMzT09Pb29vi4uLp6enp8PD4+Pj4+E8lS9YAAAEjSURBVCjPY2DACYRtrDhwywralTZL45RldymLb5LEJctkWBOXHiSAS9q8OSOzxRiXrGJPWmqdNTcOWY3ggtQyN15cXvIpTCnxVcIhK+pYnZwZLYdDls22Nj2rywiHLKNeW2p8oxaMK6vGgiKt05edVGXPDOWJB7S6KyPJSgVnxZRb8sC4Co2JJV4icFkJl6KYXB8hOJ8/KiW5wl8TyuN0KonP81BEMs24Iye10A8S+Kwm1bGZESoobtHszkwtDtUGMS0aUjM7TdG8YhRZmJIdLc/IpNqWktpoxonuVeWw3OTC3gkT+/NTK525MINCN6QsNSuvND+93FUGW1CJeZekJiSn5wSKYw9KNc/q9IS8cH1cCYDPoT63XR13ypQwczbAEAQAnkJCZAp/V+QAAAAASUVORK5CYII=')
;
background-repeat
:
no-repeat
;
background-size
:
16px
;
background-position
:
2px
4px
;
}
.dropdown-menu
.dropdown-multi-menu-selections
ul
a
.selected.dropdown-multi-menu-item-with-subtitle
,
.dropdown-menu
.dropdown-multi-menu-selections
ul
a
.selected.dropdown-multi-menu-with-image
{
background-position
:
2px
12px
;
}
.dropdown-menu
.dropdown-multi-menu-image
{
background-size
:
cover
;
/*crop from the center*/
background-position
:
center
;
width
:
30px
;
height
:
30px
;
display
:
inline-block
;
position
:
relative
;
border-radius
:
15px
;
float
:
left
;
margin-top
:
3px
;
margin-right
:
11px
;
}
.dropdown-menu
.dropdown-multi-menu-tip-area
{
margin
:
10px
;
text-align
:
left
;
max-height
:
40px
;
overflow-y
:
scroll
;
}
.dropdown-menu
.dropdown-multi-menu-header-area
{
}
.dropdown-menu
.dropdown-multi-menu-tip-area
p
{
}
.dropdown-menu
.input-with-icon
{
position
:
relative
;
margin
:
10px
0
;
}
.dropdown-menu
.input-with-icon
i
{
position
:
absolute
;
right
:
0
;
padding
:
10px
12px
;
color
:
#817F7F
;
pointer-events
:
none
;
}
app/controllers/projects/milestones_controller.rb
浏览文件 @
2b8457d6
...
...
@@ -19,6 +19,10 @@ class Projects::MilestonesController < Projects::ApplicationController
@milestones
=
@milestones
.
includes
(
:project
)
@milestones
=
@milestones
.
page
(
params
[
:page
]).
per
(
PER_PAGE
)
respond_to
do
|
format
|
format
.
html
# show.html.erb
format
.
json
{
render
:json
=>
@milestones
}
end
end
def
new
...
...
app/helpers/milestones_helper.rb
浏览文件 @
2b8457d6
...
...
@@ -20,7 +20,7 @@ module MilestonesHelper
end
end
def
projects_milestones_options
def
projects_milestones_
data_
options
milestones
=
if
@project
@project
.
milestones
...
...
@@ -28,12 +28,11 @@ module MilestonesHelper
Milestone
.
where
(
project_id:
@projects
)
end
.
active
epoch
=
DateTime
.
parse
(
'1970-01-01'
)
grouped_milestones
=
GlobalMilestone
.
build_collection
(
milestones
)
grouped_milestones
=
grouped_milestones
.
sort_by
{
|
x
|
x
.
due_date
.
nil?
?
epoch
:
x
.
due_date
}
grouped_milestones
.
unshift
(
Milestone
::
None
)
grouped_milestones
.
unshift
(
Milestone
::
Any
)
milestones
.
as_json
only:
[
:title
,
:id
]
end
options_from_collection_for_select
(
grouped_milestones
,
'name'
,
'title'
,
params
[
:milestone_title
])
def
projects_milestones_header_options
grouped_milestones
=
[
Milestone
::
Any
,
Milestone
::
None
].
as_json
end
end
app/helpers/selects_helper.rb
浏览文件 @
2b8457d6
...
...
@@ -36,6 +36,41 @@ module SelectsHelper
hidden_field_tag
(
id
,
value
,
html
)
end
def
multi_select_tag
(
name
,
opts
=
{})
css_class
=
"dropdown-menu "
css_class
<<
(
opts
[
:class
]
||
''
)
header
=
opts
[
:header
]
||
''
ul_html
=
{
class:
css_class
,
data:
{
header:
header
,
data:
opts
[
:header_url
],
"multi-awesome"
=>
''
,
"data-object"
=>
{
label:
"title"
,
data:
"id"
}
}
}
button_html
=
{
class:
[
"btn"
,
"btn-default"
,
"dropdown-toggle"
],
type:
"button"
,
data:
{
toggle:
"dropdown"
}
}
button_class
=
"btn btn-default dropdown-toggle"
content_tag
:div
,
:class
=>
"button-group"
do
content_tag
(
:button
,
content_tag
(
:span
,
name
)
+
content_tag
(
:span
,
nil
,
:class
=>
"caret"
),
button_html
)
+
content_tag
(
:ul
,
nil
,
ul_html
)
end
end
def
groups_select_tag
(
id
,
opts
=
{})
opts
[
:class
]
||=
''
opts
[
:class
]
<<
' ajax-groups-select'
...
...
app/views/shared/issuable/_filter.html.haml
浏览文件 @
2b8457d6
...
...
@@ -38,9 +38,7 @@
placeholder:
'Assignee'
,
class:
'trigger-submit'
,
any_user:
"Any Assignee"
,
null_user:
true
,
first_user:
true
,
current_user:
true
)
.filter-item.inline.milestone-filter
=
select_tag
(
'milestone_title'
,
projects_milestones_options
,
class:
'select2 trigger-submit'
,
include_blank:
true
,
data:
{
placeholder:
'Milestone'
})
=
multi_select_tag
(
'Milestone'
,
class:
'milestone-filter'
,
header:
projects_milestones_header_options
)
.filter-item.inline.labels-filter
=
select_tag
(
'label_name'
,
projects_labels_options
,
...
...
vendor/assets/javascripts/sifter.js
0 → 100644
浏览文件 @
2b8457d6
/**
* sifter.js
* Copyright (c) 2013 Brian Reavis & contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
* @author Brian Reavis <brian@thirdroute.com>
*/
(
function
(
root
,
factory
)
{
if
(
typeof
define
===
'
function
'
&&
define
.
amd
)
{
define
(
factory
);
}
else
if
(
typeof
exports
===
'
object
'
)
{
module
.
exports
=
factory
();
}
else
{
root
.
Sifter
=
factory
();
}
}(
this
,
function
()
{
/**
* Textually searches arrays and hashes of objects
* by property (or multiple properties). Designed
* specifically for autocomplete.
*
* @constructor
* @param {array|object} items
* @param {object} items
*/
var
Sifter
=
function
(
items
,
settings
)
{
this
.
items
=
items
;
this
.
settings
=
settings
||
{
diacritics
:
true
};
};
/**
* Splits a search string into an array of individual
* regexps to be used to match results.
*
* @param {string} query
* @returns {array}
*/
Sifter
.
prototype
.
tokenize
=
function
(
query
)
{
query
=
trim
(
String
(
query
||
''
).
toLowerCase
());
if
(
!
query
||
!
query
.
length
)
return
[];
var
i
,
n
,
regex
,
letter
;
var
tokens
=
[];
var
words
=
query
.
split
(
/ +/
);
for
(
i
=
0
,
n
=
words
.
length
;
i
<
n
;
i
++
)
{
regex
=
escape_regex
(
words
[
i
]);
if
(
this
.
settings
.
diacritics
)
{
for
(
letter
in
DIACRITICS
)
{
if
(
DIACRITICS
.
hasOwnProperty
(
letter
))
{
regex
=
regex
.
replace
(
new
RegExp
(
letter
,
'
g
'
),
DIACRITICS
[
letter
]);
}
}
}
tokens
.
push
({
string
:
words
[
i
],
regex
:
new
RegExp
(
regex
,
'
i
'
)
});
}
return
tokens
;
};
/**
* Iterates over arrays and hashes.
*
* ```
* this.iterator(this.items, function(item, id) {
* // invoked for each item
* });
* ```
*
* @param {array|object} object
*/
Sifter
.
prototype
.
iterator
=
function
(
object
,
callback
)
{
var
iterator
;
if
(
is_array
(
object
))
{
iterator
=
Array
.
prototype
.
forEach
||
function
(
callback
)
{
for
(
var
i
=
0
,
n
=
this
.
length
;
i
<
n
;
i
++
)
{
callback
(
this
[
i
],
i
,
this
);
}
};
}
else
{
iterator
=
function
(
callback
)
{
for
(
var
key
in
this
)
{
if
(
this
.
hasOwnProperty
(
key
))
{
callback
(
this
[
key
],
key
,
this
);
}
}
};
}
iterator
.
apply
(
object
,
[
callback
]);
};
/**
* Returns a function to be used to score individual results.
*
* Good matches will have a higher score than poor matches.
* If an item is not a match, 0 will be returned by the function.
*
* @param {object|string} search
* @param {object} options (optional)
* @returns {function}
*/
Sifter
.
prototype
.
getScoreFunction
=
function
(
search
,
options
)
{
var
self
,
fields
,
tokens
,
token_count
;
self
=
this
;
search
=
self
.
prepareSearch
(
search
,
options
);
tokens
=
search
.
tokens
;
fields
=
search
.
options
.
fields
;
token_count
=
tokens
.
length
;
/**
* Calculates how close of a match the
* given value is against a search token.
*
* @param {mixed} value
* @param {object} token
* @return {number}
*/
var
scoreValue
=
function
(
value
,
token
)
{
var
score
,
pos
;
if
(
!
value
)
return
0
;
value
=
String
(
value
||
''
);
pos
=
value
.
search
(
token
.
regex
);
if
(
pos
===
-
1
)
return
0
;
score
=
token
.
string
.
length
/
value
.
length
;
if
(
pos
===
0
)
score
+=
0.5
;
return
score
;
};
/**
* Calculates the score of an object
* against the search query.
*
* @param {object} token
* @param {object} data
* @return {number}
*/
var
scoreObject
=
(
function
()
{
var
field_count
=
fields
.
length
;
if
(
!
field_count
)
{
return
function
()
{
return
0
;
};
}
if
(
field_count
===
1
)
{
return
function
(
token
,
data
)
{
return
scoreValue
(
data
[
fields
[
0
]],
token
);
};
}
return
function
(
token
,
data
)
{
for
(
var
i
=
0
,
sum
=
0
;
i
<
field_count
;
i
++
)
{
sum
+=
scoreValue
(
data
[
fields
[
i
]],
token
);
}
return
sum
/
field_count
;
};
})();
if
(
!
token_count
)
{
return
function
()
{
return
0
;
};
}
if
(
token_count
===
1
)
{
return
function
(
data
)
{
return
scoreObject
(
tokens
[
0
],
data
);
};
}
if
(
search
.
options
.
conjunction
===
'
and
'
)
{
return
function
(
data
)
{
var
score
;
for
(
var
i
=
0
,
sum
=
0
;
i
<
token_count
;
i
++
)
{
score
=
scoreObject
(
tokens
[
i
],
data
);
if
(
score
<=
0
)
return
0
;
sum
+=
score
;
}
return
sum
/
token_count
;
};
}
else
{
return
function
(
data
)
{
for
(
var
i
=
0
,
sum
=
0
;
i
<
token_count
;
i
++
)
{
sum
+=
scoreObject
(
tokens
[
i
],
data
);
}
return
sum
/
token_count
;
};
}
};
/**
* Returns a function that can be used to compare two
* results, for sorting purposes. If no sorting should
* be performed, `null` will be returned.
*
* @param {string|object} search
* @param {object} options
* @return function(a,b)
*/
Sifter
.
prototype
.
getSortFunction
=
function
(
search
,
options
)
{
var
i
,
n
,
self
,
field
,
fields
,
fields_count
,
multiplier
,
multipliers
,
get_field
,
implicit_score
,
sort
;
self
=
this
;
search
=
self
.
prepareSearch
(
search
,
options
);
sort
=
(
!
search
.
query
&&
options
.
sort_empty
)
||
options
.
sort
;
/**
* Fetches the specified sort field value
* from a search result item.
*
* @param {string} name
* @param {object} result
* @return {mixed}
*/
get_field
=
function
(
name
,
result
)
{
if
(
name
===
'
$score
'
)
return
result
.
score
;
return
self
.
items
[
result
.
id
][
name
];
};
// parse options
fields
=
[];
if
(
sort
)
{
for
(
i
=
0
,
n
=
sort
.
length
;
i
<
n
;
i
++
)
{
if
(
search
.
query
||
sort
[
i
].
field
!==
'
$score
'
)
{
fields
.
push
(
sort
[
i
]);
}
}
}
// the "$score" field is implied to be the primary
// sort field, unless it's manually specified
if
(
search
.
query
)
{
implicit_score
=
true
;
for
(
i
=
0
,
n
=
fields
.
length
;
i
<
n
;
i
++
)
{
if
(
fields
[
i
].
field
===
'
$score
'
)
{
implicit_score
=
false
;
break
;
}
}
if
(
implicit_score
)
{
fields
.
unshift
({
field
:
'
$score
'
,
direction
:
'
desc
'
});
}
}
else
{
for
(
i
=
0
,
n
=
fields
.
length
;
i
<
n
;
i
++
)
{
if
(
fields
[
i
].
field
===
'
$score
'
)
{
fields
.
splice
(
i
,
1
);
break
;
}
}
}
multipliers
=
[];
for
(
i
=
0
,
n
=
fields
.
length
;
i
<
n
;
i
++
)
{
multipliers
.
push
(
fields
[
i
].
direction
===
'
desc
'
?
-
1
:
1
);
}
// build function
fields_count
=
fields
.
length
;
if
(
!
fields_count
)
{
return
null
;
}
else
if
(
fields_count
===
1
)
{
field
=
fields
[
0
].
field
;
multiplier
=
multipliers
[
0
];
return
function
(
a
,
b
)
{
return
multiplier
*
cmp
(
get_field
(
field
,
a
),
get_field
(
field
,
b
)
);
};
}
else
{
return
function
(
a
,
b
)
{
var
i
,
result
,
a_value
,
b_value
,
field
;
for
(
i
=
0
;
i
<
fields_count
;
i
++
)
{
field
=
fields
[
i
].
field
;
result
=
multipliers
[
i
]
*
cmp
(
get_field
(
field
,
a
),
get_field
(
field
,
b
)
);
if
(
result
)
return
result
;
}
return
0
;
};
}
};
/**
* Parses a search query and returns an object
* with tokens and fields ready to be populated
* with results.
*
* @param {string} query
* @param {object} options
* @returns {object}
*/
Sifter
.
prototype
.
prepareSearch
=
function
(
query
,
options
)
{
if
(
typeof
query
===
'
object
'
)
return
query
;
options
=
extend
({},
options
);
var
option_fields
=
options
.
fields
;
var
option_sort
=
options
.
sort
;
var
option_sort_empty
=
options
.
sort_empty
;
if
(
option_fields
&&
!
is_array
(
option_fields
))
options
.
fields
=
[
option_fields
];
if
(
option_sort
&&
!
is_array
(
option_sort
))
options
.
sort
=
[
option_sort
];
if
(
option_sort_empty
&&
!
is_array
(
option_sort_empty
))
options
.
sort_empty
=
[
option_sort_empty
];
return
{
options
:
options
,
query
:
String
(
query
||
''
).
toLowerCase
(),
tokens
:
this
.
tokenize
(
query
),
total
:
0
,
items
:
[]
};
};
/**
* Searches through all items and returns a sorted array of matches.
*
* The `options` parameter can contain:
*
* - fields {string|array}
* - sort {array}
* - score {function}
* - filter {bool}
* - limit {integer}
*
* Returns an object containing:
*
* - options {object}
* - query {string}
* - tokens {array}
* - total {int}
* - items {array}
*
* @param {string} query
* @param {object} options
* @returns {object}
*/
Sifter
.
prototype
.
search
=
function
(
query
,
options
)
{
var
self
=
this
,
value
,
score
,
search
,
calculateScore
;
var
fn_sort
;
var
fn_score
;
search
=
this
.
prepareSearch
(
query
,
options
);
options
=
search
.
options
;
query
=
search
.
query
;
// generate result scoring function
fn_score
=
options
.
score
||
self
.
getScoreFunction
(
search
);
// perform search and sort
if
(
query
.
length
)
{
self
.
iterator
(
self
.
items
,
function
(
item
,
id
)
{
score
=
fn_score
(
item
);
if
(
options
.
filter
===
false
||
score
>
0
)
{
search
.
items
.
push
({
'
score
'
:
score
,
'
id
'
:
id
});
}
});
}
else
{
self
.
iterator
(
self
.
items
,
function
(
item
,
id
)
{
search
.
items
.
push
({
'
score
'
:
1
,
'
id
'
:
id
});
});
}
fn_sort
=
self
.
getSortFunction
(
search
,
options
);
if
(
fn_sort
)
search
.
items
.
sort
(
fn_sort
);
// apply limits
search
.
total
=
search
.
items
.
length
;
if
(
typeof
options
.
limit
===
'
number
'
)
{
search
.
items
=
search
.
items
.
slice
(
0
,
options
.
limit
);
}
return
search
;
};
// utilities
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
var
cmp
=
function
(
a
,
b
)
{
if
(
typeof
a
===
'
number
'
&&
typeof
b
===
'
number
'
)
{
return
a
>
b
?
1
:
(
a
<
b
?
-
1
:
0
);
}
a
=
asciifold
(
String
(
a
||
''
));
b
=
asciifold
(
String
(
b
||
''
));
if
(
a
>
b
)
return
1
;
if
(
b
>
a
)
return
-
1
;
return
0
;
};
var
extend
=
function
(
a
,
b
)
{
var
i
,
n
,
k
,
object
;
for
(
i
=
1
,
n
=
arguments
.
length
;
i
<
n
;
i
++
)
{
object
=
arguments
[
i
];
if
(
!
object
)
continue
;
for
(
k
in
object
)
{
if
(
object
.
hasOwnProperty
(
k
))
{
a
[
k
]
=
object
[
k
];
}
}
}
return
a
;
};
var
trim
=
function
(
str
)
{
return
(
str
+
''
).
replace
(
/^
\s
+|
\s
+$|/g
,
''
);
};
var
escape_regex
=
function
(
str
)
{
return
(
str
+
''
).
replace
(
/
([
.?*+^$[
\]\\
(){}|-
])
/g
,
'
\\
$1
'
);
};
var
is_array
=
Array
.
isArray
||
(
typeof
$
!==
'
undefined
'
&&
$
.
isArray
)
||
function
(
object
)
{
return
Object
.
prototype
.
toString
.
call
(
object
)
===
'
[object Array]
'
;
};
var
DIACRITICS
=
{
'
a
'
:
'
[aÀÁÂÃÄÅàáâãäåĀāąĄ]
'
,
'
c
'
:
'
[cÇçćĆčČ]
'
,
'
d
'
:
'
[dđĐďĎð]
'
,
'
e
'
:
'
[eÈÉÊËèéêëěĚĒēęĘ]
'
,
'
i
'
:
'
[iÌÍÎÏìíîïĪī]
'
,
'
l
'
:
'
[lłŁ]
'
,
'
n
'
:
'
[nÑñňŇńŃ]
'
,
'
o
'
:
'
[oÒÓÔÕÕÖØòóôõöøŌō]
'
,
'
r
'
:
'
[rřŘ]
'
,
'
s
'
:
'
[sŠšśŚ]
'
,
'
t
'
:
'
[tťŤ]
'
,
'
u
'
:
'
[uÙÚÛÜùúûüůŮŪū]
'
,
'
y
'
:
'
[yŸÿýÝ]
'
,
'
z
'
:
'
[zŽžżŻźŹ]
'
};
var
asciifold
=
(
function
()
{
var
i
,
n
,
k
,
chunk
;
var
foreignletters
=
''
;
var
lookup
=
{};
for
(
k
in
DIACRITICS
)
{
if
(
DIACRITICS
.
hasOwnProperty
(
k
))
{
chunk
=
DIACRITICS
[
k
].
substring
(
2
,
DIACRITICS
[
k
].
length
-
1
);
foreignletters
+=
chunk
;
for
(
i
=
0
,
n
=
chunk
.
length
;
i
<
n
;
i
++
)
{
lookup
[
chunk
.
charAt
(
i
)]
=
k
;
}
}
}
var
regexp
=
new
RegExp
(
'
[
'
+
foreignletters
+
'
]
'
,
'
g
'
);
return
function
(
str
)
{
return
str
.
replace
(
regexp
,
function
(
foreignletter
)
{
return
lookup
[
foreignletter
];
}).
toLowerCase
();
};
})();
// export
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
return
Sifter
;
}));
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录