Show file path in the tab when there are several files with the same name - filepath

Please take a look at the screenshot:
As you can see there are 3 tabs with 3 different "index.xml" files opened.
I've been looking for an option to show something like "folder/file.extension" in the tabs name to be able to differentiate the files, but I can't find anything.
Using "Go to file" is not very helpful either, because the name of the path for all the files is so long that I can't see the folder containing the files.
Any ideas?
Cheers
UPDATE:
It's possible to increase the 'Go to file' panel width using the mouse and Komodo will remember the size in the future. That helps!

We added the OpenFiles pane specifically for this purpose, please check out View > Tabs & Sidebars > Open Files.
This is what my pane looks like with several files with the same name open:
Additionally you can specify your own patterns, currently this is done programmatically via a macro but in the future you will be able to do this through the UI.
For example, I use the following macro for Komodo development:
ko.openfiles.groupers.byPattern.patterns = [
{
name: 'Plat - %match% - config',
pattern: /\/skin\/plat\/([a-z0-9_-]*)\/_config\//i
},
{
name: 'Plat - %match%',
pattern: /\/skin\/plat\/([a-z0-9_-]*)\//i
},
{
name: 'Module - %match% - skin config',
pattern: /\/(?:module|modules)\/([a-z0-9_-]*)\/skin\/_config\//i
},
{
name: 'Module - %match%',
pattern: /\/(?:module|modules)\/([a-z0-9_-]*)\//i
},
{
name: 'Skin - %match% - config',
pattern: /\/chrome\/skins\/([a-z0-9_-]*)\/_config\//i
},
{
name: 'Skin - %match%',
pattern: /\/chrome\/skins\/([a-z0-9_-]*)\//i
},
{
name: 'Iconset - %match%',
pattern: /\/chrome\/iconsets\/([a-z0-9_-]*)\//i
},
{
name: 'Component - %match%',
pattern: /\/(?:component|components)\/([a-z0-9_-]*)/i
},
{
name: 'Locale',
pattern: /\/locale(?:\/|$)/i
},
{
name: 'Skin',
pattern: /\/skin(?:\/|$)/i
},
{
name: 'Module',
pattern: /\/(?:module|modules)(?:\/|$)/i
},
{
name: 'Component',
pattern: /\/(?:component|components)(?:\/|$)/i
},
];
ko.openfiles.reload(true);
You can read up on macro's here: http://docs.activestate.com/komodo/8.5/macros.html#macros_writing
Using the above macro I need to make sure I have the "Group By Pattern" option selected, then I just run the macro and the same files you see in my above screenshot will be grouped according to the patterns I specified:
Note that this requires the latest version of Komodo (8.5).
Also note that if you use the Open Files pane you may find that you do not need the regular tabs anymore, you can disable these under "View > View Editor Tabs".
Hope that helps, good luck!

Use the Komodo JavaScript API to change the default display of tab titles:
komodo.assertMacroVersion(3);
function changeTabTitles(useLongerTitle, splitLength) {
try {
var vm = ko.views.manager.topView;
var box = document.getAnonymousNodes(vm)[0];
// Get the views-tabbed elements.
var topTabs = box.firstChild;
var bottomTabs = box.lastChild;
if (!useLongerTitle) {
// Restore the original functionality.
if (topTabs._tweakui_updateLeafName) {
topTabs.updateLeafName = topTabs._tweakui_updateLeafName;
topTabs._tweakui_updateLeafName = null;
}
if (bottomTabs._tweakui_updateLeafName) {
bottomTabs.updateLeafName = bottomTabs._tweakui_updateLeafName;
bottomTabs._tweakui_updateLeafName = null;
}
} else {
// Save the original functionality.
if (!topTabs._tweakui_updateLeafName)
topTabs._tweakui_updateLeafName = topTabs.updateLeafName;
if (!bottomTabs._tweakui_updateLeafName)
bottomTabs._tweakui_updateLeafName = bottomTabs.updateLeafName;
// Replace the updateLeafName implementation to use something
// different for the tab label.
var osSvc = Components.classes["#activestate.com/koOs;1"].
getService(Components.interfaces.koIOs);
var dirsep = osSvc.sep;
topTabs.updateLeafName =
bottomTabs.updateLeafName = function(view) {
view.parentNode._tab.label = view.title;
if (view.document) {
view.parentNode._tab.setAttribute('crop', 'start');
var path = view.document.displayPath;
var sep = dirsep;
if (path.lastIndexOf(sep) == -1) {
// Try using the URI separator.
sep = "/";
}
var path_split = path.split(sep);
var l = path_split.length;
var label = path_split.slice(l-splitLength, l).join(sep);
view.parentNode._tab.label = label;
view.parentNode._tab.setAttribute('tooltiptext',view.document.displayPath);
this.tabbox.firstChild.scrollBoxObject.ensureElementIsVisible(this.tabbox.firstChild.selectedItem);
}
}
}
Save it as a macro and customize it to your liking.

code updated for Komodo 8.5 (view.document -> view.koDoc)
komodo.assertMacroVersion(3);
try {
var vm = ko.views.manager.topView;
var box = document.getAnonymousNodes(vm)[0];
// get the views-tabbed elements
var tabset1 = box.firstChild;
var tabset2 = box.lastChild;
// replace the updateLeafName implementation to use something different
// for the tab label
tabset1.updateLeafName =
tabset2.updateLeafName = function(view) {
view.parentNode._tab.label = view.title;
if (view.koDoc) {
var language = view.koDoc.language;
if (language == 'Python') {
var parts = view.koDoc.displayPath.split('/');
var len = parts.length;
var label = '';
if (len > 2) {
label += parts[len-2] + '/';
}
label += parts[len-1];
view.parentNode._tab.setAttribute('crop', 'start');
view.parentNode._tab.label = label;
view.parentNode._tab.setAttribute('tooltiptext',view.koDoc.displayPath);
this.tabbox.firstChild.scrollBoxObject.ensureElementIsVisible(this.tabbox.firstChild.selectedItem);
}
}
};
// the "on startup" trigger happens after files
// are opened, so we need to call updateLeafName
// for each opened view. Files opened after startup
// will be fine
var views = ko.views.manager.topView.getViews(true);
for (var i=0; i < views.length; i++) {
if (views[i].koDoc) {
views[i].updateLeafName(views[i]);
}
}
} catch(e) {
alert(e);
}

Related

File System to JSON tree asynchronously in Javascript Chrome App

I have been going at this for hours, but my head is going round in circles and confusing itself with async with callbacks...
I am looking to take a DirectoryEntry in a chrome app and recursively create a JSON file tree, like below:
[{
name: ent.name,
path: ent.fullPath,
isDir: ent.isDirectory
},
{
name: ent.name,
path: ent.fullPath,
isDir: ent.isDirectory,
children: [
{
name: ent.name,
path: ent.fullPath,
isDir: ent.isDirectory
},
{
name: ent.name,
path: ent.fullPath,
isDir: ent.isDirectory
}
]
}]
In searching I have found many node.js implementations, and have it working in node, but in trying to port it over to FileSystem API and chrome.filesystem in particular, it all goes to pot.
I have also found https://stackoverflow.com/a/21909322 this post which list_dir function is very nearly what I want but it creates a flat structure of all files rather than the structure.
I have tried adapting to create the children but cannot get my head around the callbacks.
function walkSync(dirent, cb, listing) {
console.log('Entering walkSync()')
if (listing === undefined) listing = [];
var reader = dirent.createReader();
var read_some = reader.readEntries.bind(reader, function(ents) {
if (ents.length === 0)
return cb && cb(listing);
process_some(ents, 0);
function process_some(ents, i) {
for(; i < ents.length; i++) {
var obj = { name: ents[i].name, isDir: ents[i].isDirectory};
if (ents[i].isDirectory) {
obj.children = walkSync(ents[i], process_some.bind(null, ents, i + 1), listing);
}
listing.push(obj);
}
read_some();
}
}, function() {
console.error('error reading directory');
});
read_some();
}
Any pointers to help me get my head round recursion and callbacks to create this would be a great help and reduce the amount of further lost hours..
Thanks!

Meteor GridFS 403 forbidden

Possible duplicate: Meteor collectionfs insert server side
I have multiple image collections using GridFS to store files. If I use "Images" as name for the collection everything works fine, but as soon as I change the name I'm getting GET http://localhost:3000/cfs/files/PlayerImages/zo4Z7rnYLb32MLYkM/taylor.jpg 403 (Forbidden)
(And for example, this one does work: http://localhost:3000/cfs/files/Images/7j9XAEebGctuivGhz/see-more.png)
This is how I defined my collections:
function createImageCollection(name,transform){
var collectionName = name + 's';
var storeName = name + 'Store';
var options = {
filter: {
allow: {
contentTypes: ['image/*'],
extensions: ['png', 'PNG', 'jpg', 'JPG', 'jpeg', 'JPEG']
}
}
};
if (Meteor.isServer) {
storeOptions = {
path: process.env.PWD + "/public"
};
// only add the transform when graphicsmagick is available
if(gm.isAvailable && typeof transform === 'function'){
storeOptions.transformWrite = transform;
}
options.stores = [new FS.Store.GridFS(storeName,storeOptions)];
} else {
options.stores = [new FS.Store.GridFS(storeName)];
}
return new FS.Collection(collectionName,options);
}
// and finally create them
// THIS FIRST ONE WORKS JUST FINE
Images = createImageCollection('Image',imageResizeTimeline); // default resize is timeline, nothing bigger then that?
BackgroundImages = createImageCollection('BackgroundImage',imageResizeBackground);
PlayerImages = createImageCollection('PlayerImage',imageResizePlayer);
LogoImages = createImageCollection('LogoImage',imageResizeLogo);
And I also added allow/deny rules for each collection:
var ImagePermissions = {
insert: function () {
return true;
},
update: function () {
return true;
},
download: function () {
return true;
},
remove: function () {
return false;
},
fetch: null
};
Images.allow(ImagePermissions);
PlayerImages.allow(ImagePermissions);
BackgroundImages.allow(ImagePermissions);
LogoImages.allow(ImagePermissions);
On a sidenote, I am using autoform to generate forms, but I don't think the problem is located in there because "Images" works.
Must I add this path to some "routing" or "config" files? Maybe "Images" is added by default? (This project is set-up by someone else, but I don't think I missed anything he did.)

How to make Chrome Extension run for each new Iframe added?

I created a Chrome Extension as a solution to override the helpText bubbles in SalesForce Console pages. The helpText bubbles show up the text without the ability to link URLs. It looks like this:
The extension is taking the helpText bubble (which in the SalesForce console window, is inside an iFrame) and makes the URL click-able. It also adds word wrap and marks the links in blue.
The solution works fine when the page loads with the initial iFrame (or iFrames) on it, meaning when you open the SalesForce console the first time (https://eu3.salesforce.com/console).
When a new tab is created at the SalesForce console, my inject script doesn't run.
Can you please assist in understanding how to inject the script on each and every new Tab SalesForce Console is creating?
The Extension as follows:
manifest.js:
{
"browser_action": {
"default_icon": "icons/icon16.png"
},
"content_scripts": [ {
"all_frames": true,
"js": [ "js/jquery/jquery.js", "src/inject/inject.js" ],
"matches": [ "https://*.salesforce.com/*", "http://*.salesforce.com/*" ]
} ],
"default_locale": "en",
"description": "This extension Fix SalesForce help bubbles",
"icons": {
"128": "icons/icon128.png",
"16": "icons/icon16.png",
"48": "icons/icon48.png"
},
"manifest_version": 2,
"name": "--Fix SalesForce bubble text--",
"permissions": [ "https://*.salesforce.com/*", "http://*.salesforce.com/*" ],
"update_url": "https://clients2.google.com/service/update2/crx",
"version": "5"
}
And this is the inject.js:
chrome.extension.sendMessage({}, function(response) {
var readyStateCheckInterval = setInterval(function() {
if (document.readyState === "complete") {
clearInterval(readyStateCheckInterval);
var frame = jQuery('#servicedesk iframe.x-border-panel');
frame = frame.contents();
function linkify(inputText) {
var replacedText, replacePattern1, replacePattern2, replacePattern3;
var originalText = inputText;
//URLs starting with http://, https://, file:// or ftp://
replacePattern1 = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&##\/%?=~_|!:,.;]*[-A-Z0-9+&##\/%=~_|])/gim;
replacedText = inputText.replace(replacePattern1, '$1');
//URLs starting with "www." (without // before it, or it'd re-link the ones done above).
replacePattern2 = /(^|[^\/f])(www\.[\S]+(\b|$))/gim;
replacedText = replacedText.replace(replacePattern2, '$1$2');
//Change email addresses to mailto:: links.
replacePattern3 = /(([a-zA-Z0-9\-\_\.])+#[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
replacedText = replacedText.replace(replacePattern3, '$1');
//If there are hrefs in the original text, let's split
// the text up and only work on the parts that don't have urls yet.
var count = originalText.match(/<a href/g) || [];
if(count.length > 0){
var combinedReplacedText;
//Keep delimiter when splitting
var splitInput = originalText.split(/(<\/a>)/g);
for (i = 0 ; i < splitInput.length ; i++){
if(splitInput[i].match(/<a href/g) == null){
splitInput[i] = splitInput[i].replace(replacePattern1, '$1').replace(replacePattern2, '$1$2').replace(replacePattern3, '$1');
}
}
combinedReplacedText = splitInput.join('');
return combinedReplacedText;
} else {
return replacedText;
}
}
var helpOrbReady = setInterval(function() {
var helpOrb = frame.find('.helpOrb');
if (helpOrb) {
clearInterval(helpOrbReady)
} else {
return;
}
helpOrb.on('mouseout', function(event) {
event.stopPropagation();
event.preventDefault();
setTimeout(function() {
var helpText = frame.find('.helpText')
helpText.css('display', 'block');
helpText.css('opacity', '1');
helpText.css('word-wrap', 'break-word');
var text = helpText.html()
text = text.substr(text.indexOf('http'))
text = text.substr(0, text.indexOf(' '))
var newHtml = helpText.html()
helpText.html(linkify(newHtml))
}, 500); });
}, 1000);
}
}, 1000);
});
It is possible (I have not tested it, but it sounds plausible from a few questions I've seen here) that Chrome does not automatically inject manifest-specified code into newly-created <iframe> elements.
In that case, you will have to use a background script to re-inject your script:
chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) {
if(request.reinject) {
chrome.tabs.executeScript(
sender.tab.id,
{ file: "js/jquery/jquery.js", "all_frames": true },
function(){
chrome.tabs.executeScript(
sender.tab.id,
{ file: "js/inject/inject.js", "all_frames": true }
);
}
);
});
Content script:
// Before everything: include guard, ensure injected only once
if(injected) return;
var injected = true;
function onNewIframe(){
chrome.runtime.sendMessage({reinject: true});
}
Now, I have many questions about your code, which are not directly related to your question.
Why the pointless sendMessage wrapper? No-one is even listening, so your code basically returns with an error set.
Why all the intervals? Use events instead of polling.
If you are waiting on document to become ready, jQuery offers $(document).ready(...)
If you're waiting on DOM modifications, learn to use DOM Mutation Observers, as documented and as outlined here or here. This would be, by the way, the preferred way to call onNewIframe().

ExtJS : move a record in a grid

I have a simple grid on ExtJS and would like the user to be able to move the record from its original position.
When the user double clicks on a record, a small window containing a combobox appears, he can choose a value on the combobox and then click the save button to apply the change.
However, it doesn't work, I've searched many solutions for this on different forums and none seems to work. Either nothing happens, or an undefined row is added at the end of the grid. Here is the base code I use :
onEditRank: function(view, cell, cellIndex, record, row, rowIndex, e)
{
var reditor = Ext.create('CMS.view.Views.RankEditor', {id: 'reditorView'});
var form = reditor.down('form');
var oldPos = this.getFlatrq().getView().indexOf(record);
var grStore = this.getGridRnkStoreStore();
var i;
var data = [];
for(i = 1; i <= CMS.global.Variables.limit + 1; i++)
{
data.push(i);
}
var combo = Ext.create
(
'Ext.form.field.ComboBox',
{
fieldLabel: 'Rank',
itemId: 'cmbRank',
store: data
}
);
var saveRnk = Ext.create
(
'Ext.button.Button',
{
text: 'Save',
handler: function()
{
}
}
);
form.add(combo);
form.add(saveRnk);
reditor.show();
}
Now here are the different handlers I have tried for my save button :
handler: function()
{
grStore.remove(record);
grStore.insert(record, combo.getValue() - 1);
this.up('form').up('window').close();
}
handler: function()
{
grStore.removeAt(oldPos);
grStore.insert(record, combo.getValue() - 1);
this.up('form').up('window').close();
}
handler: function()
{
var rec = grStore.getAt(oldPos).copy();
grStore.removeAt(oldPos);
grStore.insert(rec, combo.getValue() - 1);
this.up('form').up('window').close();
}
Those 3 handlers inserted undefined rows at the end of my grid. I displayed the values of oldPos and combo.getValue() and they are correct, I also displayed the record variable before and after the remove because I thought it might be destroyed but it wasn't. I have also tried to add a move function to store and call it :
'CMS.store.GridRnkStore',
{
extend: 'Ext.data.Store',
model: 'CMS.model.GridRnkModel',
autoLoad: false,
filterOnLoad: true,
autoSync: true,
move: function(from, to)
{
console.log(from + " " + to);
var r = this.getAt(from);
this.data.removeAt(from);
this.data.insert(to, r);
this.fireEvent("move", this, from, to);
},
}
);
But it didn't work either, it did nothing actually (I put some console.log in the move function to see if it was called and it was, with the right parameters). I'm running out of ideas, any help would be appreciated.
Thank you.
try to set the private move parameter to true of store.remove():
remove: function(records, /* private */ isMove, silent)

Persisting json data in jstree through postback via asp:hiddenfield

I've been pouring over this for hours and I've yet to make much headway so I was hoping one of the wonderful denizens of SO could help me out. Here's the problem...
I'm implementing a tree via the jstree plugin for jQuery. I'm pulling the data with which I populate the tree programatically from our webapp via json dumped into an asp:HiddenField, basically like this:
JavaScriptSerializer serializer = new JavaScriptSerializer();
string json = serializer.Serialize(Items);
json = json.ToLower();
data.Value = json;
Then, the tree pulls the json from the hidden field to build itself. This works perfectly fine up until I try to persist data for which nodes are selected/opened. To simplify my problem I've hardcoded some json data into the tree and attempted to use the cookie plugin to persist the tree state data. This does not work for whatever reason. I've seen other issues where people need to load the plugins in a specific order, etc, this did not solve my issue. I tried the same setup with html_data and it works perfectly. With this working persistence I converted the cookie plugin to persist the data in a different asp:hiddenfield (we can't use cookies for this type of thing in our application.)
essentially the cookie operations are identical, it just saves the array of nodes as the value of a hidden field. This works with the html_data, still not with the json and I have yet to be able to put my finger on where it's failing.
This is the jQuery.cookie.js replacement:
jQuery.persist = function(name, value) {
if (typeof value != 'undefined') { // name and value given, set persist
if (value === null) {
value = '';
}
jQuery('#' + name).attr('value', value);
} else { // only name given, get value
var persistValue = null;
persistValue = jQuery('#' + name).attr('value');
return persistValue;
}
};
The jstree.cookie.js code is identical save for a few variable name changes.
And this is my tree:
$(function() {
$("#demo1").jstree({
"json_data": {
"data" : [
{
"data" : "A node",
"children" : [ "Child 1", "Child 2" ]
},
{
"attr": { "id": "li.node.id" },
"data" : {
"title": "li.node.id",
"attr": { "href": "#" }
},
"children": ["Child 1", "Child 2"]
}
]
},
"persistence": {
"save_opened": "<%= open.ClientID %>",
"save_selected": "<%= select.ClientID %>",
"auto_save": true
},
"plugins": ["themes", "ui", "persistence", "json_data"]
});
});
The data -is- being stored appropriately in the hiddenfields, the problem occurs on a postback, it does not reopen the nodes. Any help would be greatly appreciated.
After looking through this some more, I just wanted to explain that it appears to me that the issue is that the tree has not yet been built from the JSON_data when the persistence operations are being attempted. Is there any way to postpone these actions until after the tree is fully loaded?
If anyone is still attempting to perform the same type of operation on a jsTree version 3.0+ there is an easier way to accomplish the same type of functionality, without editing any of the jsTree's core JavaScript, and without relying on the "state" plugin (Version 1.0 - "Persistence"):
var jsTreeControl = $("#jsTreeControl");
//Can be a "asp:HiddenField"
var stateJSONControl = $("#stateJSONControl");
var url = "exampleURL";
jsTreeControl.jstree({
'core': {
"data": function (node, cb) {
var thisVar = this;
//On the initial load, if the "state" already exists in the hidden value
//then simply use that rather than make a AJAX call
if (stateJSONControl.val() !== "" && node.id === "#") {
cb.call(thisVar, { d: JSON.parse(stateJSONControl.val()) });
}
else {
$.ajax({
type: "POST",
url: url,
async: true,
success: function (json) {
cb.call(thisVar, json);
},
contentType: "application/json; charset=utf-8",
dataType: "json"
}).responseText;
}
}
}
});
//If the user changes the jsTree, save the full JSON of the jsTree into the hidden value,
//this will then be restored on postback by the "data" function in the jsTree decleration
jsTreeControl.on("changed.jstree", function (e, data) {
if (typeof (data.node) != 'undefined') {
stateJSONControl.val(JSON.stringify(jsTreeControl.jstree(true).get_json()));
}
});
This code will create a jsTree and save it's "state" into a hidden value, then upon postback when the jsTree is recreated, it will use its old "state" restored from the "HiddenField" rather than make a new AJAX call and lose the expansions/selections that the user has made.
Got it working properly with JSON data. I had to edit the "reopen" and "reselect" functions inside jstree itself.
Here's the new functioning reopen function for anyone who needs it.
reopen: function(is_callback) {
var _this = this,
done = true,
current = [],
remaining = [];
if (!is_callback) { this.data.core.reopen = false; this.data.core.refreshing = true; }
if (this.data.core.to_open.length) {
$.each(this.data.core.to_open, function(i, val) {
val = val.replace(/^#/, "")
if (val == "#") { return true; }
if ($(("li[id=" + val + "]")).length && $(("li[id=" + val + "]")).is(".jstree-closed")) { current.push($(("li[id=" + val + "]"))); }
else { remaining.push(val); }
});
if (current.length) {
this.data.core.to_open = remaining;
$.each(current, function(i, val) {
_this.open_node(val, function() { _this.reopen(true); }, true);
});
done = false;
}
}
if (done) {
// TODO: find a more elegant approach to syncronizing returning requests
if (this.data.core.reopen) { clearTimeout(this.data.core.reopen); }
this.data.core.reopen = setTimeout(function() { _this.__callback({}, _this); }, 50);
this.data.core.refreshing = false;
}
},
The problem was that it was trying to find the element by a custom attribute. It was just pushing these strings into the array to search when it was expecting node objects. Using this line
if ($(("li[id=" + val + "]")).length && $(("li[id=" + val + "]")).is(".jstree-closed")) { current.push($(("li[id=" + val + "]"))); }
instead of
if ($(val).length && $(val).is(".jstree-closed")) { current.push(val); }
was all it took. Using a similar process I was able to persist the selected nodes this way as well.
Hope this is of help to someone.

Resources