diff --git a/plugins/filefinder/facebox.css b/plugins/filefinder/facebox.css new file mode 100644 index 000000000..7f165dcc5 --- /dev/null +++ b/plugins/filefinder/facebox.css @@ -0,0 +1,79 @@ +#facebox { + position:absolute; + top:0; + left:0; + z-index:100; + text-align:left; +} + +#facebox .popup{ + position:relative; + border:3px solid rgba(0,0,0,0); + -webkit-border-radius:5px; + -moz-border-radius:5px; + border-radius:5px; + -webkit-box-shadow:0 0 18px rgba(0,0,0,0.4); + -moz-box-shadow:0 0 18px rgba(0,0,0,0.4); + box-shadow:0 0 18px rgba(0,0,0,0.4); +} + +#facebox .content{ + display:table; + width:370px; + padding:10px; + background:#fff; + -webkit-border-radius:4px; + -moz-border-radius:4px; + border-radius:4px; +} + +#facebox .content > p:first-child{ + margin-top:0; +} +#facebox .content > p:last-child{ + margin-bottom:0; +} + +#facebox .close{ + position:absolute; + top:5px; + right:5px; + padding:2px; + width:8px; + height:8px; + opacity:0.3; + z-index:10000; +} +#facebox .close:hover{ + opacity:1.0; + text-decoration:none; +} + +#facebox .fb-loading{ + height:32px; + text-align:center; +} + +#facebox img{ + display:block; + border:0; + margin:0 auto; +} + +#facebox-overlay{ + position:fixed; + top:0px; + left:0px; + height:100%; + width:100%; + background-color:#000; + z-index:99; + display:none; +} + +#facebox .close.inline{ + background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEpJREFUeNpiYGBgEAXivUCsxYAAukC8CyoHlvwPxK+gErpQ9n+oHIrAKzS2LrKRMAkUSSYG3OAvNt1YrSDoSJg3ddG8CRITBQgwALGIIG7dYZgsAAAAAElFTkSuQmCC") top right no-repeat; +} +#facebox .fb-loading{ + background:center url("data:image/gif;base64,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") no-repeat; +} diff --git a/plugins/filefinder/facebox.js b/plugins/filefinder/facebox.js new file mode 100644 index 000000000..6a8b4aa1d --- /dev/null +++ b/plugins/filefinder/facebox.js @@ -0,0 +1,154 @@ +// facebox 2.0 +// MIT Licensed +// https://github.com/defunkt/facebox +(function($){ + + // jQuery plugin + // + // $('a[rel*=facebox]').facebox() + // $('.js-add-cc').facebox(function() { + // $('#facebox .js-thanks, #facebox .rule:first').hide() + // }) + $.fn.facebox = function( callback ) { + return this.live('click.facebox', function(){ + // rel="facebox.my-class" adds my-class to '#facebox .content' + var contentClass = ( /facebox\.(\S+)/.exec(this.rel) || [] )[1] + + if ( callback ) + $(document).one('show.facebox', callback) + + $.facebox({ div: this.href }, contentClass) + + return false + }) + } + + // The Real Deal + // + // $.facebox('Cool!') + $.facebox = function( data, contentClass ) { + if ( $('#facebox .fb-loading').length == 0 ) { + show('
 
') + $(document).trigger('loading.facebox') + } + + $('#facebox .content').addClass(contentClass) + + if ( !data ) return + + // $.facebox('#info') => $.facebox({div: '#info'}) + if ( /^#/.test(data) ) + data = { div: data } + + // $.facebox('/some/url') => $.facebox({ajax: '/some/url' }) + if ( /^\//.test(data) ) + data = { ajax: data } + + var href = data.ajax || data.image || data.div + if ( href ) { + // div + var div = /#.+$/.exec(href) + if ( div ) { + show( $(div[0]).html() ) + + // image + } else if ( data.image || /\.(png|jpe?g|gif)(\?\S*)?$/i.test(href) ) { + var image = new Image + image.onload = function() { + show('') + } + image.src = href + + // ajax + } else { + $.get(href, show) + } + } + + else if ( $.isFunction(data) ) + data() + else + show(data) + } + + + // + // Default settings + // + + var settings = $.facebox.settings = { + opacity : 0.2, + overlay : true, + faceboxHTML: '
' + } + + + // + // Methods + // + + // Close Facebox + var close = $.facebox.close = function(){ + $(document).trigger('close.facebox') + return false + } + + // Show the Facebox + function show( data ) { + // Dim the lights + if ( settings.overlay && !$('#facebox-overlay').is(':visible') ) { + $('body').append('
') + + $('#facebox-overlay') + .css('opacity', settings.opacity) + .fadeIn(200) + .click(close) + } + + // Create the Facebox + if ( $('#facebox').length == 0 ) { + $('body').append( $(settings.faceboxHTML).hide() ) + $(document).trigger('setup.facebox') + } + + // Make that money + $('#facebox .content').html(data).show() + + // Position Facebox based on the user's scroll + $('#facebox').show().css({ + top: $(window).scrollTop() + ($(window).height() / 10), + left: $(window).width() / 2 - ($('#facebox .popup').outerWidth() / 2) + }) + + // Fire events if we're not loading. + if ( $('.fb-loading:visible').length == 0 ) + $(document).trigger('show.facebox').trigger('reveal.facebox') + } + + + // + // Bindings + // + + $(document).bind('close.facebox', function(){ + $('#facebox').fadeOut(function(){ + $(this).remove() + $(document).trigger('afterClose.facebox') + }) + + $('#facebox-overlay').fadeOut(200, function(){ + $(this).remove() + }) + }) + + $(document).one('show.facebox', function(){ + // Click on close image = Close Facebox + $('#facebox .close').live('click', close) + + // ESC = Close Facebox + $(document).bind('keydown.facebox', function(e){ + if ( e.keyCode == 27 && $('#facebox:visible').length ) close() + return true + }) + }) +})(jQuery); diff --git a/plugins/filefinder/filefinder.coffee b/plugins/filefinder/filefinder.coffee new file mode 100644 index 000000000..e818ca760 --- /dev/null +++ b/plugins/filefinder/filefinder.coffee @@ -0,0 +1,106 @@ +$ = require 'jquery' +_ = require 'underscore' + +{activeWindow} = require 'app' +File = require 'fs' +Pane = require 'pane' + +jQuery = $ +facebox = eval File.read require.resolve 'filefinder/facebox' +require 'filefinder/stringscore' + +module.exports = +class Filefinder extends Pane + showing: false + files: [] + + html: require "filefinder/filefinder.html" + + keymap: + 'Command-T': 'toggle' + # really wish i could put up/down keyboad shortcuts here + # and have them activated when the filefinder is open + + initialize: -> + $('#filefinder input').live 'keydown', @onKeydown + + css = File.read require.resolve 'filefinder/facebox.css' + head = $('head')[0] + style = document.createElement 'style' + rules = document.createTextNode css + style.type = 'text/css' + style.appendChild rules + head.appendChild style + + onKeydown: (e) => + keys = up: 38, down: 40, enter: 13 + + if e.keyCode is keys.enter + @openSelected() + else if e.keyCode is keys.up + @moveUp() + else if e.keyCode is keys.down + @moveDown() + else + @filterFiles() + + toggle: -> + if @showing + $.facebox.close() + else + @showFinder() + @showing = not @showing + + showFinder: -> + $.facebox @html + @files = [] + for file in activeWindow.project.paths() + @files.push file.replace "#{activeWindow.project.dir}/", '' + @filterFiles() + + findMatchingFiles: (query) -> + return [] if not query + + results = [] + for file in @files + score = file.score query + if score > 0 + # Basename matches count for more. + if not query.match '/' + if name.match '/' + score += name.replace(/^.*\//, '').score query + else + score *= 2 + results.push [score, file] + + sorted = results.sort (a, b) -> b[0] - a[0] + _.map sorted, (el) -> el[1] + + filterFiles: -> + if query = $('#filefinder input').val() + files = @findMatchingFiles query + else + files = @files + $('#filefinder ul').empty() + for file in files[0..10] + $('#filefinder ul').append "
  • #{file}
  • " + $('#filefinder input').focus() + $('#filefinder li:first').addClass 'selected' + + openSelected: -> + dir = activeWindow.project.dir + file = $('#filefinder .selected').text() + activeWindow.open "#{dir}/#{file}" + @toggle() + + moveUp: -> + selected = $('#filefinder .selected') + if selected.prev().length + selected.prev().addClass 'selected' + selected.removeClass 'selected' + + moveDown: -> + selected = $('#filefinder .selected') + if selected.next().length + selected.next().addClass 'selected' + selected.removeClass 'selected' \ No newline at end of file diff --git a/plugins/filefinder/filefinder.html b/plugins/filefinder/filefinder.html new file mode 100644 index 000000000..44dff0048 --- /dev/null +++ b/plugins/filefinder/filefinder.html @@ -0,0 +1,17 @@ + + +
    + +
    + +
    \ No newline at end of file diff --git a/plugins/filefinder/index.coffee b/plugins/filefinder/index.coffee new file mode 100644 index 000000000..2983adaa4 --- /dev/null +++ b/plugins/filefinder/index.coffee @@ -0,0 +1,3 @@ +exports.Filefinder = Filefinder = require 'filefinder/filefinder' + +new Filefinder \ No newline at end of file diff --git a/plugins/filefinder/stringscore.js b/plugins/filefinder/stringscore.js new file mode 100644 index 000000000..f1f9cabfd --- /dev/null +++ b/plugins/filefinder/stringscore.js @@ -0,0 +1,122 @@ +/*! + * string_score.js: String Scoring Algorithm 0.1.9 + * + * http://joshaven.com/string_score + * https://github.com/joshaven/string_score + * + * Copyright (C) 2009-2011 Joshaven Potter + * Copyright (C) 2010-2011 Yesudeep Mangalapilly + * MIT license: http://www.opensource.org/licenses/mit-license.php + * + * Date: Tue Mar 1 2011 +*/ + +/** + * Scores a string against another string. + * 'Hello World'.score('he'); //=> 0.5931818181818181 + * 'Hello World'.score('Hello'); //=> 0.7318181818181818 + */ +String.prototype.score = function(abbreviation, fuzziness) { + var total_character_score = 0, + abbreviation_length = abbreviation.length, + string = this, + string_length = string.length, + start_of_string_bonus, + abbreviation_score, + fuzzies=1, + final_score; + + // If the string is equal to the abbreviation, perfect match. + if (string == abbreviation) {return 1.0;} + + // Walk through abbreviation and add up scores. + for (var i = 0, + character_score/* = 0*/, + index_in_string/* = 0*/, + c/* = ''*/, + index_c_lowercase/* = 0*/, + index_c_uppercase/* = 0*/, + min_index/* = 0*/; + i < abbreviation_length; + ++i) { + + // Find the first case-insensitive match of a character. + c = abbreviation[i]; + + //index_in_string = __first_valid_index( + // string.indexOf(c.toLowerCase()), + // string.indexOf(c.toUpperCase()) + //); + // Inlined the above call below. + index_c_lowercase = string.indexOf(c.toLowerCase()); + index_c_uppercase = string.indexOf(c.toUpperCase()); + min_index = Math.min(index_c_lowercase, index_c_uppercase); + index_in_string = (min_index > -1) ? + min_index : + Math.max(index_c_lowercase, index_c_uppercase); + // End inlining. + + if (index_in_string === -1) { + if (fuzziness) { + fuzzies += 1-fuzziness; + break; + } else { + return 0; + } + } else { + character_score = 0.1; + } + + // Set base score for matching 'c'. + + // Same case bonus. + if (string[index_in_string] === c) { + character_score += 0.1; + } + + // Consecutive letter & start-of-string Bonus + if (index_in_string === 0) { + // Increase the score when matching first character of the + // remainder of the string + character_score += 0.6; + if (i === 0) { + // If match is the first character of the string + // & the first character of abbreviation, add a + // start-of-string match bonus. + start_of_string_bonus = 1 //true; + } + } + + // Acronym Bonus + // Weighing Logic: Typing the first character of an acronym is as if you + // preceded it with two perfect character matches. + if (string.charAt(index_in_string - 1) === ' ') { + character_score += 0.8; // * Math.min(index_in_string, 5); // Cap bonus at 0.4 * 5 + } + + // Left trim the already matched part of the string + // (forces sequential matching). + string = string.substring(index_in_string + 1, string_length); + + total_character_score += character_score; + } // end of for loop + + // Uncomment to weigh smaller words higher. + // return total_character_score / string_length; + + abbreviation_score = total_character_score / abbreviation_length; + //percentage_of_matched_string = abbreviation_length / string_length; + //word_score = abbreviation_score * percentage_of_matched_string; + + // Reduce penalty for longer strings. + //final_score = (word_score + abbreviation_score) / 2; + final_score = ((abbreviation_score * (abbreviation_length / string_length)) + abbreviation_score) / 2; + + final_score = final_score / fuzzies; + + if (start_of_string_bonus && (final_score + 0.15 < 1)) { + final_score += 0.15; + } + + return final_score; +};