From d2bfb53d986f9fde5919c4ccab1cc6235f4e381e Mon Sep 17 00:00:00 2001 From: "David Humphrey (:humph) david.humphrey@senecacollege.ca" Date: Mon, 23 Mar 2015 23:07:23 -0400 Subject: [PATCH 1/3] Fix #10786 - Allow QuickView image preview for arbitrary URLs --- .../JavaScriptCodeHints/thirdparty/acorn | 2 +- .../JavaScriptCodeHints/thirdparty/tern | 2 +- src/extensions/default/QuickView/main.js | 134 ++++++++++-------- 3 files changed, 76 insertions(+), 62 deletions(-) diff --git a/src/extensions/default/JavaScriptCodeHints/thirdparty/acorn b/src/extensions/default/JavaScriptCodeHints/thirdparty/acorn index 78e1d7ada68..4435013900b 160000 --- a/src/extensions/default/JavaScriptCodeHints/thirdparty/acorn +++ b/src/extensions/default/JavaScriptCodeHints/thirdparty/acorn @@ -1 +1 @@ -Subproject commit 78e1d7ada684868d92d09555408ae2df1812b5ae +Subproject commit 4435013900b329797224a4e94acc0934e28acc4a diff --git a/src/extensions/default/JavaScriptCodeHints/thirdparty/tern b/src/extensions/default/JavaScriptCodeHints/thirdparty/tern index 7606a6448a8..ed952deb43d 160000 --- a/src/extensions/default/JavaScriptCodeHints/thirdparty/tern +++ b/src/extensions/default/JavaScriptCodeHints/thirdparty/tern @@ -1 +1 @@ -Subproject commit 7606a6448a8f7a2aacd50d10d9752440689803e8 +Subproject commit ed952deb43d3fc095038763cf777ee61d8e7665a diff --git a/src/extensions/default/QuickView/main.js b/src/extensions/default/QuickView/main.js index 7ac6bb9ee48..302cded10ab 100644 --- a/src/extensions/default/QuickView/main.js +++ b/src/extensions/default/QuickView/main.js @@ -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"); @@ -450,67 +451,80 @@ 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 = "
" + - " " + - "
"; - 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("
" + - this.naturalWidth + " × " + this.naturalHeight + " " + Strings.UNIT_PIXELS + - "
" - ); - $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). + if (hasProtocol && (isImage || !ext)) { + 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; + } else { + 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 = "
" + + " " + + "
"; + 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("
" + + this.naturalWidth + " × " + this.naturalHeight + " " + Strings.UNIT_PIXELS + + "
" + ); + $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 + }; } From 83eb27629eba9dc4659690a75877145f5c850e0f Mon Sep 17 00:00:00 2001 From: "David Humphrey (:humph) david.humphrey@senecacollege.ca" Date: Thu, 30 Apr 2015 15:06:50 -0400 Subject: [PATCH 2/3] Add pref for extensionless preview, add tests. --- src/extensions/default/QuickView/main.js | 29 ++++++++-- .../default/QuickView/unittest-files/test.css | 31 ++++++++++ src/extensions/default/QuickView/unittests.js | 57 ++++++++++++++++++- 3 files changed, 110 insertions(+), 7 deletions(-) diff --git a/src/extensions/default/QuickView/main.js b/src/extensions/default/QuickView/main.js index 302cded10ab..01c252c7513 100644 --- a/src/extensions/default/QuickView/main.js +++ b/src/extensions/default/QuickView/main.js @@ -49,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", @@ -61,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: @@ -472,14 +476,17 @@ define(function (require, exports, module) { // 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). - if (hasProtocol && (isImage || !ext)) { + // 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; - } else { + } + + if (!imgPath) { return null; } @@ -536,7 +543,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... @@ -733,6 +739,14 @@ define(function (require, exports, module) { CommandManager.get(CMD_ENABLE_QUICK_VIEW).setChecked(enabled); } + function setExtensionlessImagePreview(_extensionlessImagePreview) { + if(extensionlessImagePreview !== _extensionlessImagePreview) { + extensionlessImagePreview = _extensionlessImagePreview; + prefs.set("extensionlessImagePreview", enabled); + prefs.save(); + } + } + function setEnabled(_enabled, doNotSave) { if (enabled !== _enabled) { enabled = _enabled; @@ -800,10 +814,15 @@ define(function (require, exports, module) { // Setup initial UI state setEnabled(prefs.get("enabled"), true); + setExtensionlessImagePreview(prefs.get("extensionlessImagePreview")); 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; diff --git a/src/extensions/default/QuickView/unittest-files/test.css b/src/extensions/default/QuickView/unittest-files/test.css index 9324dd21172..b40c2ffc0d4 100644 --- a/src/extensions/default/QuickView/unittest-files/test.css +++ b/src/extensions/default/QuickView/unittest-files/test.css @@ -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"; +} diff --git a/src/extensions/default/QuickView/unittests.js b/src/extensions/default/QuickView/unittests.js index 39c78518cab..261b7dd52bf 100644 --- a/src/extensions/default/QuickView/unittests.js +++ b/src/extensions/default/QuickView/unittests.js @@ -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/"; @@ -472,6 +474,57 @@ define(function (require, exports, module) { checkImagePathAtPos("img/don't.png", 184, 26); // url("") containing ' checkImageDataAtPos("data:image/svg+xml;utf8, ", 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 () { From 3a09a13be7a335aca2298771d85e5682b6013717 Mon Sep 17 00:00:00 2001 From: "David Humphrey (:humph) david.humphrey@senecacollege.ca" Date: Mon, 4 May 2015 21:32:37 -0400 Subject: [PATCH 3/3] Don't write pref on initial load, per review --- .../default/JavaScriptCodeHints/thirdparty/acorn | 2 +- .../default/JavaScriptCodeHints/thirdparty/tern | 2 +- src/extensions/default/QuickView/main.js | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/extensions/default/JavaScriptCodeHints/thirdparty/acorn b/src/extensions/default/JavaScriptCodeHints/thirdparty/acorn index 4435013900b..78e1d7ada68 160000 --- a/src/extensions/default/JavaScriptCodeHints/thirdparty/acorn +++ b/src/extensions/default/JavaScriptCodeHints/thirdparty/acorn @@ -1 +1 @@ -Subproject commit 4435013900b329797224a4e94acc0934e28acc4a +Subproject commit 78e1d7ada684868d92d09555408ae2df1812b5ae diff --git a/src/extensions/default/JavaScriptCodeHints/thirdparty/tern b/src/extensions/default/JavaScriptCodeHints/thirdparty/tern index ed952deb43d..7606a6448a8 160000 --- a/src/extensions/default/JavaScriptCodeHints/thirdparty/tern +++ b/src/extensions/default/JavaScriptCodeHints/thirdparty/tern @@ -1 +1 @@ -Subproject commit ed952deb43d3fc095038763cf777ee61d8e7665a +Subproject commit 7606a6448a8f7a2aacd50d10d9752440689803e8 diff --git a/src/extensions/default/QuickView/main.js b/src/extensions/default/QuickView/main.js index 01c252c7513..912399bfe11 100644 --- a/src/extensions/default/QuickView/main.js +++ b/src/extensions/default/QuickView/main.js @@ -739,11 +739,13 @@ define(function (require, exports, module) { CommandManager.get(CMD_ENABLE_QUICK_VIEW).setChecked(enabled); } - function setExtensionlessImagePreview(_extensionlessImagePreview) { + function setExtensionlessImagePreview(_extensionlessImagePreview, doNotSave) { if(extensionlessImagePreview !== _extensionlessImagePreview) { extensionlessImagePreview = _extensionlessImagePreview; - prefs.set("extensionlessImagePreview", enabled); - prefs.save(); + if (!doNotSave) { + prefs.set("extensionlessImagePreview", enabled); + prefs.save(); + } } } @@ -814,7 +816,7 @@ define(function (require, exports, module) { // Setup initial UI state setEnabled(prefs.get("enabled"), true); - setExtensionlessImagePreview(prefs.get("extensionlessImagePreview")); + setExtensionlessImagePreview(prefs.get("extensionlessImagePreview"), true); prefs.on("change", "enabled", function () { setEnabled(prefs.get("enabled"), true);