Skip to content

Commit

Permalink
Merge pull request adobe#10788 from humphd/issue10786
Browse files Browse the repository at this point in the history
Fix adobe#10786 - Allow QuickView image preview for arbitrary URLs
  • Loading branch information
nethip committed May 26, 2015
2 parents 3d68a76 + 3a09a13 commit 654e750
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 64 deletions.
159 changes: 97 additions & 62 deletions src/extensions/default/QuickView/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ define(function (require, exports, module) {
FileUtils = brackets.getModule("file/FileUtils"),
Menus = brackets.getModule("command/Menus"),
PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
LanguageManager = brackets.getModule("language/LanguageManager"),
Strings = brackets.getModule("strings"),
ViewUtils = brackets.getModule("utils/ViewUtils"),
TokenUtils = brackets.getModule("utils/TokenUtils");
Expand All @@ -48,7 +49,8 @@ define(function (require, exports, module) {
$previewContainer, // Preview container
$previewContent, // Preview content holder
lastMousePos, // Last mouse position
animationRequest; // Request for animation frame
animationRequest, // Request for animation frame
extensionlessImagePreview; // Whether to try and preview extensionless URLs

// Constants
var CMD_ENABLE_QUICK_VIEW = "view.enableQuickView",
Expand All @@ -60,6 +62,9 @@ define(function (require, exports, module) {

prefs = PreferencesManager.getExtensionPrefs("quickview");
prefs.definePreference("enabled", "boolean", true);
// Whether or not to try and show image previews for URLs missing extensions
// (e.g., https://avatars2.githubusercontent.com/u/476009?v=3&s=200)
prefs.definePreference("extensionlessImagePreview", "boolean", true);

/**
* There are three states for this var:
Expand Down Expand Up @@ -451,67 +456,83 @@ define(function (require, exports, module) {
}
}

if (tokenString) {
// Strip leading/trailing quotes, if present
tokenString = tokenString.replace(/(^['"])|(['"]$)/g, "");

if (/^(data\:image)|(\.gif|\.png|\.jpg|\.jpeg|\.webp|\.svg)$/i.test(tokenString)) {
var sPos, ePos;
var docPath = editor.document.file.fullPath;
var imgPath;

if (PathUtils.isAbsoluteUrl(tokenString)) {
imgPath = tokenString;
} else {
imgPath = "file:///" + FileUtils.getDirectoryPath(docPath) + tokenString;
}

if (urlMatch) {
sPos = {line: pos.line, ch: urlMatch.index};
ePos = {line: pos.line, ch: urlMatch.index + urlMatch[0].length};
} else {
sPos = {line: pos.line, ch: token.start};
ePos = {line: pos.line, ch: token.end};
}

if (imgPath) {
var imgPreview = "<div class='image-preview'>" +
" <img src=\"" + imgPath + "\">" +
"</div>";
var coord = cm.charCoords(sPos);
var xpos = (cm.charCoords(ePos).left - coord.left) / 2 + coord.left;

var showHandler = function () {
// Hide the preview container until the image is loaded.
$previewContainer.hide();


$previewContainer.find(".image-preview > img").on("load", function () {
$previewContent
.append("<div class='img-size'>" +
this.naturalWidth + " &times; " + this.naturalHeight + " " + Strings.UNIT_PIXELS +
"</div>"
);
$previewContainer.show();
positionPreview(editor, popoverState.xpos, popoverState.ytop, popoverState.ybot);
});
};

return {
start: sPos,
end: ePos,
content: imgPreview,
onShow: showHandler,
xpos: xpos,
ytop: coord.top,
ybot: coord.bottom,
_imgPath: imgPath
};
}
}
if (!tokenString) {
return null;
}

return null;

// Strip leading/trailing quotes, if present
tokenString = tokenString.replace(/(^['"])|(['"]$)/g, "");

var sPos, ePos;
var docPath = editor.document.file.fullPath;
var imgPath;

// Determine whether or not this URL/path is likely to be an image.
var parsed = PathUtils.parseUrl(tokenString);
var hasProtocol = parsed.protocol !== "";
var ext = parsed.filenameExtension.replace(/^\./, '');
var language = LanguageManager.getLanguageForExtension(ext);
var id = language && language.getId();
var isImage = id === "image" || id === "svg";

// Use this URL if this is an absolute URL and either points to a
// filename with a known image extension, or lacks an extension (e.g.,
// a web service that returns an image). Honour the extensionlessImagePreview
// preference as well in the latter case.
if (hasProtocol && (isImage || (!ext && extensionlessImagePreview))) {
imgPath = tokenString;
}
// Use this filename if this is a path with a known image extension.
else if (!hasProtocol && isImage) {
imgPath = "file:///" + FileUtils.getDirectoryPath(docPath) + tokenString;
}

if (!imgPath) {
return null;
}

if (urlMatch) {
sPos = {line: pos.line, ch: urlMatch.index};
ePos = {line: pos.line, ch: urlMatch.index + urlMatch[0].length};
} else {
sPos = {line: pos.line, ch: token.start};
ePos = {line: pos.line, ch: token.end};
}

var imgPreview = "<div class='image-preview'>" +
" <img src=\"" + imgPath + "\">" +
"</div>";
var coord = cm.charCoords(sPos);
var xpos = (cm.charCoords(ePos).left - coord.left) / 2 + coord.left;

var showHandler = function () {
// Hide the preview container until the image is loaded.
$previewContainer.hide();

$previewContainer.find(".image-preview > img").on("load", function () {
$previewContent
.append("<div class='img-size'>" +
this.naturalWidth + " &times; " + this.naturalHeight + " " + Strings.UNIT_PIXELS +
"</div>"
);
$previewContainer.show();
positionPreview(editor, popoverState.xpos, popoverState.ytop, popoverState.ybot);
}).on("error", function (e) {
e.preventDefault();
hidePreview();
});
};

return {
start: sPos,
end: ePos,
content: imgPreview,
onShow: showHandler,
xpos: xpos,
ytop: coord.top,
ybot: coord.bottom,
_imgPath: imgPath
};
}


Expand All @@ -523,7 +544,6 @@ define(function (require, exports, module) {
* Lacks only hoverTimer (supplied by handleMouseMove()) and marker (supplied by showPreview()).
*/
function queryPreviewProviders(editor, pos, token) {

var line = editor.document.getLine(pos.line);

// FUTURE: Support plugin providers. For now we just hard-code...
Expand Down Expand Up @@ -720,6 +740,16 @@ define(function (require, exports, module) {
CommandManager.get(CMD_ENABLE_QUICK_VIEW).setChecked(enabled);
}

function setExtensionlessImagePreview(_extensionlessImagePreview, doNotSave) {
if(extensionlessImagePreview !== _extensionlessImagePreview) {
extensionlessImagePreview = _extensionlessImagePreview;
if (!doNotSave) {
prefs.set("extensionlessImagePreview", enabled);
prefs.save();
}
}
}

function setEnabled(_enabled, doNotSave) {
if (enabled !== _enabled) {
enabled = _enabled;
Expand Down Expand Up @@ -787,10 +817,15 @@ define(function (require, exports, module) {

// Setup initial UI state
setEnabled(prefs.get("enabled"), true);
setExtensionlessImagePreview(prefs.get("extensionlessImagePreview"), true);

prefs.on("change", "enabled", function () {
setEnabled(prefs.get("enabled"), true);
});

prefs.on("change", "extensionlessImagePreview", function () {
setExtensionlessImagePreview(prefs.get("extensionlessImagePreview"));
});

// For unit testing
exports._queryPreviewProviders = queryPreviewProviders;
Expand Down
31 changes: 31 additions & 0 deletions src/extensions/default/QuickView/unittest-files/test.css
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,34 @@ background: -ms-linear-gradient(top, #d2dfed 0%,#c8d7eb 26%,#bed0ea 51%,#a6c0e3
background-image: linear-gradient(to bottom, #333, #CCC;
background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(51,51,51)), to(rgb(204,204,204));
}

.good-preview-image-urls {
background: "http://example.com/image.gif";
background: "http://example.com/image.png";
background: "http://example.com/image.jpe";
background: "http://example.com/image.jpeg";
background: "http://example.com/image.jpg";
background: "http://example.com/image.ico";
background: "http://example.com/image.bmp";
background: "http://example.com/image.svg";

background: "https://image.service.com/id/1234513";
background: "http://image.service.com/id/1234513";
background: "https://image.service.com/id/1234513?w=300&h=400";
}

.ignored-preview-image-urls {
background: "https://website.com/index.html";
background: "https://website.com/style.css";
background: "https://website.com/script.js";
background: "https://website.com/package.json";
background: "https://website.com/readme.md";
background: "https://website.com/data.xml";
background: "https://website.com/music.mp3";
background: "https://website.com/video.ogv";
background: "https://website.com/video.mp4";
background: "https://website.com/video.mpeg";
background: "https://website.com/video.webm";
background: "https://website.com/archive.zip";
background: "https://website.com/archive.tgz";
}
57 changes: 55 additions & 2 deletions src/extensions/default/QuickView/unittests.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
define(function (require, exports, module) {
"use strict";

var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"),
FileUtils = brackets.getModule("file/FileUtils");
var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"),
FileUtils = brackets.getModule("file/FileUtils"),
PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
prefs = PreferencesManager.getExtensionPrefs("quickview");

describe("Quick View", function () {
var testFolder = FileUtils.getNativeModuleDirectoryPath(module) + "/unittest-files/";
Expand Down Expand Up @@ -472,6 +474,57 @@ define(function (require, exports, module) {
checkImagePathAtPos("img/don't.png", 184, 26); // url("") containing '
checkImageDataAtPos("data:image/svg+xml;utf8, <svg version='1.1' xmlns='http://www.w3.org/2000/svg'></svg>", 185, 26); // data url("") containing '
});

it("Should show image preview for URLs with known image extensions", function() {
checkImageDataAtPos("http://example.com/image.gif", 194, 20);
checkImageDataAtPos("http://example.com/image.png", 195, 20);
checkImageDataAtPos("http://example.com/image.jpe", 196, 20);
checkImageDataAtPos("http://example.com/image.jpeg", 197, 20);
checkImageDataAtPos("http://example.com/image.jpg", 198, 20);
checkImageDataAtPos("http://example.com/image.ico", 199, 20);
checkImageDataAtPos("http://example.com/image.bmp", 200, 20);
checkImageDataAtPos("http://example.com/image.svg", 201, 20);
});

it("Should show image preview for extensionless URLs (with protocol) with pref set", function() {
// Flip the pref on and restore when done
var original = prefs.get("extensionlessImagePreview");
prefs.set("extensionlessImagePreview", true);

checkImageDataAtPos("https://image.service.com/id/1234513", 203, 20); // https
checkImageDataAtPos("http://image.service.com/id/1234513", 204, 20); // http
checkImageDataAtPos("https://image.service.com/id/1234513?w=300&h=400", 205, 20); // qs params

prefs.set("extensionlessImagePreview", original);
});

it("Should not show image preview for extensionless URLs (with protocol) without pref set", function() {
// Flip the pref off and restore when done
var original = prefs.get("extensionlessImagePreview");
prefs.set("extensionlessImagePreview", false);

checkImageDataAtPos("https://image.service.com/id/1234513", 203, 20); // https
checkImageDataAtPos("http://image.service.com/id/1234513", 204, 20); // http
checkImageDataAtPos("https://image.service.com/id/1234513?w=300&h=400", 205, 20); // qs params

prefs.set("extensionlessImagePreview", original);
});

it("Should ignore URLs for common non-image extensions", function() {
expectNoPreviewAtPos(209, 20); // .html
expectNoPreviewAtPos(210, 20); // .css
expectNoPreviewAtPos(211, 20); // .js
expectNoPreviewAtPos(212, 20); // .json
expectNoPreviewAtPos(213, 20); // .md
expectNoPreviewAtPos(214, 20); // .xml
expectNoPreviewAtPos(215, 20); // .mp3
expectNoPreviewAtPos(216, 20); // .ogv
expectNoPreviewAtPos(217, 20); // .mp4
expectNoPreviewAtPos(218, 20); // .mpeg
expectNoPreviewAtPos(219, 20); // .webm
expectNoPreviewAtPos(220, 20); // .zip
expectNoPreviewAtPos(221, 20); // .tgz
});

it("Should show image preview for a data URI inside url()", function () {
runs(function () {
Expand Down

0 comments on commit 654e750

Please sign in to comment.