-
+
+
@if(pathList.nonEmpty){
}
-
+
@branchPullRequest.map{ case (pullRequest, issue) =>
#@pullRequest.issueId
}.getOrElse{
diff --git a/src/main/twirl/gitbucket/core/repo/find.scala.html b/src/main/twirl/gitbucket/core/repo/find.scala.html
new file mode 100644
index 0000000..42ad058
--- /dev/null
+++ b/src/main/twirl/gitbucket/core/repo/find.scala.html
@@ -0,0 +1,121 @@
+@(branch: String,
+ treeId: String,
+ repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
+ groupNames: List[String]
+ )(implicit context: gitbucket.core.controller.Context)
+@import context._
+@import gitbucket.core.view.helpers._
+@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
+ @html.menu("code", repository, Some(branch), false, groupNames.isEmpty){
+
+
+
+
+
+ You've activated the file finder
+ by pressing t
.
+ Start typing to filter the file list. Use ↑
and
+ ↓
to navigate,
+ enter
to view files.
+
+
+
+
+ |
+  |
+
+
+ |
+
+
+
+ No matching files |
---|
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/webapp/assets/common/css/gitbucket.css b/src/main/webapp/assets/common/css/gitbucket.css
index 925897b..d94df8f 100644
--- a/src/main/webapp/assets/common/css/gitbucket.css
+++ b/src/main/webapp/assets/common/css/gitbucket.css
@@ -1405,3 +1405,36 @@
h6 a.markdown-anchor-link {
top: 6px;
}
+
+/****************************************************************************/
+/* File finder */
+/****************************************************************************/
+#tree-finder-field{
+ border: none;
+ box-shadow: none;
+ padding: 0;
+ margin: 0;
+ vertical-align: baseline;
+ font-size: 100%;
+ height: inherit;
+ width: 780px;
+}
+.find-input{
+ font-size: 18px;
+ margin-bottom: 20px;
+}
+#tree-finder-results td{
+ padding:7px 6px;
+}
+#tree-finder-results td.icon{
+ width:16px; padding: 7px 2px 7px 6px;
+}
+#tree-finder-results .tree-browser-result .icon-chevron-right{
+ visibility: hidden;
+}
+#tree-finder-results .tree-browser-result.navigation-focus .icon-chevron-right{
+ visibility: visible;
+}
+#tree-finder-results .navigation-focus td{
+ background: #fff;
+}
diff --git a/src/main/webapp/assets/common/js/gitbucket.js b/src/main/webapp/assets/common/js/gitbucket.js
index e10b2fb..a6d8ae2 100644
--- a/src/main/webapp/assets/common/js/gitbucket.js
+++ b/src/main/webapp/assets/common/js/gitbucket.js
@@ -12,6 +12,12 @@
$('a[data-toggle=tooltip]').tooltip();
$('li[data-toggle=tooltip]').tooltip();
+ // activate hotkey
+ $('a[data-hotkey]').each(function(){
+ var target = this;
+ $(document).bind('keydown', $(target).data('hotkey'), function(){ target.click(); });
+ });
+
// anchor icon for markdown
$('.markdown-head').mouseenter(function(e){
$(e.target).children('a.markdown-anchor-link').show();
@@ -334,3 +340,156 @@
return ret;
}
});
+
+/**
+ * scroll target into view ( on bottom edge, or on top edge)
+ */
+function scrollIntoView(target){
+ target = $(target);
+ var $window = $(window);
+ var docViewTop = $window.scrollTop();
+ var docViewBottom = docViewTop + $window.height();
+
+ var elemTop = target.offset().top;
+ var elemBottom = elemTop + target.height();
+
+ if(elemBottom > docViewBottom){
+ $('html, body').scrollTop(elemBottom - $window.height());
+ }else if(elemTop < docViewTop){
+ $('html, body').scrollTop(elemTop);
+ }
+}
+
+/**
+ * escape html
+ */
+function escapeHtml(text){
+ return text.replace(/&/g,'&').replace(//g,'>');
+}
+
+/**
+ * calculate string ranking for path.
+ * Original ported from:
+ * http://joshaven.com/string_score
+ * https://github.com/joshaven/string_score
+ *
+ * Copyright (C) 2009-2011 Joshaven Potter
+ * Special thanks to all of the contributors listed here https://github.com/joshaven/string_score
+ * MIT license: http://www.opensource.org/licenses/mit-license.php
+ */
+function string_score(string, word) {
+ 'use strict';
+ var zero = {score:0,matchingPositions:[]};
+
+ // If the string is equal to the word, perfect match.
+ if (string === word || word === "") { return {score:1, matchingPositions:[]}; }
+
+ var lString = string.toUpperCase(),
+ strLength = string.length,
+ lWord = word.toUpperCase(),
+ wordLength = word.length;
+
+ return calc(zero, 0, 0, 0, 0, []);
+ function calc(score, startAt, skip, runningScore, i, matchingPositions){
+ if( i < wordLength) {
+ var charScore = 0;
+
+ // Find next first case-insensitive match of a character.
+ var idxOf = lString.indexOf(lWord[i], skip);
+
+ if (-1 === idxOf) { return score; }
+ score = calc(score, startAt, idxOf+1, runningScore, i, matchingPositions);
+ if (startAt === idxOf) {
+ // Consecutive letter & start-of-string Bonus
+ charScore = 0.8;
+ } else {
+ charScore = 0.1;
+
+ // Acronym Bonus
+ // Weighing Logic: Typing the first character of an acronym is as if you
+ // preceded it with two perfect character matches.
+ if (/^[^A-Za-z0-9]/.test(string[idxOf - 1])){
+ charScore += 0.7;
+ }else if(string[idxOf]==lWord[i]) {
+ // Upper case bonus
+ charScore += 0.2;
+ // Camel case bonus
+ if(/^[a-z]/.test(string[idxOf - 1])){
+ charScore += 0.5;
+ }
+ }
+ }
+
+ // Same case bonus.
+ if (string[idxOf] === word[i]) { charScore += 0.1; }
+
+ // next round
+ return calc(score, idxOf + 1, idxOf + 1, runningScore + charScore, i+1, matchingPositions.concat(idxOf));
+ }else{
+ // skip non match folder
+ var effectiveLength = strLength;
+ if(matchingPositions.length){
+ var lastSlash = string.lastIndexOf('/',matchingPositions[0]);
+ if(lastSlash!==-1){
+ effectiveLength = strLength-lastSlash;
+ }
+ }
+ // Reduce penalty for longer strings.
+ var finalScore = 0.5 * (runningScore / effectiveLength + runningScore / wordLength);
+
+ if ((lWord[0] === lString[0]) && (finalScore < 0.85)) {
+ finalScore += 0.15;
+ }
+ if(score.score >= finalScore){
+ return score;
+ }
+ return {score:finalScore, matchingPositions:matchingPositions};
+ }
+ }
+}
+/**
+ * sort by string_score.
+ * @param word {String} search word
+ * @param strings {Array[String]} search targets
+ * @param limit {Integer} result limit
+ * @return {Array[{score:"float matching score", string:"string target string", matchingPositions:"Array[Interger] matchng positions"}]}
+ */
+function string_score_sort(word, strings, limit){
+ var ret = [], i=0, l = (word==="")?Math.min(strings.length, limit):strings.length;
+ for(; i < l; i++){
+ var score = string_score(strings[i],word);
+ if(score.score){
+ score.string = strings[i];
+ ret.push(score);
+ }
+ }
+ ret.sort(function(a,b){
+ var s = b.score - a.score;
+ if(s === 0){
+ return a.string > b.string ? 1 : -1;
+ }
+ return s;
+ });
+ ret = ret.slice(0,limit);
+ return ret;
+}
+/**
+ * hilight by result.
+ * @param score {string:"string target string", matchingPositions:"Array[Interger] matchng positions"}
+ * @param hilight tag ex: ''
+ * @return array of hilighted html elements.
+ */
+function string_score_hilight(result, tag){
+ var str = result.string, msp=0;
+ return hilight([], 0, result.matchingPositions[msp]);
+ function hilight(html, c, mpos){
+ if(mpos === undefined){
+ return html.concat(document.createTextNode(str.substr(c)));
+ }else{
+ return hilight(html.concat([
+ document.createTextNode(str.substring(c,mpos)),
+ $(tag).text(str[mpos])]),
+ mpos+1, result.matchingPositions[++msp]);
+ }
+ }
+}
diff --git a/src/main/webapp/assets/vendors/jquery-hotkeys/jquery.hotkeys.js b/src/main/webapp/assets/vendors/jquery-hotkeys/jquery.hotkeys.js
new file mode 100644
index 0000000..3e5095e
--- /dev/null
+++ b/src/main/webapp/assets/vendors/jquery-hotkeys/jquery.hotkeys.js
@@ -0,0 +1,204 @@
+/*jslint browser: true*/
+/*jslint jquery: true*/
+
+/*
+ * jQuery Hotkeys Plugin
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
+ * Based upon the plugin by Tzury Bar Yochay:
+ * https://github.com/tzuryby/jquery.hotkeys
+ *
+ * Original idea by:
+ * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
+ */
+
+/*
+ * One small change is: now keys are passed by object { keys: '...' }
+ * Might be useful, when you want to pass some other data to your handler
+ */
+
+(function(jQuery) {
+
+ jQuery.hotkeys = {
+ version: "0.8",
+
+ specialKeys: {
+ 8: "backspace",
+ 9: "tab",
+ 10: "return",
+ 13: "return",
+ 16: "shift",
+ 17: "ctrl",
+ 18: "alt",
+ 19: "pause",
+ 20: "capslock",
+ 27: "esc",
+ 32: "space",
+ 33: "pageup",
+ 34: "pagedown",
+ 35: "end",
+ 36: "home",
+ 37: "left",
+ 38: "up",
+ 39: "right",
+ 40: "down",
+ 45: "insert",
+ 46: "del",
+ 59: ";",
+ 61: "=",
+ 96: "0",
+ 97: "1",
+ 98: "2",
+ 99: "3",
+ 100: "4",
+ 101: "5",
+ 102: "6",
+ 103: "7",
+ 104: "8",
+ 105: "9",
+ 106: "*",
+ 107: "+",
+ 109: "-",
+ 110: ".",
+ 111: "/",
+ 112: "f1",
+ 113: "f2",
+ 114: "f3",
+ 115: "f4",
+ 116: "f5",
+ 117: "f6",
+ 118: "f7",
+ 119: "f8",
+ 120: "f9",
+ 121: "f10",
+ 122: "f11",
+ 123: "f12",
+ 144: "numlock",
+ 145: "scroll",
+ 173: "-",
+ 186: ";",
+ 187: "=",
+ 188: ",",
+ 189: "-",
+ 190: ".",
+ 191: "/",
+ 192: "`",
+ 219: "[",
+ 220: "\\",
+ 221: "]",
+ 222: "'"
+ },
+
+ shiftNums: {
+ "`": "~",
+ "1": "!",
+ "2": "@",
+ "3": "#",
+ "4": "$",
+ "5": "%",
+ "6": "^",
+ "7": "&",
+ "8": "*",
+ "9": "(",
+ "0": ")",
+ "-": "_",
+ "=": "+",
+ ";": ": ",
+ "'": "\"",
+ ",": "<",
+ ".": ">",
+ "/": "?",
+ "\\": "|"
+ },
+
+ // excludes: button, checkbox, file, hidden, image, password, radio, reset, search, submit, url
+ textAcceptingInputTypes: [
+ "text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime",
+ "datetime-local", "search", "color", "tel"],
+
+ // default input types not to bind to unless bound directly
+ textInputTypes: /textarea|input|select/i,
+
+ options: {
+ filterInputAcceptingElements: true,
+ filterTextInputs: true,
+ filterContentEditable: true
+ }
+ };
+
+ function keyHandler(handleObj) {
+ if (typeof handleObj.data === "string") {
+ handleObj.data = {
+ keys: handleObj.data
+ };
+ }
+
+ // Only care when a possible input has been specified
+ if (!handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== "string") {
+ return;
+ }
+
+ var origHandler = handleObj.handler,
+ keys = handleObj.data.keys.toLowerCase().split(" ");
+
+ handleObj.handler = function(event) {
+ // Don't fire in text-accepting inputs that we didn't directly bind to
+ if (this !== event.target &&
+ (jQuery.hotkeys.options.filterInputAcceptingElements &&
+ jQuery.hotkeys.textInputTypes.test(event.target.nodeName) ||
+ (jQuery.hotkeys.options.filterContentEditable && jQuery(event.target).attr('contenteditable')) ||
+ (jQuery.hotkeys.options.filterTextInputs &&
+ jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) {
+ return;
+ }
+
+ var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[event.which],
+ character = String.fromCharCode(event.which).toLowerCase(),
+ modif = "",
+ possible = {};
+
+ jQuery.each(["alt", "ctrl", "shift"], function(index, specialKey) {
+
+ if (event[specialKey + 'Key'] && special !== specialKey) {
+ modif += specialKey + '+';
+ }
+ });
+
+ // metaKey is triggered off ctrlKey erronously
+ if (event.metaKey && !event.ctrlKey && special !== "meta") {
+ modif += "meta+";
+ }
+
+ if (event.metaKey && special !== "meta" && modif.indexOf("alt+ctrl+shift+") > -1) {
+ modif = modif.replace("alt+ctrl+shift+", "hyper+");
+ }
+
+ if (special) {
+ possible[modif + special] = true;
+ }
+ else {
+ possible[modif + character] = true;
+ possible[modif + jQuery.hotkeys.shiftNums[character]] = true;
+
+ // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
+ if (modif === "shift+") {
+ possible[jQuery.hotkeys.shiftNums[character]] = true;
+ }
+ }
+
+ for (var i = 0, l = keys.length; i < l; i++) {
+ if (possible[keys[i]]) {
+ return origHandler.apply(this, arguments);
+ }
+ }
+ };
+ }
+
+ jQuery.each(["keydown", "keyup", "keypress"], function() {
+ jQuery.event.special[this] = {
+ add: keyHandler
+ };
+ });
+
+})(jQuery || this.jQuery || window.jQuery);