Skip to content
This repository was archived by the owner on Sep 6, 2021. It is now read-only.

Commit 654e750

Browse files
committed
Merge pull request #10788 from humphd/issue10786
Fix #10786 - Allow QuickView image preview for arbitrary URLs
2 parents 3d68a76 + 3a09a13 commit 654e750

3 files changed

Lines changed: 183 additions & 64 deletions

File tree

src/extensions/default/QuickView/main.js

Lines changed: 97 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ define(function (require, exports, module) {
3737
FileUtils = brackets.getModule("file/FileUtils"),
3838
Menus = brackets.getModule("command/Menus"),
3939
PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
40+
LanguageManager = brackets.getModule("language/LanguageManager"),
4041
Strings = brackets.getModule("strings"),
4142
ViewUtils = brackets.getModule("utils/ViewUtils"),
4243
TokenUtils = brackets.getModule("utils/TokenUtils");
@@ -48,7 +49,8 @@ define(function (require, exports, module) {
4849
$previewContainer, // Preview container
4950
$previewContent, // Preview content holder
5051
lastMousePos, // Last mouse position
51-
animationRequest; // Request for animation frame
52+
animationRequest, // Request for animation frame
53+
extensionlessImagePreview; // Whether to try and preview extensionless URLs
5254

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

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

6469
/**
6570
* There are three states for this var:
@@ -451,67 +456,83 @@ define(function (require, exports, module) {
451456
}
452457
}
453458

454-
if (tokenString) {
455-
// Strip leading/trailing quotes, if present
456-
tokenString = tokenString.replace(/(^['"])|(['"]$)/g, "");
457-
458-
if (/^(data\:image)|(\.gif|\.png|\.jpg|\.jpeg|\.webp|\.svg)$/i.test(tokenString)) {
459-
var sPos, ePos;
460-
var docPath = editor.document.file.fullPath;
461-
var imgPath;
462-
463-
if (PathUtils.isAbsoluteUrl(tokenString)) {
464-
imgPath = tokenString;
465-
} else {
466-
imgPath = "file:///" + FileUtils.getDirectoryPath(docPath) + tokenString;
467-
}
468-
469-
if (urlMatch) {
470-
sPos = {line: pos.line, ch: urlMatch.index};
471-
ePos = {line: pos.line, ch: urlMatch.index + urlMatch[0].length};
472-
} else {
473-
sPos = {line: pos.line, ch: token.start};
474-
ePos = {line: pos.line, ch: token.end};
475-
}
476-
477-
if (imgPath) {
478-
var imgPreview = "<div class='image-preview'>" +
479-
" <img src=\"" + imgPath + "\">" +
480-
"</div>";
481-
var coord = cm.charCoords(sPos);
482-
var xpos = (cm.charCoords(ePos).left - coord.left) / 2 + coord.left;
483-
484-
var showHandler = function () {
485-
// Hide the preview container until the image is loaded.
486-
$previewContainer.hide();
487-
488-
489-
$previewContainer.find(".image-preview > img").on("load", function () {
490-
$previewContent
491-
.append("<div class='img-size'>" +
492-
this.naturalWidth + " &times; " + this.naturalHeight + " " + Strings.UNIT_PIXELS +
493-
"</div>"
494-
);
495-
$previewContainer.show();
496-
positionPreview(editor, popoverState.xpos, popoverState.ytop, popoverState.ybot);
497-
});
498-
};
499-
500-
return {
501-
start: sPos,
502-
end: ePos,
503-
content: imgPreview,
504-
onShow: showHandler,
505-
xpos: xpos,
506-
ytop: coord.top,
507-
ybot: coord.bottom,
508-
_imgPath: imgPath
509-
};
510-
}
511-
}
459+
if (!tokenString) {
460+
return null;
512461
}
513-
514-
return null;
462+
463+
// Strip leading/trailing quotes, if present
464+
tokenString = tokenString.replace(/(^['"])|(['"]$)/g, "");
465+
466+
var sPos, ePos;
467+
var docPath = editor.document.file.fullPath;
468+
var imgPath;
469+
470+
// Determine whether or not this URL/path is likely to be an image.
471+
var parsed = PathUtils.parseUrl(tokenString);
472+
var hasProtocol = parsed.protocol !== "";
473+
var ext = parsed.filenameExtension.replace(/^\./, '');
474+
var language = LanguageManager.getLanguageForExtension(ext);
475+
var id = language && language.getId();
476+
var isImage = id === "image" || id === "svg";
477+
478+
// Use this URL if this is an absolute URL and either points to a
479+
// filename with a known image extension, or lacks an extension (e.g.,
480+
// a web service that returns an image). Honour the extensionlessImagePreview
481+
// preference as well in the latter case.
482+
if (hasProtocol && (isImage || (!ext && extensionlessImagePreview))) {
483+
imgPath = tokenString;
484+
}
485+
// Use this filename if this is a path with a known image extension.
486+
else if (!hasProtocol && isImage) {
487+
imgPath = "file:///" + FileUtils.getDirectoryPath(docPath) + tokenString;
488+
}
489+
490+
if (!imgPath) {
491+
return null;
492+
}
493+
494+
if (urlMatch) {
495+
sPos = {line: pos.line, ch: urlMatch.index};
496+
ePos = {line: pos.line, ch: urlMatch.index + urlMatch[0].length};
497+
} else {
498+
sPos = {line: pos.line, ch: token.start};
499+
ePos = {line: pos.line, ch: token.end};
500+
}
501+
502+
var imgPreview = "<div class='image-preview'>" +
503+
" <img src=\"" + imgPath + "\">" +
504+
"</div>";
505+
var coord = cm.charCoords(sPos);
506+
var xpos = (cm.charCoords(ePos).left - coord.left) / 2 + coord.left;
507+
508+
var showHandler = function () {
509+
// Hide the preview container until the image is loaded.
510+
$previewContainer.hide();
511+
512+
$previewContainer.find(".image-preview > img").on("load", function () {
513+
$previewContent
514+
.append("<div class='img-size'>" +
515+
this.naturalWidth + " &times; " + this.naturalHeight + " " + Strings.UNIT_PIXELS +
516+
"</div>"
517+
);
518+
$previewContainer.show();
519+
positionPreview(editor, popoverState.xpos, popoverState.ytop, popoverState.ybot);
520+
}).on("error", function (e) {
521+
e.preventDefault();
522+
hidePreview();
523+
});
524+
};
525+
526+
return {
527+
start: sPos,
528+
end: ePos,
529+
content: imgPreview,
530+
onShow: showHandler,
531+
xpos: xpos,
532+
ytop: coord.top,
533+
ybot: coord.bottom,
534+
_imgPath: imgPath
535+
};
515536
}
516537

517538

@@ -523,7 +544,6 @@ define(function (require, exports, module) {
523544
* Lacks only hoverTimer (supplied by handleMouseMove()) and marker (supplied by showPreview()).
524545
*/
525546
function queryPreviewProviders(editor, pos, token) {
526-
527547
var line = editor.document.getLine(pos.line);
528548

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

743+
function setExtensionlessImagePreview(_extensionlessImagePreview, doNotSave) {
744+
if(extensionlessImagePreview !== _extensionlessImagePreview) {
745+
extensionlessImagePreview = _extensionlessImagePreview;
746+
if (!doNotSave) {
747+
prefs.set("extensionlessImagePreview", enabled);
748+
prefs.save();
749+
}
750+
}
751+
}
752+
723753
function setEnabled(_enabled, doNotSave) {
724754
if (enabled !== _enabled) {
725755
enabled = _enabled;
@@ -787,10 +817,15 @@ define(function (require, exports, module) {
787817

788818
// Setup initial UI state
789819
setEnabled(prefs.get("enabled"), true);
820+
setExtensionlessImagePreview(prefs.get("extensionlessImagePreview"), true);
790821

791822
prefs.on("change", "enabled", function () {
792823
setEnabled(prefs.get("enabled"), true);
793824
});
825+
826+
prefs.on("change", "extensionlessImagePreview", function () {
827+
setExtensionlessImagePreview(prefs.get("extensionlessImagePreview"));
828+
});
794829

795830
// For unit testing
796831
exports._queryPreviewProviders = queryPreviewProviders;

src/extensions/default/QuickView/unittest-files/test.css

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,34 @@ background: -ms-linear-gradient(top, #d2dfed 0%,#c8d7eb 26%,#bed0ea 51%,#a6c0e3
190190
background-image: linear-gradient(to bottom, #333, #CCC;
191191
background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(51,51,51)), to(rgb(204,204,204));
192192
}
193+
194+
.good-preview-image-urls {
195+
background: "http://example.com/image.gif";
196+
background: "http://example.com/image.png";
197+
background: "http://example.com/image.jpe";
198+
background: "http://example.com/image.jpeg";
199+
background: "http://example.com/image.jpg";
200+
background: "http://example.com/image.ico";
201+
background: "http://example.com/image.bmp";
202+
background: "http://example.com/image.svg";
203+
204+
background: "https://image.service.com/id/1234513";
205+
background: "http://image.service.com/id/1234513";
206+
background: "https://image.service.com/id/1234513?w=300&h=400";
207+
}
208+
209+
.ignored-preview-image-urls {
210+
background: "https://website.com/index.html";
211+
background: "https://website.com/style.css";
212+
background: "https://website.com/script.js";
213+
background: "https://website.com/package.json";
214+
background: "https://website.com/readme.md";
215+
background: "https://website.com/data.xml";
216+
background: "https://website.com/music.mp3";
217+
background: "https://website.com/video.ogv";
218+
background: "https://website.com/video.mp4";
219+
background: "https://website.com/video.mpeg";
220+
background: "https://website.com/video.webm";
221+
background: "https://website.com/archive.zip";
222+
background: "https://website.com/archive.tgz";
223+
}

src/extensions/default/QuickView/unittests.js

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727
define(function (require, exports, module) {
2828
"use strict";
2929

30-
var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"),
31-
FileUtils = brackets.getModule("file/FileUtils");
30+
var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"),
31+
FileUtils = brackets.getModule("file/FileUtils"),
32+
PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
33+
prefs = PreferencesManager.getExtensionPrefs("quickview");
3234

3335
describe("Quick View", function () {
3436
var testFolder = FileUtils.getNativeModuleDirectoryPath(module) + "/unittest-files/";
@@ -472,6 +474,57 @@ define(function (require, exports, module) {
472474
checkImagePathAtPos("img/don't.png", 184, 26); // url("") containing '
473475
checkImageDataAtPos("data:image/svg+xml;utf8, <svg version='1.1' xmlns='http://www.w3.org/2000/svg'></svg>", 185, 26); // data url("") containing '
474476
});
477+
478+
it("Should show image preview for URLs with known image extensions", function() {
479+
checkImageDataAtPos("http://example.com/image.gif", 194, 20);
480+
checkImageDataAtPos("http://example.com/image.png", 195, 20);
481+
checkImageDataAtPos("http://example.com/image.jpe", 196, 20);
482+
checkImageDataAtPos("http://example.com/image.jpeg", 197, 20);
483+
checkImageDataAtPos("http://example.com/image.jpg", 198, 20);
484+
checkImageDataAtPos("http://example.com/image.ico", 199, 20);
485+
checkImageDataAtPos("http://example.com/image.bmp", 200, 20);
486+
checkImageDataAtPos("http://example.com/image.svg", 201, 20);
487+
});
488+
489+
it("Should show image preview for extensionless URLs (with protocol) with pref set", function() {
490+
// Flip the pref on and restore when done
491+
var original = prefs.get("extensionlessImagePreview");
492+
prefs.set("extensionlessImagePreview", true);
493+
494+
checkImageDataAtPos("https://image.service.com/id/1234513", 203, 20); // https
495+
checkImageDataAtPos("http://image.service.com/id/1234513", 204, 20); // http
496+
checkImageDataAtPos("https://image.service.com/id/1234513?w=300&h=400", 205, 20); // qs params
497+
498+
prefs.set("extensionlessImagePreview", original);
499+
});
500+
501+
it("Should not show image preview for extensionless URLs (with protocol) without pref set", function() {
502+
// Flip the pref off and restore when done
503+
var original = prefs.get("extensionlessImagePreview");
504+
prefs.set("extensionlessImagePreview", false);
505+
506+
checkImageDataAtPos("https://image.service.com/id/1234513", 203, 20); // https
507+
checkImageDataAtPos("http://image.service.com/id/1234513", 204, 20); // http
508+
checkImageDataAtPos("https://image.service.com/id/1234513?w=300&h=400", 205, 20); // qs params
509+
510+
prefs.set("extensionlessImagePreview", original);
511+
});
512+
513+
it("Should ignore URLs for common non-image extensions", function() {
514+
expectNoPreviewAtPos(209, 20); // .html
515+
expectNoPreviewAtPos(210, 20); // .css
516+
expectNoPreviewAtPos(211, 20); // .js
517+
expectNoPreviewAtPos(212, 20); // .json
518+
expectNoPreviewAtPos(213, 20); // .md
519+
expectNoPreviewAtPos(214, 20); // .xml
520+
expectNoPreviewAtPos(215, 20); // .mp3
521+
expectNoPreviewAtPos(216, 20); // .ogv
522+
expectNoPreviewAtPos(217, 20); // .mp4
523+
expectNoPreviewAtPos(218, 20); // .mpeg
524+
expectNoPreviewAtPos(219, 20); // .webm
525+
expectNoPreviewAtPos(220, 20); // .zip
526+
expectNoPreviewAtPos(221, 20); // .tgz
527+
});
475528

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

0 commit comments

Comments
 (0)