Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
gitlab-ce
Commits
aa90e8ea
Commit
aa90e8ea
authored
Dec 13, 2017
by
Filipa Lacerda
Committed by
Phil Hughes
Dec 13, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Export old code into es6 modules
parent
bcb14a0d
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
688 additions
and
706 deletions
+688
-706
app/assets/javascripts/dispatcher.js
app/assets/javascripts/dispatcher.js
+4
-3
app/assets/javascripts/main.js
app/assets/javascripts/main.js
+0
-5
app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
...ets/javascripts/merge_conflicts/merge_conflicts_bundle.js
+2
-1
app/assets/javascripts/merge_request_tabs.js
app/assets/javascripts/merge_request_tabs.js
+2
-1
app/assets/javascripts/project_find_file.js
app/assets/javascripts/project_find_file.js
+146
-152
app/assets/javascripts/render_gfm.js
app/assets/javascripts/render_gfm.js
+2
-2
app/assets/javascripts/repo/components/repo_preview.vue
app/assets/javascripts/repo/components/repo_preview.vue
+2
-1
app/assets/javascripts/search.js
app/assets/javascripts/search.js
+101
-106
app/assets/javascripts/search_autocomplete.js
app/assets/javascripts/search_autocomplete.js
+383
-386
app/assets/javascripts/single_file_diff.js
app/assets/javascripts/single_file_diff.js
+2
-1
app/assets/javascripts/syntax_highlight.js
app/assets/javascripts/syntax_highlight.js
+6
-8
spec/javascripts/search_autocomplete_spec.js
spec/javascripts/search_autocomplete_spec.js
+2
-2
spec/javascripts/syntax_highlight_spec.js
spec/javascripts/syntax_highlight_spec.js
+36
-38
No files found.
app/assets/javascripts/dispatcher.js
View file @
aa90e8ea
...
...
@@ -15,7 +15,7 @@ import GroupLabelSubscription from './group_label_subscription';
import
BuildArtifacts
from
'
./build_artifacts
'
;
import
CILintEditor
from
'
./ci_lint_editor
'
;
import
groupsSelect
from
'
./groups_select
'
;
/* global Search */
import
Search
from
'
./search
'
;
/* global Admin */
import
NamespaceSelect
from
'
./namespace_select
'
;
import
NewCommitForm
from
'
./new_commit_form
'
;
...
...
@@ -24,7 +24,7 @@ import projectAvatar from './project_avatar';
/* global MergeRequest */
import
Compare
from
'
./compare
'
;
import
initCompareAutocomplete
from
'
./compare_autocomplete
'
;
/* global ProjectFindFile */
import
ProjectFindFile
from
'
./project_find_file
'
;
import
ProjectNew
from
'
./project_new
'
;
import
projectImport
from
'
./project_import
'
;
import
Labels
from
'
./labels
'
;
...
...
@@ -91,6 +91,7 @@ import DueDateSelectors from './due_date_select';
import
Diff
from
'
./diff
'
;
import
ProjectLabelSubscription
from
'
./project_label_subscription
'
;
import
ProjectVariables
from
'
./project_variables
'
;
import
SearchAutocomplete
from
'
./search_autocomplete
'
;
(
function
()
{
var
Dispatcher
;
...
...
@@ -683,7 +684,7 @@ import ProjectVariables from './project_variables';
Dispatcher
.
prototype
.
initSearch
=
function
()
{
// Only when search form is present
if
(
$
(
'
.search
'
).
length
)
{
return
new
gl
.
SearchAutocomplete
();
return
new
SearchAutocomplete
();
}
};
...
...
app/assets/javascripts/main.js
View file @
aa90e8ea
...
...
@@ -60,15 +60,10 @@ import './notifications_dropdown';
import
'
./notifications_form
'
;
import
'
./pager
'
;
import
'
./preview_markdown
'
;
import
'
./project_find_file
'
;
import
'
./project_import
'
;
import
'
./projects_dropdown
'
;
import
'
./projects_list
'
;
import
'
./syntax_highlight
'
;
import
'
./render_gfm
'
;
import
'
./right_sidebar
'
;
import
'
./search
'
;
import
'
./search_autocomplete
'
;
import
initBreadcrumbs
from
'
./breadcrumb
'
;
import
'
./dispatcher
'
;
...
...
app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
View file @
aa90e8ea
...
...
@@ -10,6 +10,7 @@ import './mixins/line_conflict_actions';
import
'
./components/diff_file_editor
'
;
import
'
./components/inline_conflict_lines
'
;
import
'
./components/parallel_conflict_lines
'
;
import
syntaxHighlight
from
'
../syntax_highlight
'
;
$
(()
=>
{
const
INTERACTIVE_RESOLVE_MODE
=
'
interactive
'
;
...
...
@@ -53,7 +54,7 @@ $(() => {
mergeConflictsStore
.
setLoadingState
(
false
);
this
.
$nextTick
(()
=>
{
$
(
'
.js-syntax-highlight
'
).
syntaxHighlight
(
);
syntaxHighlight
(
$
(
'
.js-syntax-highlight
'
)
);
});
});
},
...
...
app/assets/javascripts/merge_request_tabs.js
View file @
aa90e8ea
...
...
@@ -14,6 +14,7 @@ import {
import
{
getLocationHash
}
from
'
./lib/utils/url_utility
'
;
import
initDiscussionTab
from
'
./image_diff/init_discussion_tab
'
;
import
Diff
from
'
./diff
'
;
import
syntaxHighlight
from
'
./syntax_highlight
'
;
/* eslint-disable max-len */
// MergeRequestTabs
...
...
@@ -295,7 +296,7 @@ import Diff from './diff';
}
gl
.
utils
.
localTimeAgo
(
$
(
'
.js-timeago
'
,
'
div#diffs
'
));
$
(
'
#diffs .js-syntax-highlight
'
).
syntaxHighlight
(
);
syntaxHighlight
(
$
(
'
#diffs .js-syntax-highlight
'
)
);
if
(
this
.
diffViewType
()
===
'
parallel
'
&&
this
.
isDiffAction
(
this
.
currentAction
))
{
this
.
expandViewContainer
();
...
...
app/assets/javascripts/project_find_file.js
View file @
aa90e8ea
...
...
@@ -2,169 +2,163 @@
import
fuzzaldrinPlus
from
'
fuzzaldrin-plus
'
;
(
function
()
{
this
.
ProjectFindFile
=
(
function
()
{
var
highlighter
;
function
ProjectFindFile
(
element1
,
options
)
{
this
.
element
=
element1
;
this
.
options
=
options
;
this
.
goToBlob
=
this
.
goToBlob
.
bind
(
this
);
this
.
goToTree
=
this
.
goToTree
.
bind
(
this
);
this
.
selectRowDown
=
this
.
selectRowDown
.
bind
(
this
);
this
.
selectRowUp
=
this
.
selectRowUp
.
bind
(
this
);
this
.
filePaths
=
{};
this
.
inputElement
=
this
.
element
.
find
(
"
.file-finder-input
"
);
// init event
this
.
initEvent
();
// focus text input box
this
.
inputElement
.
focus
();
// load file list
this
.
load
(
this
.
options
.
url
);
// highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
const
highlighter
=
function
(
element
,
text
,
matches
)
{
var
highlightText
,
j
,
lastIndex
,
len
,
matchIndex
,
matchedChars
,
unmatched
;
lastIndex
=
0
;
highlightText
=
""
;
matchedChars
=
[];
for
(
j
=
0
,
len
=
matches
.
length
;
j
<
len
;
j
+=
1
)
{
matchIndex
=
matches
[
j
];
unmatched
=
text
.
substring
(
lastIndex
,
matchIndex
);
if
(
unmatched
)
{
if
(
matchedChars
.
length
)
{
element
.
append
(
matchedChars
.
join
(
""
).
bold
());
}
matchedChars
=
[];
element
.
append
(
document
.
createTextNode
(
unmatched
));
}
ProjectFindFile
.
prototype
.
initEvent
=
function
()
{
this
.
inputElement
.
off
(
"
keyup
"
);
this
.
inputElement
.
on
(
"
keyup
"
,
(
function
(
_this
)
{
return
function
(
event
)
{
var
oldValue
,
ref
,
target
,
value
;
target
=
$
(
event
.
target
);
value
=
target
.
val
();
oldValue
=
(
ref
=
target
.
data
(
"
oldValue
"
))
!=
null
?
ref
:
""
;
if
(
value
!==
oldValue
)
{
target
.
data
(
"
oldValue
"
,
value
);
_this
.
findFile
();
return
_this
.
element
.
find
(
"
tr.tree-item
"
).
eq
(
0
).
addClass
(
"
selected
"
).
focus
();
}
};
})(
this
));
};
ProjectFindFile
.
prototype
.
findFile
=
function
()
{
var
result
,
searchText
;
searchText
=
this
.
inputElement
.
val
();
result
=
searchText
.
length
>
0
?
fuzzaldrinPlus
.
filter
(
this
.
filePaths
,
searchText
)
:
this
.
filePaths
;
return
this
.
renderList
(
result
,
searchText
);
// find file
};
matchedChars
.
push
(
text
[
matchIndex
]);
lastIndex
=
matchIndex
+
1
;
}
if
(
matchedChars
.
length
)
{
element
.
append
(
matchedChars
.
join
(
""
).
bold
());
}
return
element
.
append
(
document
.
createTextNode
(
text
.
substring
(
lastIndex
)));
};
export
default
class
ProjectFindFile
{
constructor
(
element1
,
options
)
{
this
.
element
=
element1
;
this
.
options
=
options
;
this
.
goToBlob
=
this
.
goToBlob
.
bind
(
this
);
this
.
goToTree
=
this
.
goToTree
.
bind
(
this
);
this
.
selectRowDown
=
this
.
selectRowDown
.
bind
(
this
);
this
.
selectRowUp
=
this
.
selectRowUp
.
bind
(
this
);
this
.
filePaths
=
{};
this
.
inputElement
=
this
.
element
.
find
(
"
.file-finder-input
"
);
// init event
this
.
initEvent
();
// focus text input box
this
.
inputElement
.
focus
();
// load file list
this
.
load
(
this
.
options
.
url
);
}
initEvent
()
{
this
.
inputElement
.
off
(
"
keyup
"
);
this
.
inputElement
.
on
(
"
keyup
"
,
(
function
(
_this
)
{
return
function
(
event
)
{
var
oldValue
,
ref
,
target
,
value
;
target
=
$
(
event
.
target
);
value
=
target
.
val
();
oldValue
=
(
ref
=
target
.
data
(
"
oldValue
"
))
!=
null
?
ref
:
""
;
if
(
value
!==
oldValue
)
{
target
.
data
(
"
oldValue
"
,
value
);
_this
.
findFile
();
return
_this
.
element
.
find
(
"
tr.tree-item
"
).
eq
(
0
).
addClass
(
"
selected
"
).
focus
();
}
};
})(
this
));
}
findFile
()
{
var
result
,
searchText
;
searchText
=
this
.
inputElement
.
val
();
result
=
searchText
.
length
>
0
?
fuzzaldrinPlus
.
filter
(
this
.
filePaths
,
searchText
)
:
this
.
filePaths
;
return
this
.
renderList
(
result
,
searchText
);
// find file
}
// files pathes load
ProjectFindFile
.
prototype
.
load
=
function
(
url
)
{
return
$
.
ajax
({
url
:
url
,
method
:
"
get
"
,
dataType
:
"
json
"
,
success
:
(
function
(
_this
)
{
return
function
(
data
)
{
_this
.
element
.
find
(
"
.loading
"
).
hide
();
_this
.
filePaths
=
data
;
_this
.
findFile
();
return
_this
.
element
.
find
(
"
.files-slider tr.tree-item
"
).
eq
(
0
).
addClass
(
"
selected
"
).
focus
();
};
})(
this
)
});
};
load
(
url
)
{
return
$
.
ajax
({
url
:
url
,
method
:
"
get
"
,
dataType
:
"
json
"
,
success
:
(
function
(
_this
)
{
return
function
(
data
)
{
_this
.
element
.
find
(
"
.loading
"
).
hide
();
_this
.
filePaths
=
data
;
_this
.
findFile
();
return
_this
.
element
.
find
(
"
.files-slider tr.tree-item
"
).
eq
(
0
).
addClass
(
"
selected
"
).
focus
();
};
})(
this
)
});
}
// render result
ProjectFindFile
.
prototype
.
renderList
=
function
(
filePaths
,
searchText
)
{
var
blobItemUrl
,
filePath
,
html
,
i
,
j
,
len
,
matches
,
results
;
this
.
element
.
find
(
"
.tree-table > tbody
"
).
empty
();
results
=
[];
for
(
i
=
j
=
0
,
len
=
filePaths
.
length
;
j
<
len
;
i
=
(
j
+=
1
))
{
filePath
=
filePaths
[
i
];
if
(
i
===
20
)
{
break
;
}
if
(
searchText
)
{
matches
=
fuzzaldrinPlus
.
match
(
filePath
,
searchText
);
}
blobItemUrl
=
this
.
options
.
blobUrlTemplate
+
"
/
"
+
filePath
;
html
=
this
.
makeHtml
(
filePath
,
matches
,
blobItemUrl
);
results
.
push
(
this
.
element
.
find
(
"
.tree-table > tbody
"
).
append
(
html
));
}
return
results
;
};
// highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
highlighter
=
function
(
element
,
text
,
matches
)
{
var
highlightText
,
j
,
lastIndex
,
len
,
matchIndex
,
matchedChars
,
unmatched
;
lastIndex
=
0
;
highlightText
=
""
;
matchedChars
=
[];
for
(
j
=
0
,
len
=
matches
.
length
;
j
<
len
;
j
+=
1
)
{
matchIndex
=
matches
[
j
];
unmatched
=
text
.
substring
(
lastIndex
,
matchIndex
);
if
(
unmatched
)
{
if
(
matchedChars
.
length
)
{
element
.
append
(
matchedChars
.
join
(
""
).
bold
());
}
matchedChars
=
[];
element
.
append
(
document
.
createTextNode
(
unmatched
));
}
matchedChars
.
push
(
text
[
matchIndex
]);
lastIndex
=
matchIndex
+
1
;
}
if
(
matchedChars
.
length
)
{
element
.
append
(
matchedChars
.
join
(
""
).
bold
());
renderList
(
filePaths
,
searchText
)
{
var
blobItemUrl
,
filePath
,
html
,
i
,
j
,
len
,
matches
,
results
;
this
.
element
.
find
(
"
.tree-table > tbody
"
).
empty
();
results
=
[];
for
(
i
=
j
=
0
,
len
=
filePaths
.
length
;
j
<
len
;
i
=
(
j
+=
1
))
{
filePath
=
filePaths
[
i
];
if
(
i
===
20
)
{
break
;
}
return
element
.
append
(
document
.
createTextNode
(
text
.
substring
(
lastIndex
)));
};
// make tbody row html
ProjectFindFile
.
prototype
.
makeHtml
=
function
(
filePath
,
matches
,
blobItemUrl
)
{
var
$tr
;
$tr
=
$
(
"
<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>
"
);
if
(
matches
)
{
$tr
.
find
(
"
a
"
).
replaceWith
(
highlighter
(
$tr
.
find
(
"
a
"
),
filePath
,
matches
).
attr
(
"
href
"
,
blobItemUrl
));
}
else
{
$tr
.
find
(
"
a
"
).
attr
(
"
href
"
,
blobItemUrl
);
$tr
.
find
(
"
.str-truncated
"
).
text
(
filePath
);
if
(
searchText
)
{
matches
=
fuzzaldrinPlus
.
match
(
filePath
,
searchText
);
}
return
$tr
;
};
ProjectFindFile
.
prototype
.
selectRow
=
function
(
type
)
{
var
next
,
rows
,
selectedRow
;
rows
=
this
.
element
.
find
(
"
.files-slider tr.tree-item
"
);
selectedRow
=
this
.
element
.
find
(
"
.files-slider tr.tree-item.selected
"
);
if
(
rows
&&
rows
.
length
>
0
)
{
if
(
selectedRow
&&
selectedRow
.
length
>
0
)
{
if
(
type
===
"
UP
"
)
{
next
=
selectedRow
.
prev
();
}
else
if
(
type
===
"
DOWN
"
)
{
next
=
selectedRow
.
next
();
}
if
(
next
.
length
>
0
)
{
selectedRow
.
removeClass
(
"
selected
"
);
selectedRow
=
next
;
}
}
else
{
selectedRow
=
rows
.
eq
(
0
);
blobItemUrl
=
this
.
options
.
blobUrlTemplate
+
"
/
"
+
filePath
;
html
=
ProjectFindFile
.
makeHtml
(
filePath
,
matches
,
blobItemUrl
);
results
.
push
(
this
.
element
.
find
(
"
.tree-table > tbody
"
).
append
(
html
));
}
return
results
;
}
// make tbody row html
static
makeHtml
(
filePath
,
matches
,
blobItemUrl
)
{
var
$tr
;
$tr
=
$
(
"
<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>
"
);
if
(
matches
)
{
$tr
.
find
(
"
a
"
).
replaceWith
(
highlighter
(
$tr
.
find
(
"
a
"
),
filePath
,
matches
).
attr
(
"
href
"
,
blobItemUrl
));
}
else
{
$tr
.
find
(
"
a
"
).
attr
(
"
href
"
,
blobItemUrl
);
$tr
.
find
(
"
.str-truncated
"
).
text
(
filePath
);
}
return
$tr
;
}
selectRow
(
type
)
{
var
next
,
rows
,
selectedRow
;
rows
=
this
.
element
.
find
(
"
.files-slider tr.tree-item
"
);
selectedRow
=
this
.
element
.
find
(
"
.files-slider tr.tree-item.selected
"
);
if
(
rows
&&
rows
.
length
>
0
)
{
if
(
selectedRow
&&
selectedRow
.
length
>
0
)
{
if
(
type
===
"
UP
"
)
{
next
=
selectedRow
.
prev
();
}
else
if
(
type
===
"
DOWN
"
)
{
next
=
selectedRow
.
next
();
}
if
(
next
.
length
>
0
)
{
selectedRow
.
removeClass
(
"
selected
"
);
selectedRow
=
next
;
}
return
selectedRow
.
addClass
(
"
selected
"
).
focus
();
}
else
{
selectedRow
=
rows
.
eq
(
0
);
}
};
ProjectFindFile
.
prototype
.
selectRowUp
=
function
()
{
return
this
.
selectRow
(
"
UP
"
);
};
return
selectedRow
.
addClass
(
"
selected
"
).
focus
();
}
}
ProjectFindFile
.
prototype
.
selectRowDown
=
function
()
{
return
this
.
selectRow
(
"
DOWN
"
);
};
selectRowUp
()
{
return
this
.
selectRow
(
"
UP
"
);
}
ProjectFindFile
.
prototype
.
goToTree
=
functio
n
()
{
return
location
.
href
=
this
.
options
.
treeUrl
;
};
selectRowDow
n
()
{
return
this
.
selectRow
(
"
DOWN
"
)
;
}
ProjectFindFile
.
prototype
.
goToBlob
=
function
()
{
var
$link
=
this
.
element
.
find
(
"
.tree-item.selected .tree-item-file-name a
"
);
goToTree
()
{
return
location
.
href
=
this
.
options
.
treeUrl
;
}
if
(
$link
.
length
)
{
$link
.
get
(
0
).
click
();
}
};
goToBlob
()
{
var
$link
=
this
.
element
.
find
(
"
.tree-item.selected .tree-item-file-name a
"
);
return
ProjectFindFile
;
})();
}).
call
(
window
);
if
(
$link
.
length
)
{
$link
.
get
(
0
).
click
();
}
}
}
app/assets/javascripts/render_gfm.js
View file @
aa90e8ea
import
renderMath
from
'
./render_math
'
;
import
renderMermaid
from
'
./render_mermaid
'
;
import
syntaxHighlight
from
'
./syntax_highlight
'
;
// Render Gitlab flavoured Markdown
//
// Delegates to syntax highlight and render math & mermaid diagrams.
//
$
.
fn
.
renderGFM
=
function
renderGFM
()
{
this
.
find
(
'
.js-syntax-highlight
'
).
syntaxHighlight
(
);
syntaxHighlight
(
this
.
find
(
'
.js-syntax-highlight
'
)
);
renderMath
(
this
.
find
(
'
.js-render-math
'
));
renderMermaid
(
this
.
find
(
'
.js-render-mermaid
'
));
return
this
;
...
...
app/assets/javascripts/repo/components/repo_preview.vue
View file @
aa90e8ea
<
script
>
/* global LineHighlighter */
import
{
mapGetters
}
from
'
vuex
'
;
import
syntaxHighlight
from
'
../../syntax_highlight
'
;
export
default
{
computed
:
{
...
...
@@ -13,7 +14,7 @@ export default {
},
methods
:
{
highlightFile
()
{
$
(
this
.
$el
).
find
(
'
.file-content
'
).
syntaxHighlight
(
);
syntaxHighlight
(
$
(
this
.
$el
).
find
(
'
.file-content
'
)
);
},
},
mounted
()
{
...
...
app/assets/javascripts/search.js
View file @
aa90e8ea
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, max-len */
import
Flash
from
'
./flash
'
;
import
Api
from
'
./api
'
;
(
function
()
{
this
.
Search
=
(
function
()
{
function
Search
()
{
var
$groupDropdown
,
$projectDropdown
;
$groupDropdown
=
$
(
'
.js-search-group-dropdown
'
);
$projectDropdown
=
$
(
'
.js-search-project-dropdown
'
);
this
.
groupId
=
$groupDropdown
.
data
(
'
group-id
'
);
this
.
eventListeners
();
$groupDropdown
.
glDropdown
({
selectable
:
true
,
filterable
:
true
,
fieldName
:
'
group_id
'
,
search
:
{
fields
:
[
'
full_name
'
]
},
data
:
function
(
term
,
callback
)
{
return
Api
.
groups
(
term
,
{},
function
(
data
)
{
export
default
class
Search
{
constructor
()
{
const
$groupDropdown
=
$
(
'
.js-search-group-dropdown
'
);
const
$projectDropdown
=
$
(
'
.js-search-project-dropdown
'
);
this
.
searchInput
=
'
.js-search-input
'
;
this
.
searchClear
=
'
.js-search-clear
'
;
this
.
groupId
=
$groupDropdown
.
data
(
'
group-id
'
);
this
.
eventListeners
();
$groupDropdown
.
glDropdown
({
selectable
:
true
,
filterable
:
true
,
fieldName
:
'
group_id
'
,
search
:
{
fields
:
[
'
full_name
'
],
},
data
(
term
,
callback
)
{
return
Api
.
groups
(
term
,
{},
(
data
)
=>
{
data
.
unshift
({
full_name
:
'
Any
'
,
});
data
.
splice
(
1
,
0
,
'
divider
'
);
return
callback
(
data
);
});
},
id
(
obj
)
{
return
obj
.
id
;
},
text
(
obj
)
{
return
obj
.
full_name
;
},
toggleLabel
(
obj
)
{
return
`
${(
$groupDropdown
.
data
(
'
default-label
'
))}
${
obj
.
full_name
}
`
;
},
clicked
:
()
=>
Search
.
submitSearch
(),
});
$projectDropdown
.
glDropdown
({
selectable
:
true
,
filterable
:
true
,
fieldName
:
'
project_id
'
,
search
:
{
fields
:
[
'
name
'
],
},
data
:
(
term
,
callback
)
=>
{
this
.
getProjectsData
(
term
)
.
then
((
data
)
=>
{
data
.
unshift
({
full_name
:
'
Any
'
name_with_namespace
:
'
Any
'
,
});
data
.
splice
(
1
,
0
,
'
divider
'
);
return
callback
(
data
);
});
},
id
:
function
(
obj
)
{
return
obj
.
id
;
},
text
:
function
(
obj
)
{
return
obj
.
full_name
;
},
toggleLabel
:
function
(
obj
)
{
return
(
$groupDropdown
.
data
(
'
default-label
'
))
+
"
"
+
obj
.
full_name
;
},
clicked
:
(
function
(
_this
)
{
return
function
()
{
return
_this
.
submitSearch
();
};
})(
this
)
});
$projectDropdown
.
glDropdown
({
selectable
:
true
,
filterable
:
true
,
fieldName
:
'
project_id
'
,
search
:
{
fields
:
[
'
name
'
]
},
data
:
(
term
,
callback
)
=>
{
this
.
getProjectsData
(
term
)
.
then
((
data
)
=>
{
data
.
unshift
({
name_with_namespace
:
'
Any
'
});
data
.
splice
(
1
,
0
,
'
divider
'
);
return
data
;
})
.
then
(
data
=>
callback
(
data
))
.
catch
(()
=>
new
Flash
(
'
Error fetching projects
'
));
},
id
:
function
(
obj
)
{
return
obj
.
id
;
},
text
:
function
(
obj
)
{
return
obj
.
name_with_namespace
;
},
toggleLabel
:
function
(
obj
)
{
return
(
$projectDropdown
.
data
(
'
default-label
'
))
+
"
"
+
obj
.
name_with_namespace
;
},
clicked
:
(
function
(
_this
)
{
return
function
()
{
return
_this
.
submitSearch
();
};
})(
this
)
});
}
return
data
;
})
.
then
(
data
=>
callback
(
data
))
.
catch
(()
=>
new
Flash
(
'
Error fetching projects
'
));
},
id
(
obj
)
{
return
obj
.
id
;
},
text
(
obj
)
{
return
obj
.
name_with_namespace
;
},
toggleLabel
(
obj
)
{
return
`
${(
$projectDropdown
.
data
(
'
default-label
'
))}
${
obj
.
name_with_namespace
}
`
;
},
clicked
:
()
=>
Search
.
submitSearch
(),
});
}
Search
.
prototype
.
eventListeners
=
function
()
{
$
(
document
).
off
(
'
keyup
'
,
'
.js-search-input
'
).
on
(
'
keyup
'
,
'
.js-search-input
'
,
this
.
searchKeyUp
);
return
$
(
document
).
off
(
'
click
'
,
'
.js-search-clear
'
).
on
(
'
click
'
,
'
.js-search-clear
'
,
this
.
clearSearchField
);
};
eventListeners
()
{
$
(
document
)
.
off
(
'
keyup
'
,
this
.
searchInput
)
.
on
(
'
keyup
'
,
this
.
searchInput
,
this
.
searchKeyUp
);
$
(
document
)
.
off
(
'
click
'
,
this
.
searchClear
)
.
on
(
'
click
'
,
this
.
searchClear
,
this
.
clearSearchField
.
bind
(
this
));
}
Search
.
prototype
.
submitSearch
=
function
()
{
return
$
(
'
.js-search-form
'
).
submit
();
};
static
submitSearch
()
{
return
$
(
'
.js-search-form
'
).
submit
();
}
Search
.
prototype
.
searchKeyUp
=
function
()
{
var
$input
;
$input
=
$
(
this
);
if
(
$input
.
val
()
===
''
)
{
return
$
(
'
.js-search-clear
'
).
addClass
(
'
hidden
'
);
}
else
{
return
$
(
'
.js-search-clear
'
).
removeClass
(
'
hidden
'
);
}
};
Search
.
prototype
.
clearSearchField
=
function
()
{
return
$
(
'
.js-search-input
'
).
val
(
''
).
trigger
(
'
keyup
'
).
focus
();
};
searchKeyUp
()
{
const
$input
=
$
(
this
);
if
(
$input
.
val
()
===
''
)
{
$
(
'
.js-search-clear
'
).
addClass
(
'
hidden
'
);
}
else
{
$
(
'
.js-search-clear
'
).
removeClass
(
'
hidden
'
);
}
}
Search
.
prototype
.
getProjectsData
=
function
(
term
)
{
return
new
Promise
((
resolve
)
=>
{
if
(
this
.
groupId
)
{
Api
.
groupProjects
(
this
.
groupId
,
term
,
resolve
);
}
else
{
Api
.
projects
(
term
,
{
order_by
:
'
id
'
,
},
resolve
);
}
});
};
clearSearchField
()
{
return
$
(
this
.
searchInput
).
val
(
''
).
trigger
(
'
keyup
'
).
focus
();
}
return
Search
;
})();
}).
call
(
window
);
getProjectsData
(
term
)
{
return
new
Promise
((
resolve
)
=>
{
if
(
this
.
groupId
)
{
Api
.
groupProjects
(
this
.
groupId
,
term
,
resolve
);
}
else
{
Api
.
projects
(
term
,
{
order_by
:
'
id
'
,
},
resolve
);
}
});
}
}
app/assets/javascripts/search_autocomplete.js
View file @
aa90e8ea
...
...
@@ -8,448 +8,445 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
* When the user clicks `x` button it cleans the input and closes the dropdown.
*/
((
global
)
=>
{
const
KEYCODE
=
{
ESCAPE
:
27
,
BACKSPACE
:
8
,
ENTER
:
13
,
UP
:
38
,
DOWN
:
40
,
};
class
SearchAutocomplete
{
constructor
({
wrap
,
optsEl
,
autocompletePath
,
projectId
,
projectRef
}
=
{})
{
this
.
bindEventContext
();
this
.
wrap
=
wrap
||
$
(
'
.search
'
);
this
.
optsEl
=
optsEl
||
this
.
wrap
.
find
(
'
.search-autocomplete-opts
'
);
this
.
autocompletePath
=
autocompletePath
||
this
.
optsEl
.
data
(
'
autocomplete-path
'
);
this
.
projectId
=
projectId
||
(
this
.
optsEl
.
data
(
'
autocomplete-project-id
'
)
||
''
);
this
.
projectRef
=
projectRef
||
(
this
.
optsEl
.
data
(
'
autocomplete-project-ref
'
)
||
''
);
this
.
dropdown
=
this
.
wrap
.
find
(
'
.dropdown
'
);
this
.
dropdownToggle
=
this
.
wrap
.
find
(
'
.js-dropdown-search-toggle
'
);
this
.
dropdownContent
=
this
.
dropdown
.
find
(
'
.dropdown-content
'
);
this
.
locationBadgeEl
=
this
.
getElement
(
'
.location-badge
'
);
this
.
scopeInputEl
=
this
.
getElement
(
'
#scope
'
);
this
.
searchInput
=
this
.
getElement
(
'
.search-input
'
);
this
.
projectInputEl
=
this
.
getElement
(
'
#search_project_id
'
);
this
.
groupInputEl
=
this
.
getElement
(
'
#group_id
'
);
this
.
searchCodeInputEl
=
this
.
getElement
(
'
#search_code
'
);
this
.
repositoryInputEl
=
this
.
getElement
(
'
#repository_ref
'
);
this
.
clearInput
=
this
.
getElement
(
'
.js-clear-input
'
);
this
.
saveOriginalState
();
// Only when user is logged in
if
(
gon
.
current_user_id
)
{
this
.
createAutocomplete
();
}
const
KEYCODE
=
{
ESCAPE
:
27
,
BACKSPACE
:
8
,
ENTER
:
13
,
UP
:
38
,
DOWN
:
40
,
};
function
setSearchOptions
()
{
var
$projectOptionsDataEl
=
$
(
'
.js-search-project-options
'
);
var
$groupOptionsDataEl
=
$
(
'
.js-search-group-options
'
);
var
$dashboardOptionsDataEl
=
$
(
'
.js-search-dashboard-options
'
);
if
(
$projectOptionsDataEl
.
length
)
{
gl
.
projectOptions
=
gl
.
projectOptions
||
{};
var
projectPath
=
$projectOptionsDataEl
.
data
(
'
project-path
'
);
gl
.
projectOptions
[
projectPath
]
=
{
name
:
$projectOptionsDataEl
.
data
(
'
name
'
),
issuesPath
:
$projectOptionsDataEl
.
data
(
'
issues-path
'
),
issuesDisabled
:
$projectOptionsDataEl
.
data
(
'
issues-disabled
'
),
mrPath
:
$projectOptionsDataEl
.
data
(
'
mr-path
'
),
};
}
this
.
searchInput
.
addClass
(
'
disabled
'
);
this
.
saveTextLength
();
this
.
bindEvents
();
this
.
dropdownToggle
.
dropdown
();
}
if
(
$groupOptionsDataEl
.
length
)
{
gl
.
groupOptions
=
gl
.
groupOptions
||
{};
// Finds an element inside wrapper element
bindEventContext
()
{
this
.
onSearchInputBlur
=
this
.
onSearchInputBlur
.
bind
(
this
);
this
.
onClearInputClick
=
this
.
onClearInputClick
.
bind
(
this
);
this
.
onSearchInputFocus
=
this
.
onSearchInputFocus
.
bind
(
this
);
this
.
onSearchInputKeyUp
=
this
.
onSearchInputKeyUp
.
bind
(
this
);
this
.
onSearchInputKeyDown
=
this
.
onSearchInputKeyDown
.
bind
(
this
);
}
getElement
(
selector
)
{
return
this
.
wrap
.
find
(
selector
);
}
var
groupPath
=
$groupOptionsDataEl
.
data
(
'
group-path
'
);
saveOriginalState
()
{
return
this
.
originalState
=
this
.
serializeState
();
}
gl
.
groupOptions
[
groupPath
]
=
{
name
:
$groupOptionsDataEl
.
data
(
'
name
'
),
issuesPath
:
$groupOptionsDataEl
.
data
(
'
issues-path
'
),
mrPath
:
$groupOptionsDataEl
.
data
(
'
mr-path
'
),
};
}
saveTextLength
()
{
return
this
.
lastTextLength
=
this
.
searchInput
.
val
().
length
;
if
(
$dashboardOptionsDataEl
.
length
)
{
gl
.
dashboardOptions
=
{
issuesPath
:
$dashboardOptionsDataEl
.
data
(
'
issues-path
'
),
mrPath
:
$dashboardOptionsDataEl
.
data
(
'
mr-path
'
),
};
}
}
export
default
class
SearchAutocomplete
{
constructor
({
wrap
,
optsEl
,
autocompletePath
,
projectId
,
projectRef
}
=
{})
{
setSearchOptions
();
this
.
bindEventContext
();
this
.
wrap
=
wrap
||
$
(
'
.search
'
);
this
.
optsEl
=
optsEl
||
this
.
wrap
.
find
(
'
.search-autocomplete-opts
'
);
this
.
autocompletePath
=
autocompletePath
||
this
.
optsEl
.
data
(
'
autocomplete-path
'
);
this
.
projectId
=
projectId
||
(
this
.
optsEl
.
data
(
'
autocomplete-project-id
'
)
||
''
);
this
.
projectRef
=
projectRef
||
(
this
.
optsEl
.
data
(
'
autocomplete-project-ref
'
)
||
''
);
this
.
dropdown
=
this
.
wrap
.
find
(
'
.dropdown
'
);
this
.
dropdownToggle
=
this
.
wrap
.
find
(
'
.js-dropdown-search-toggle
'
);
this
.
dropdownContent
=
this
.
dropdown
.
find
(
'
.dropdown-content
'
);
this
.
locationBadgeEl
=
this
.
getElement
(
'
.location-badge
'
);
this
.
scopeInputEl
=
this
.
getElement
(
'
#scope
'
);
this
.
searchInput
=
this
.
getElement
(
'
.search-input
'
);
this
.
projectInputEl
=
this
.
getElement
(
'
#search_project_id
'
);
this
.
groupInputEl
=
this
.
getElement
(
'
#group_id
'
);
this
.
searchCodeInputEl
=
this
.
getElement
(
'
#search_code
'
);
this
.
repositoryInputEl
=
this
.
getElement
(
'
#repository_ref
'
);
this
.
clearInput
=
this
.
getElement
(
'
.js-clear-input
'
);
this
.
saveOriginalState
();
// Only when user is logged in
if
(
gon
.
current_user_id
)
{
this
.
createAutocomplete
();
}
createAutocomplete
()
{
return
this
.
searchInput
.
glDropdown
({
filterInputBlur
:
false
,
filterable
:
true
,
filterRemote
:
true
,
highlight
:
true
,
enterCallback
:
false
,
filterInput
:
'
input#search
'
,
search
:
{
fields
:
[
'
text
'
],
},
id
:
this
.
getSearchText
,
data
:
this
.
getData
.
bind
(
this
),
selectable
:
true
,
clicked
:
this
.
onClick
.
bind
(
this
),
});
this
.
searchInput
.
addClass
(
'
disabled
'
);
this
.
saveTextLength
();
this
.
bindEvents
();
this
.
dropdownToggle
.
dropdown
();
}
// Finds an element inside wrapper element
bindEventContext
()
{
this
.
onSearchInputBlur
=
this
.
onSearchInputBlur
.
bind
(
this
);
this
.
onClearInputClick
=
this
.
onClearInputClick
.
bind
(
this
);
this
.
onSearchInputFocus
=
this
.
onSearchInputFocus
.
bind
(
this
);
this
.
onSearchInputKeyUp
=
this
.
onSearchInputKeyUp
.
bind
(
this
);
this
.
onSearchInputKeyDown
=
this
.
onSearchInputKeyDown
.
bind
(
this
);
}
getElement
(
selector
)
{
return
this
.
wrap
.
find
(
selector
);
}
saveOriginalState
()
{
return
this
.
originalState
=
this
.
serializeState
();
}
saveTextLength
()
{
return
this
.
lastTextLength
=
this
.
searchInput
.
val
().
length
;
}
createAutocomplete
()
{
return
this
.
searchInput
.
glDropdown
({
filterInputBlur
:
false
,
filterable
:
true
,
filterRemote
:
true
,
highlight
:
true
,
enterCallback
:
false
,
filterInput
:
'
input#search
'
,
search
:
{
fields
:
[
'
text
'
],
},
id
:
this
.
getSearchText
,
data
:
this
.
getData
.
bind
(
this
),
selectable
:
true
,
clicked
:
this
.
onClick
.
bind
(
this
),
});
}
getSearchText
(
selectedObject
,
el
)
{
return
selectedObject
.
id
?
selectedObject
.
text
:
''
;
}
getData
(
term
,
callback
)
{
if
(
!
term
)
{
const
contents
=
this
.
getCategoryContents
();
if
(
contents
)
{
this
.
searchInput
.
data
(
'
glDropdown
'
).
filter
.
options
.
callback
(
contents
);
this
.
enableAutocomplete
();
}
return
;
}
getSearchText
(
selectedObject
,
el
)
{
return
selectedObject
.
id
?
selectedObject
.
text
:
''
;
// Prevent multiple ajax calls
if
(
this
.
loadingSuggestions
)
{
return
;
}
getData
(
term
,
callback
)
{
if
(
!
term
)
{
const
contents
=
this
.
getCategoryContents
();
if
(
contents
)
{
this
.
searchInput
.
data
(
'
glDropdown
'
).
filter
.
options
.
callback
(
contents
);
this
.
enableAutocomplete
();
}
return
;
}
this
.
loadingSuggestions
=
true
;
// Prevent multiple ajax calls
if
(
this
.
loadingSuggestions
)
{
return
$
.
get
(
this
.
autocompletePath
,
{
project_id
:
this
.
projectId
,
project_ref
:
this
.
projectRef
,
term
:
term
,
},
(
response
)
=>
{
var
firstCategory
,
i
,
lastCategory
,
len
,
suggestion
;
// Hide dropdown menu if no suggestions returns
if
(
!
response
.
length
)
{
this
.
disableAutocomplete
();
return
;
}
this
.
loadingSuggestions
=
true
;
return
$
.
get
(
this
.
autocompletePath
,
{
project_id
:
this
.
projectId
,
project_ref
:
this
.
projectRef
,
term
:
term
,
},
(
response
)
=>
{
var
firstCategory
,
i
,
lastCategory
,
len
,
suggestion
;
// Hide dropdown menu if no suggestions returns
if
(
!
response
.
length
)
{
this
.
disableAutocomplete
();
return
;
}
const
data
=
[];
// List results
firstCategory
=
true
;
for
(
i
=
0
,
len
=
response
.
length
;
i
<
len
;
i
+=
1
)
{
suggestion
=
response
[
i
];
// Add group header before list each group
if
(
lastCategory
!==
suggestion
.
category
)
{
if
(
!
firstCategory
)
{
data
.
push
(
'
separator
'
);
}
if
(
firstCategory
)
{
firstCategory
=
false
;
}
data
.
push
({
header
:
suggestion
.
category
,
});
lastCategory
=
suggestion
.
category
;
const
data
=
[];
// List results
firstCategory
=
true
;
for
(
i
=
0
,
len
=
response
.
length
;
i
<
len
;
i
+=
1
)
{
suggestion
=
response
[
i
];
// Add group header before list each group
if
(
lastCategory
!==
suggestion
.
category
)
{
if
(
!
firstCategory
)
{
data
.
push
(
'
separator
'
);
}
if
(
firstCategory
)
{
firstCategory
=
false
;
}
data
.
push
({
id
:
(
suggestion
.
category
.
toLowerCase
())
+
"
-
"
+
suggestion
.
id
,
category
:
suggestion
.
category
,
text
:
suggestion
.
label
,
url
:
suggestion
.
url
,
});
}
// Add option to proceed with the search
if
(
data
.
length
)
{
data
.
push
(
'
separator
'
);
data
.
push
({
text
:
"
Result name contains
\"
"
+
term
+
"
\"
"
,
url
:
"
/search?search=
"
+
term
+
"
&project_id=
"
+
(
this
.
projectInputEl
.
val
())
+
"
&group_id=
"
+
(
this
.
groupInputEl
.
val
()),
header
:
suggestion
.
category
,
});
lastCategory
=
suggestion
.
category
;
}
return
callback
(
data
);
})
.
always
(()
=>
{
this
.
loadingSuggestions
=
false
;
});
}
getCategoryContents
()
{
const
userId
=
gon
.
current_user_id
;
const
userName
=
gon
.
current_username
;
const
{
projectOptions
,
groupOptions
,
dashboardOptions
}
=
gl
;
// Get options
let
options
;
if
(
isInGroupsPage
()
&&
groupOptions
)
{
options
=
groupOptions
[
getGroupSlug
()];
}
else
if
(
isInProjectPage
()
&&
projectOptions
)
{
options
=
projectOptions
[
getProjectSlug
()];
}
else
if
(
dashboardOptions
)
{
options
=
dashboardOptions
;
data
.
push
({
id
:
(
suggestion
.
category
.
toLowerCase
())
+
"
-
"
+
suggestion
.
id
,
category
:
suggestion
.
category
,
text
:
suggestion
.
label
,
url
:
suggestion
.
url
,
});
}
const
{
issuesPath
,
mrPath
,
name
,
issuesDisabled
}
=
options
;
const
baseItems
=
[];
if
(
name
)
{
baseItems
.
push
({
header
:
`
${
name
}
`
,
// Add option to proceed with the search
if
(
data
.
length
)
{
data
.
push
(
'
separator
'
);
data
.
push
({
text
:
"
Result name contains
\"
"
+
term
+
"
\"
"
,
url
:
"
/search?search=
"
+
term
+
"
&project_id=
"
+
(
this
.
projectInputEl
.
val
())
+
"
&group_id=
"
+
(
this
.
groupInputEl
.
val
()),
});
}
return
callback
(
data
);
})
.
always
(()
=>
{
this
.
loadingSuggestions
=
false
;
});
}
const
issueItems
=
[
{
text
:
'
Issues assigned to me
'
,
url
:
`
${
issuesPath
}
/?assignee_username=
${
userName
}
`
,
},
{
text
:
"
Issues I've created
"
,
url
:
`
${
issuesPath
}
/?author_username=
${
userName
}
`
,
},
];
const
mergeRequestItems
=
[
{
text
:
'
Merge requests assigned to me
'
,
url
:
`
${
mrPath
}
/?assignee_username=
${
userName
}
`
,
},
{
text
:
"
Merge requests I've created
"
,
url
:
`
${
mrPath
}
/?author_username=
${
userName
}
`
,
},
];
let
items
;
if
(
issuesDisabled
)
{
items
=
baseItems
.
concat
(
mergeRequestItems
);
}
else
{
items
=
baseItems
.
concat
(...
issueItems
,
'
separator
'
,
...
mergeRequestItems
);
}
return
items
;
getCategoryContents
()
{
const
userId
=
gon
.
current_user_id
;
const
userName
=
gon
.
current_username
;
const
{
projectOptions
,
groupOptions
,
dashboardOptions
}
=
gl
;
// Get options
let
options
;
if
(
isInGroupsPage
()
&&
groupOptions
)
{
options
=
groupOptions
[
getGroupSlug
()];
}
else
if
(
isInProjectPage
()
&&
projectOptions
)
{
options
=
projectOptions
[
getProjectSlug
()];
}
else
if
(
dashboardOptions
)
{
options
=
dashboardOptions
;
}
serializeState
()
{
return
{
// Search Criteria
search_project_id
:
this
.
projectInputEl
.
val
(),
group_id
:
this
.
groupInputEl
.
val
(),
search_code
:
this
.
searchCodeInputEl
.
val
(),
repository_ref
:
this
.
repositoryInputEl
.
val
(),
scope
:
this
.
scopeInputEl
.
val
(),
// Location badge
_location
:
this
.
locationBadgeEl
.
text
(),
};
const
{
issuesPath
,
mrPath
,
name
,
issuesDisabled
}
=
options
;
const
baseItems
=
[];
if
(
name
)
{
baseItems
.
push
({
header
:
`
${
name
}
`
,
});
}
bindEvents
()
{
this
.
searchInput
.
on
(
'
keydown
'
,
this
.
onSearchInputKeyDown
);
this
.
searchInput
.
on
(
'
keyup
'
,
this
.
onSearchInputKeyUp
);
this
.
searchInput
.
on
(
'
focus
'
,
this
.
onSearchInputFocus
);
this
.
searchInput
.
on
(
'
blur
'
,
this
.
onSearchInputBlur
);
this
.
clearInput
.
on
(
'
click
'
,
this
.
onClearInputClick
);
this
.
locationBadgeEl
.
on
(
'
click
'
,
()
=>
this
.
searchInput
.
focus
());
const
issueItems
=
[
{
text
:
'
Issues assigned to me
'
,
url
:
`
${
issuesPath
}
/?assignee_username=
${
userName
}
`
,
},
{
text
:
"
Issues I've created
"
,
url
:
`
${
issuesPath
}
/?author_username=
${
userName
}
`
,
},
];
const
mergeRequestItems
=
[
{
text
:
'
Merge requests assigned to me
'
,
url
:
`
${
mrPath
}
/?assignee_username=
${
userName
}
`
,
},
{
text
:
"
Merge requests I've created
"
,
url
:
`
${
mrPath
}
/?author_username=
${
userName
}
`
,
},
];
let
items
;
if
(
issuesDisabled
)
{
items
=
baseItems
.
concat
(
mergeRequestItems
);
}
else
{
items
=
baseItems
.
concat
(...
issueItems
,
'
separator
'
,
...
mergeRequestItems
);
}
return
items
;
}
enableAutocomplete
()
{
// No need to enable anything if user is not logged in
if
(
!
gon
.
current_user_id
)
{
return
;
}
serializeState
()
{
return
{
// Search Criteria
search_project_id
:
this
.
projectInputEl
.
val
(),
group_id
:
this
.
groupInputEl
.
val
(),
search_code
:
this
.
searchCodeInputEl
.
val
(),
repository_ref
:
this
.
repositoryInputEl
.
val
(),
scope
:
this
.
scopeInputEl
.
val
(),
// Location badge
_location
:
this
.
locationBadgeEl
.
text
(),
};
}
// If the dropdown is closed, we'll open it
if
(
!
this
.
dropdown
.
hasClass
(
'
open
'
))
{
this
.
loadingSuggestions
=
false
;
this
.
dropdownToggle
.
dropdown
(
'
toggle
'
);
return
this
.
searchInput
.
removeClass
(
'
disabled
'
);
}
bindEvents
()
{
this
.
searchInput
.
on
(
'
keydown
'
,
this
.
onSearchInputKeyDown
);
this
.
searchInput
.
on
(
'
keyup
'
,
this
.
onSearchInputKeyUp
);
this
.
searchInput
.
on
(
'
focus
'
,
this
.
onSearchInputFocus
);
this
.
searchInput
.
on
(
'
blur
'
,
this
.
onSearchInputBlur
);
this
.
clearInput
.
on
(
'
click
'
,
this
.
onClearInputClick
);
this
.
locationBadgeEl
.
on
(
'
click
'
,
()
=>
this
.
searchInput
.
focus
());
}
enableAutocomplete
()
{
// No need to enable anything if user is not logged in
if
(
!
gon
.
current_user_id
)
{
return
;
}
// Saves last length of the entered text
onSearchInputKeyDown
()
{
return
this
.
saveTextLength
();
// If the dropdown is closed, we'll open it
if
(
!
this
.
dropdown
.
hasClass
(
'
open
'
))
{
this
.
loadingSuggestions
=
false
;
this
.
dropdownToggle
.
dropdown
(
'
toggle
'
);
return
this
.
searchInput
.
removeClass
(
'
disabled
'
);
}
}
onSearchInputKeyUp
(
e
)
{
switch
(
e
.
keyCode
)
{
case
KEYCODE
.
BACKSPACE
:
// when trying to remove the location badge
if
(
this
.
lastTextLength
===
0
&&
this
.
badgePresent
())
{
this
.
removeLocationBadge
();
}
// When removing the last character and no badge is present
if
(
this
.
lastTextLength
===
1
)
{
this
.
disableAutocomplete
();
}
// When removing any character from existin value
if
(
this
.
lastTextLength
>
1
)
{
this
.
enableAutocomplete
();
}
break
;
case
KEYCODE
.
ESCAPE
:
this
.
restoreOriginalState
();
break
;
case
KEYCODE
.
ENTER
:
// Saves last length of the entered text
onSearchInputKeyDown
()
{
return
this
.
saveTextLength
();
}
onSearchInputKeyUp
(
e
)
{
switch
(
e
.
keyCode
)
{
case
KEYCODE
.
BACKSPACE
:
// when trying to remove the location badge
if
(
this
.
lastTextLength
===
0
&&
this
.
badgePresent
())
{
this
.
removeLocationBadge
();
}
// When removing the last character and no badge is present
if
(
this
.
lastTextLength
===
1
)
{
this
.
disableAutocomplete
();
}
// When removing any character from existin value
if
(
this
.
lastTextLength
>
1
)
{
this
.
enableAutocomplete
();
}
break
;
case
KEYCODE
.
ESCAPE
:
this
.
restoreOriginalState
();
break
;
case
KEYCODE
.
ENTER
:
this
.
disableAutocomplete
();
break
;
case
KEYCODE
.
UP
:
case
KEYCODE
.
DOWN
:
return
;
default
:
// Handle the case when deleting the input value other than backspace
// e.g. Pressing ctrl + backspace or ctrl + x
if
(
this
.
searchInput
.
val
()
===
''
)
{
this
.
disableAutocomplete
();
break
;
case
KEYCODE
.
UP
:
case
KEYCODE
.
DOWN
:
return
;
default
:
// Handle the case when deleting the input value other than backspace
// e.g. Pressing ctrl + backspace or ctrl + x
if
(
this
.
searchInput
.
val
()
===
''
)
{
this
.
disableAutocomplete
();
}
else
{
// We should display the menu only when input is not empty
if
(
e
.
keyCode
!==
KEYCODE
.
ENTER
)
{
this
.
enableAutocomplete
();
}
}
else
{
// We should display the menu only when input is not empty
if
(
e
.
keyCode
!==
KEYCODE
.
ENTER
)
{
this
.
enableAutocomplete
();
}
}
this
.
wrap
.
toggleClass
(
'
has-value
'
,
!!
e
.
target
.
value
);
}
}
this
.
wrap
.
toggleClass
(
'
has-value
'
,
!!
e
.
target
.
value
);
}
onSearchInputFocus
()
{
this
.
isFocused
=
true
;
this
.
wrap
.
addClass
(
'
search-active
'
);
if
(
this
.
getValue
()
===
''
)
{
return
this
.
getData
();
}
onSearchInputFocus
()
{
this
.
isFocused
=
true
;
this
.
wrap
.
addClass
(
'
search-active
'
);
if
(
this
.
getValue
()
===
''
)
{
return
this
.
getData
();
}
}
getValue
()
{
return
this
.
searchInput
.
val
();
}
getValue
()
{
return
this
.
searchInput
.
val
();
}
onClearInputClick
(
e
)
{
e
.
preventDefault
();
this
.
wrap
.
toggleClass
(
'
has-value
'
,
!!
e
.
target
.
value
);
return
this
.
searchInput
.
val
(
''
).
focus
();
}
onClearInputClick
(
e
)
{
e
.
preventDefault
();
this
.
wrap
.
toggleClass
(
'
has-value
'
,
!!
e
.
target
.
value
);
return
this
.
searchInput
.
val
(
''
).
focus
();
}
onSearchInputBlur
(
e
)
{
this
.
isFocused
=
false
;
this
.
wrap
.
removeClass
(
'
search-active
'
);
// If input is blank then restore state
if
(
this
.
searchInput
.
val
()
===
''
)
{
return
this
.
restoreOriginalState
();
}
onSearchInputBlur
(
e
)
{
this
.
isFocused
=
false
;
this
.
wrap
.
removeClass
(
'
search-active
'
);
// If input is blank then restore state
if
(
this
.
searchInput
.
val
()
===
''
)
{
return
this
.
restoreOriginalState
();
}
}
addLocationBadge
(
item
)
{
var
badgeText
,
category
,
value
;
category
=
item
.
category
!=
null
?
item
.
category
+
"
:
"
:
''
;
value
=
item
.
value
!=
null
?
item
.
value
:
''
;
badgeText
=
""
+
category
+
value
;
this
.
locationBadgeEl
.
text
(
badgeText
).
show
();
return
this
.
wrap
.
addClass
(
'
has-location-badge
'
);
}
addLocationBadge
(
item
)
{
var
badgeText
,
category
,
value
;
category
=
item
.
category
!=
null
?
item
.
category
+
"
:
"
:
''
;
value
=
item
.
value
!=
null
?
item
.
value
:
''
;
badgeText
=
""
+
category
+
value
;
this
.
locationBadgeEl
.
text
(
badgeText
).
show
();
return
this
.
wrap
.
addClass
(
'
has-location-badge
'
);
}
hasLocationBadge
()
{
return
this
.
wrap
.
is
(
'
.has-location-badge
'
);
}
hasLocationBadge
()
{
return
this
.
wrap
.
is
(
'
.has-location-badge
'
);
}
restoreOriginalState
()
{
var
i
,
input
,
inputs
,
len
;
inputs
=
Object
.
keys
(
this
.
originalState
);
for
(
i
=
0
,
len
=
inputs
.
length
;
i
<
len
;
i
+=
1
)
{
input
=
inputs
[
i
];
this
.
getElement
(
"
#
"
+
input
).
val
(
this
.
originalState
[
input
]);
}
if
(
this
.
originalState
.
_location
===
''
)
{
return
this
.
locationBadgeEl
.
hide
();
}
else
{
return
this
.
addLocationBadge
({
value
:
this
.
originalState
.
_location
,
});
}
restoreOriginalState
()
{
var
i
,
input
,
inputs
,
len
;
inputs
=
Object
.
keys
(
this
.
originalState
);
for
(
i
=
0
,
len
=
inputs
.
length
;
i
<
len
;
i
+=
1
)
{
input
=
inputs
[
i
];
this
.
getElement
(
"
#
"
+
input
).
val
(
this
.
originalState
[
input
]);
}
badgePresent
()
{
return
this
.
locationBadgeEl
.
length
;
if
(
this
.
originalState
.
_location
===
''
)
{
return
this
.
locationBadgeEl
.
hide
();
}
else
{
return
this
.
addLocationBadge
({
value
:
this
.
originalState
.
_location
,
});
}
}
resetSearchState
()
{
var
i
,
input
,
inputs
,
len
,
results
;
inputs
=
Object
.
keys
(
this
.
originalState
);
results
=
[];
for
(
i
=
0
,
len
=
inputs
.
length
;
i
<
len
;
i
+=
1
)
{
input
=
inputs
[
i
];
// _location isnt a input
if
(
input
===
'
_location
'
)
{
break
;
}
results
.
push
(
this
.
getElement
(
"
#
"
+
input
).
val
(
''
));
badgePresent
()
{
return
this
.
locationBadgeEl
.
length
;
}
resetSearchState
()
{
var
i
,
input
,
inputs
,
len
,
results
;
inputs
=
Object
.
keys
(
this
.
originalState
);
results
=
[];
for
(
i
=
0
,
len
=
inputs
.
length
;
i
<
len
;
i
+=
1
)
{
input
=
inputs
[
i
];
// _location isnt a input
if
(
input
===
'
_location
'
)
{
break
;
}
re
turn
results
;
re
sults
.
push
(
this
.
getElement
(
"
#
"
+
input
).
val
(
''
))
;
}
return
results
;
}
removeLocationBadge
()
{
this
.
locationBadgeEl
.
hide
();
this
.
resetSearchState
();
this
.
wrap
.
removeClass
(
'
has-location-badge
'
);
return
this
.
disableAutocomplete
();
}
removeLocationBadge
()
{
this
.
locationBadgeEl
.
hide
();
this
.
resetSearchState
();
this
.
wrap
.
removeClass
(
'
has-location-badge
'
);
return
this
.
disableAutocomplete
();
}
disableAutocomplete
()
{
if
(
!
this
.
searchInput
.
hasClass
(
'
disabled
'
)
&&
this
.
dropdown
.
hasClass
(
'
open
'
))
{
this
.
searchInput
.
addClass
(
'
disabled
'
);
this
.
dropdown
.
removeClass
(
'
open
'
).
trigger
(
'
hidden.bs.dropdown
'
);
this
.
restoreMenu
();
}
disableAutocomplete
()
{
if
(
!
this
.
searchInput
.
hasClass
(
'
disabled
'
)
&&
this
.
dropdown
.
hasClass
(
'
open
'
))
{
this
.
searchInput
.
addClass
(
'
disabled
'
);
this
.
dropdown
.
removeClass
(
'
open
'
).
trigger
(
'
hidden.bs.dropdown
'
);
this
.
restoreMenu
();
}
}
restoreMenu
()
{
var
html
;
html
=
'
<ul><li class="dropdown-menu-empty-item"><a>Loading...</a></li></ul>
'
;
return
this
.
dropdownContent
.
html
(
html
);
}
restoreMenu
()
{
var
html
;
html
=
'
<ul><li class="dropdown-menu-empty-item"><a>Loading...</a></li></ul>
'
;
return
this
.
dropdownContent
.
html
(
html
);
}
onClick
(
item
,
$el
,
e
)
{
if
(
location
.
pathname
.
indexOf
(
item
.
url
)
!==
-
1
)
{
if
(
!
e
.
metaKey
)
e
.
preventDefault
();
if
(
!
this
.
badgePresent
)
{
if
(
item
.
category
===
'
Projects
'
)
{
this
.
projectInputEl
.
val
(
item
.
id
);
this
.
addLocationBadge
({
value
:
'
This project
'
,
});
}
if
(
item
.
category
===
'
Groups
'
)
{
this
.
groupInputEl
.
val
(
item
.
id
);
this
.
addLocationBadge
({
value
:
'
This group
'
,
});
}
onClick
(
item
,
$el
,
e
)
{
if
(
location
.
pathname
.
indexOf
(
item
.
url
)
!==
-
1
)
{
if
(
!
e
.
metaKey
)
e
.
preventDefault
();
if
(
!
this
.
badgePresent
)
{
if
(
item
.
category
===
'
Projects
'
)
{
this
.
projectInputEl
.
val
(
item
.
id
);
this
.
addLocationBadge
({
value
:
'
This project
'
,
});
}
if
(
item
.
category
===
'
Groups
'
)
{
this
.
groupInputEl
.
val
(
item
.
id
);
this
.
addLocationBadge
({
value
:
'
This group
'
,
});
}
$el
.
removeClass
(
'
is-active
'
);
this
.
disableAutocomplete
();
return
this
.
searchInput
.
val
(
''
).
focus
();
}
$el
.
removeClass
(
'
is-active
'
);
this
.
disableAutocomplete
();
return
this
.
searchInput
.
val
(
''
).
focus
();
}
}
global
.
SearchAutocomplete
=
SearchAutocomplete
;
$
(
function
()
{
var
$projectOptionsDataEl
=
$
(
'
.js-search-project-options
'
);
var
$groupOptionsDataEl
=
$
(
'
.js-search-group-options
'
);
var
$dashboardOptionsDataEl
=
$
(
'
.js-search-dashboard-options
'
);
if
(
$projectOptionsDataEl
.
length
)
{
gl
.
projectOptions
=
gl
.
projectOptions
||
{};
var
projectPath
=
$projectOptionsDataEl
.
data
(
'
project-path
'
);
gl
.
projectOptions
[
projectPath
]
=
{
name
:
$projectOptionsDataEl
.
data
(
'
name
'
),
issuesPath
:
$projectOptionsDataEl
.
data
(
'
issues-path
'
),
issuesDisabled
:
$projectOptionsDataEl
.
data
(
'
issues-disabled
'
),
mrPath
:
$projectOptionsDataEl
.
data
(
'
mr-path
'
),
};
}
if
(
$groupOptionsDataEl
.
length
)
{
gl
.
groupOptions
=
gl
.
groupOptions
||
{};
var
groupPath
=
$groupOptionsDataEl
.
data
(
'
group-path
'
);
gl
.
groupOptions
[
groupPath
]
=
{
name
:
$groupOptionsDataEl
.
data
(
'
name
'
),
issuesPath
:
$groupOptionsDataEl
.
data
(
'
issues-path
'
),
mrPath
:
$groupOptionsDataEl
.
data
(
'
mr-path
'
),
};
}
if
(
$dashboardOptionsDataEl
.
length
)
{
gl
.
dashboardOptions
=
{
issuesPath
:
$dashboardOptionsDataEl
.
data
(
'
issues-path
'
),
mrPath
:
$dashboardOptionsDataEl
.
data
(
'
mr-path
'
),
};
}
});
})(
window
.
gl
||
(
window
.
gl
=
{}));
}
app/assets/javascripts/single_file_diff.js
View file @
aa90e8ea
...
...
@@ -2,6 +2,7 @@
import
FilesCommentButton
from
'
./files_comment_button
'
;
import
imageDiffHelper
from
'
./image_diff/helpers/index
'
;
import
syntaxHighlight
from
'
./syntax_highlight
'
;
const
WRAPPER
=
'
<div class="diff-content"></div>
'
;
const
LOADING_HTML
=
'
<i class="fa fa-spinner fa-spin"></i>
'
;
...
...
@@ -64,7 +65,7 @@ export default class SingleFileDiff {
_this
.
loadingContent
.
hide
();
if
(
data
.
html
)
{
_this
.
content
=
$
(
data
.
html
);
_this
.
content
.
syntaxHighlight
(
);
syntaxHighlight
(
_this
.
content
);
}
else
{
_this
.
hasError
=
true
;
_this
.
content
=
$
(
ERROR_HTML
);
...
...
app/assets/javascripts/syntax_highlight.js
View file @
aa90e8ea
...
...
@@ -10,17 +10,15 @@
// <div class="js-syntax-highlight"></div>
//
$
.
fn
.
syntaxHighlight
=
function
()
{
var
$children
;
if
(
$
(
this
).
hasClass
(
'
js-syntax-highlight
'
))
{
export
default
function
syntaxHighlight
(
el
)
{
if
(
$
(
el
).
hasClass
(
'
js-syntax-highlight
'
))
{
// Given the element itself, apply highlighting
return
$
(
this
).
addClass
(
gon
.
user_color_scheme
);
return
$
(
el
).
addClass
(
gon
.
user_color_scheme
);
}
else
{
// Given a parent element, recurse to any of its applicable children
$children
=
$
(
this
).
find
(
'
.js-syntax-highlight
'
);
const
$children
=
$
(
el
).
find
(
'
.js-syntax-highlight
'
);
if
(
$children
.
length
)
{
return
$children
.
syntaxHighlight
(
);
return
syntaxHighlight
(
$children
);
}
}
}
;
}
spec/javascripts/search_autocomplete_spec.js
View file @
aa90e8ea
/* eslint-disable space-before-function-paren, max-len, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, comma-dangle, object-shorthand, prefer-template, quotes, new-parens, vars-on-top, new-cap, max-len */
import
'
~/gl_dropdown
'
;
import
'
~/search_autocomplete
'
;
import
SearchAutocomplete
from
'
~/search_autocomplete
'
;
import
'
~/lib/utils/common_utils
'
;
import
*
as
urlUtils
from
'
~/lib/utils/url_utility
'
;
...
...
@@ -128,7 +128,7 @@ import * as urlUtils from '~/lib/utils/url_utility';
window
.
gon
.
current_user_id
=
userId
;
window
.
gon
.
current_username
=
userName
;
return
widget
=
new
gl
.
SearchAutocomplete
;
return
widget
=
new
SearchAutocomplete
()
;
});
afterEach
(
function
()
{
...
...
spec/javascripts/syntax_highlight_spec.js
View file @
aa90e8ea
/* eslint-disable space-before-function-paren, no-var, no-return-assign, quotes */
import
'
~/syntax_highlight
'
;
import
syntaxHighlight
from
'
~/syntax_highlight
'
;
(
function
()
{
describe
(
'
Syntax Highlighter
'
,
function
()
{
var
stubUserColorScheme
;
stubUserColorScheme
=
function
(
value
)
{
if
(
window
.
gon
==
null
)
{
window
.
gon
=
{};
}
return
window
.
gon
.
user_color_scheme
=
value
;
};
describe
(
'
on a js-syntax-highlight element
'
,
function
()
{
beforeEach
(
function
()
{
return
setFixtures
(
'
<div class="js-syntax-highlight"></div>
'
);
});
return
it
(
'
applies syntax highlighting
'
,
function
()
{
stubUserColorScheme
(
'
monokai
'
);
$
(
'
.js-syntax-highlight
'
).
syntaxHighlight
();
return
expect
(
$
(
'
.js-syntax-highlight
'
)).
toHaveClass
(
'
monokai
'
);
});
describe
(
'
Syntax Highlighter
'
,
function
()
{
var
stubUserColorScheme
;
stubUserColorScheme
=
function
(
value
)
{
if
(
window
.
gon
==
null
)
{
window
.
gon
=
{};
}
return
window
.
gon
.
user_color_scheme
=
value
;
};
describe
(
'
on a js-syntax-highlight element
'
,
function
()
{
beforeEach
(
function
()
{
return
setFixtures
(
'
<div class="js-syntax-highlight"></div>
'
);
});
return
describe
(
'
on a parent element
'
,
function
()
{
beforeEach
(
function
()
{
return
setFixtures
(
"
<div class=
\"
parent
\"
>
\n
<div class=
\"
js-syntax-highlight
\"
></div>
\n
<div class=
\"
foo
\"
></div>
\n
<div class=
\"
js-syntax-highlight
\"
></div>
\n
</div>
"
);
});
it
(
'
applies highlighting to all applicable children
'
,
function
()
{
stubUserColorScheme
(
'
monokai
'
);
$
(
'
.parent
'
).
syntaxHighlight
();
expect
(
$
(
'
.parent, .foo
'
)).
not
.
toHaveClass
(
'
monokai
'
);
return
expect
(
$
(
'
.monokai
'
).
length
).
toBe
(
2
);
});
return
it
(
'
prevents an infinite loop when no matches exist
'
,
function
()
{
var
highlight
;
setFixtures
(
'
<div></div>
'
);
highlight
=
function
()
{
return
$
(
'
div
'
).
syntaxHighlight
();
};
return
expect
(
highlight
).
not
.
toThrow
();
});
return
it
(
'
applies syntax highlighting
'
,
function
()
{
stubUserColorScheme
(
'
monokai
'
);
syntaxHighlight
(
$
(
'
.js-syntax-highlight
'
));
return
expect
(
$
(
'
.js-syntax-highlight
'
)).
toHaveClass
(
'
monokai
'
);
});
});
}).
call
(
window
);
return
describe
(
'
on a parent element
'
,
function
()
{
beforeEach
(
function
()
{
return
setFixtures
(
"
<div class=
\"
parent
\"
>
\n
<div class=
\"
js-syntax-highlight
\"
></div>
\n
<div class=
\"
foo
\"
></div>
\n
<div class=
\"
js-syntax-highlight
\"
></div>
\n
</div>
"
);
});
it
(
'
applies highlighting to all applicable children
'
,
function
()
{
stubUserColorScheme
(
'
monokai
'
);
syntaxHighlight
(
$
(
'
.parent
'
));
expect
(
$
(
'
.parent, .foo
'
)).
not
.
toHaveClass
(
'
monokai
'
);
return
expect
(
$
(
'
.monokai
'
).
length
).
toBe
(
2
);
});
return
it
(
'
prevents an infinite loop when no matches exist
'
,
function
()
{
var
highlight
;
setFixtures
(
'
<div></div>
'
);
highlight
=
function
()
{
return
syntaxHighlight
(
$
(
'
div
'
));
};
return
expect
(
highlight
).
not
.
toThrow
();
});
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment