Skip to content

Commit 3c38d64

Browse files
committed
feat: bookmarks working prototype
1 parent 3a2db30 commit 3c38d64

File tree

9 files changed

+697
-2
lines changed

9 files changed

+697
-2
lines changed

src-node/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
define(function (require, exports, module) {
2+
var CommandManager = require("command/CommandManager"),
3+
Commands = require("command/Commands"),
4+
ProjectManager = require("project/ProjectManager"),
5+
WorkspaceManager = require("view/WorkspaceManager"),
6+
MainViewManger = require("view/MainViewManager"),
7+
Strings = require("strings"),
8+
bookmarksPanelTemplate = require("text!./htmlContent/bookmarks-panel.html"),
9+
bookmarksListTemplate = require("text!./htmlContent/bookmarks-list.html"),
10+
Mustache = require("thirdparty/mustache/mustache");
11+
12+
/**
13+
* @const
14+
* Debounce time for document changes updating the search results view.
15+
* @type {number}
16+
*/
17+
var UPDATE_TIMEOUT = 400;
18+
19+
/**
20+
* @const
21+
* MainViewManager events
22+
* @type {string}
23+
*/
24+
var MVM_EVENTS = `workingSetAdd
25+
workingSetAddList
26+
workingSetMove
27+
workingSetRemove
28+
workingSetRemoveList
29+
workingSetUpdate
30+
currentFileChange
31+
activePaneChange`;
32+
33+
/**
34+
* @constructor
35+
* Creates a bookmarks panel
36+
* Dispatches the following events:
37+
* close - when the panel is closed.
38+
*
39+
* @typedef {Object.<string, Array<number>>} BookmarksModel
40+
* @param {BookmarksModel} model - bookmarks model
41+
* @param {function=} beforeRender - function to call before rendering the view
42+
*/
43+
function BookmarksView(model, beforeRender) {
44+
var panelHtml = Mustache.render(bookmarksPanelTemplate, {
45+
Strings: Strings
46+
});
47+
48+
this._panel = WorkspaceManager.createBottomPanel("bookmarks", $(panelHtml), 100);
49+
this._$panel = this._panel.$panel;
50+
this._$table = this._$panel.find(".table-container");
51+
this._model = model;
52+
this._beforeRender = beforeRender;
53+
}
54+
55+
/** @type {BookmarksModel} bookmarks model */
56+
BookmarksView.prototype._model = null;
57+
58+
/** @type {Panel} Bottom panel holding the bookmarks */
59+
BookmarksView.prototype._panel = null;
60+
61+
/** @type {$.Element} The table that holds the results */
62+
BookmarksView.prototype._$table = null;
63+
64+
/** @type {number} The ID we use for timeouts when handling model changes. */
65+
BookmarksView.prototype._timeoutID = null;
66+
67+
/** @type {function=} function that is called before refreshing the view */
68+
BookmarksView.prototype._beforeRender = null;
69+
70+
/**
71+
* @private
72+
* Handles when model changes. Updates the view, buffering changes if necessary so as not to churn too much.
73+
*/
74+
BookmarksView.prototype._handleModelChange = function () {
75+
var self = this;
76+
if (self._ignoreModelChangeEvents) {
77+
return;
78+
}
79+
if (this._timeoutID) {
80+
window.clearTimeout(this._timeoutID);
81+
}
82+
this._timeoutID = window.setTimeout(function () {
83+
// _updateResults causes the model to be recomputed
84+
// which triggers another model change event which
85+
// we need to ignore or we endup in a race condition
86+
// which may lead to data loss
87+
self._ignoreModelChangeEvents = true;
88+
self._updateResults();
89+
self._timeoutID = null;
90+
delete self._ignoreModelChangeEvents;
91+
}, UPDATE_TIMEOUT);
92+
};
93+
94+
/**
95+
* @private
96+
* Adds the listeners for close and clicking on a bookmark in the list
97+
*/
98+
BookmarksView.prototype._addPanelListeners = function () {
99+
var self = this;
100+
this._$panel
101+
.off(".bookmarks") // Remove the old events
102+
.on("click.bookmarks", ".close", function () {
103+
self.close();
104+
})
105+
// Add the click event listener directly on the table parent
106+
.on("click.bookmarks .table-container", function (e) {
107+
var $row = $(e.target).closest("tr");
108+
109+
if ($row.length) {
110+
if (self._$selectedRow) {
111+
self._$selectedRow.removeClass("selected");
112+
}
113+
$row.addClass("selected");
114+
self._$selectedRow = $row;
115+
116+
var fullPathAndLineNo = $row.find(".bookmark-result").text();
117+
118+
CommandManager.execute(Commands.FILE_OPEN, { fullPath: fullPathAndLineNo });
119+
}
120+
});
121+
MainViewManger.on(MVM_EVENTS, this._updateResults.bind(this));
122+
};
123+
124+
/**
125+
* @private
126+
* @param {!String} fullpath - path of the file to show
127+
*/
128+
BookmarksView.prototype._shouldShow = function (fullpath) {
129+
if (!this._options || !this._options.show || this._options.show === "opened") {
130+
return Boolean(MainViewManger._getPaneIdForPath(fullpath));
131+
} else if (this._options.show === "all") {
132+
return true;
133+
} else if (this._options.show === "project" && ProjectManager.getProjectRoot()) {
134+
// show open files and any file bookmarked in the current project
135+
return (
136+
Boolean(MainViewManger._getPaneIdForPath(fullpath)) ||
137+
fullpath.toLowerCase().indexOf(ProjectManager.getProjectRoot().fullPath.toLowerCase()) === 0
138+
);
139+
}
140+
141+
// unknown option
142+
return false;
143+
};
144+
145+
/**
146+
* @private
147+
* Shows the current set of results.
148+
*/
149+
BookmarksView.prototype._render = function () {
150+
var self = this,
151+
bookmarks = [];
152+
153+
if (this._beforeRender) {
154+
this._beforeRender();
155+
}
156+
157+
// Iterates throuh the files to display the results sorted by filenamess. The loop ends as soon as
158+
// we filled the results for one page
159+
Object.keys(this._model)
160+
.filter(function (fullPath) {
161+
return self._shouldShow(fullPath);
162+
})
163+
.sort(function (a, b) {
164+
return a > b;
165+
})
166+
.forEach(function (fullPath) {
167+
self._model[fullPath].forEach(function (lineNo) {
168+
bookmarks.push({
169+
fullPath: fullPath,
170+
lineNo: lineNo + 1
171+
});
172+
});
173+
});
174+
175+
// Insert the search results
176+
this._$table.empty().append(
177+
Mustache.render(bookmarksListTemplate, {
178+
bookmarks: bookmarks,
179+
Strings: Strings
180+
})
181+
);
182+
183+
if (this._$selectedRow) {
184+
this._$selectedRow.removeClass("selected");
185+
this._$selectedRow = null;
186+
}
187+
188+
this._panel.show();
189+
this._$table.scrollTop(0); // Otherwise scroll pos from previous contents is remembered
190+
};
191+
192+
/**
193+
* Updates the results view after a model change, preserving scroll position and selection.
194+
*/
195+
BookmarksView.prototype._updateResults = function () {
196+
// In general this shouldn't get called if the panel is closed, but in case some
197+
// asynchronous process kicks this (e.g. a debounced model change), we double-check.
198+
if (this._panel.isVisible()) {
199+
var scrollTop = this._$table.scrollTop(),
200+
index = this._$selectedRow ? this._$selectedRow.index() : null;
201+
this._render();
202+
this._$table.scrollTop(scrollTop);
203+
if (index) {
204+
this._$selectedRow = this._$table.find("tr:eq(" + index + ")");
205+
this._$selectedRow.addClass("selected");
206+
}
207+
}
208+
};
209+
210+
/**
211+
* Opens the results panel and displays the current set of results from the model.
212+
*/
213+
BookmarksView.prototype.open = function (options) {
214+
this._options = options;
215+
this._render();
216+
this._addPanelListeners();
217+
$(this._model).on("change.BookmarksView", this._handleModelChange.bind(this));
218+
};
219+
220+
/**
221+
* Hides the Search Results Panel and unregisters listeners.
222+
*/
223+
BookmarksView.prototype.close = function () {
224+
if (this._panel && this._panel.isVisible()) {
225+
this._$table.empty();
226+
this._panel.hide();
227+
this._panel.$panel.off(".bookmarks");
228+
$(this._model).off("change.BookmarksView");
229+
$(this).triggerHandler("close");
230+
}
231+
};
232+
233+
/**
234+
* Hides the Search Results Panel and unregisters listeners.
235+
*/
236+
BookmarksView.prototype.isOpen = function () {
237+
return this._panel && this._panel.isVisible();
238+
};
239+
240+
// Public API
241+
exports.BookmarksView = BookmarksView;
242+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<table class="bottom-panel-table table table-striped table-condensed row-highlight">
2+
<tbody>
3+
{{#bookmarks}}
4+
<tr>
5+
<td class="bookmark-result">{{fullPath}}:{{lineNo}}</td>
6+
</tr>
7+
{{/bookmarks}}
8+
</tbody>
9+
</table>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<div id="bookmarks-panel" class="bottom-panel vert-resizable top-resizer no-focus">
2+
<div class="toolbar simple-toolbar-layout">
3+
<div class="title">{{Strings.BOOKMARKS_PANEL_TITLE}}</div>
4+
<a href="#" class="close">&times;</a>
5+
</div>
6+
<div class="table-container resizable-content"></div>
7+
</div>

0 commit comments

Comments
 (0)