Skip to content

Commit

Permalink
Reorganize repo, make things mostly work in Chrome
Browse files Browse the repository at this point in the history
  • Loading branch information
bhollis committed May 8, 2018
1 parent 4c33938 commit 4d0df08
Show file tree
Hide file tree
Showing 41 changed files with 183 additions and 56 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*.xpi
*.zip
.tern-port
/ts-out
/build
/main.js
/node_modules

30 changes: 10 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
JSONView
========

[JSONView](http://jsonview.com) is a Firefox extension that helps you view JSON documents in the browser.
[JSONView](http://jsonview.com) is a Web extension compatible with Firefox and Google Chrome that helps you view JSON documents in the browser.

Normally when encountering a [JSON](http://json.org) document (content type `application/json`), Firefox simply prompts you to download the view. With the JSONView extension, JSON documents are shown in the browser similar to how XML documents are shown. The document is formatted, highlighted, and arrays and objects can be collapsed. Even if the JSON document contains errors, JSONView will still show the raw text.

Once you've got JSONView installed, check out [this example JSON file](http://jsonview.com/example.json) to see the extension in action!

[CouchDB](http://couchdb.apache.org/) users and others who need to have `application/json` sent in the HTTP Accept header to serve JSON properly should set that option in JSONView's options panel. Be aware that telling sites that you accept JSON can mess up some sites that don't expect it.

If you want to inspect a JSON snippet from your clipboard you can do so by entering `data:application/json,<paste the json document>` in the URL bar.

If you'd like to contribute to JSONView but don't want to code, consider contributing a translation. Copy the existing localization files from `locale` and `src/locale` and fill them in for your own language, then send a pull request. You can do it all from the GitHub interface. There's not much there to translate!
If you'd like to contribute to JSONView but don't want to code, consider contributing a translation. Copy the existing localization files from `src/_locale` and fill them in for your own language, then send a pull request. You can do it all from the GitHub interface. There's not many strings to translate!

Keyboard Shortcuts
----------------
Expand All @@ -27,26 +23,20 @@ Use the GitHub [Issue tracker for JSONView](https://github.com/bhollis/jsonview/
Developing JSONView
-------------------

Before contributing to JSONView, make sure to read the [Contributing Guidelines](CONTRIBUTING.md). I appreciate contributions people make to JSONView, but the goal of the add-on is to be simple and straightforward, so I frequently reject contributions that add complexity or unnecessary features. Please consider filing an issue before doing any work, so you don't waste time on something I won't accept.
Before contributing to JSONView, make sure to read the [Contributing Guidelines](CONTRIBUTING.md). I appreciate contributions people make to JSONView, but the goal of the extension is to be simple and straightforward, so I frequently reject contributions that add complexity or unnecessary features. Please consider filing an issue before doing any work, so you don't waste time on something I won't accept.

* Install [Add-on SDK](https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/jpm#Installation)
* Install [Firefox Nightly](https://nightly.mozilla.org/) - the regular Firefox won't run unsigned extensions.
* Run `jpm run -b Nightly` to test in Firefox Nightly.
* Run `jpm xpi` to create an xpi.
* Install [NodeJS](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/en/docs/install).
* Check out jsonview.
* Run `yarn` inside the jsonview repository.
* Run `yarn start` to build the extension.
* In Firefox, go to `about:debugging#addons` in the address bar, check "Enable add-on debugging", select "Load Temporary Add-on", and choose the `jsonview/build` folder.
* In Chrome, go to `chrome://extensions/` in the address bar, select "Load Unpacked", and choose the `jsonview/build` folder.

The build script also comes with a HTTP server which can be used to test JSON files in `tests` folder. To start the integrated server, listening on port 8000, along with Firefox, run `python build.py -b 8000 run`.

Unofficial Ports
----------------
* [jsonview-chrome](https://github.com/jamiew/jsonview-chrome)
* [jsonview-opera](https://github.com/fearphage/jsonview-opera)
* [jsonview-safari](https://github.com/acrogenesis/jsonview-safari)
JSONView makes use of [TypeScript](https://www.typescriptlang.org/). I recommend [VSCode](https://code.visualstudio.com/) for editing the code - it will automatically prompt to install the correct extensions, and will highlight errors.

Common Issues
-------------
* **JSONView isn't displaying my file as JSON**: You are probably not serving
the JSON with the "application/json" MIME type.
* **JSONView is mangling large numbers**:
[Here's the explanation](https://github.com/bhollis/jsonview/issues/21).

JSONView is open source software under the MIT licence.
19 changes: 19 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/sh -e

tsc

mkdir -p build

rollup ts-out/background.js --format iife --name 'background' --file build/background.js
rollup ts-out/content.js --format iife --name 'background' --file build/content.js
rollup ts-out/viewer.js --format iife --name 'background' --file build/viewer.js
cp src/viewer.css build/viewer.css
cp src/manifest.json build/manifest.json
cp license.txt build/license.txt
cp -r src/_locales build
cp src/icon*.png build

rm jsonview.zip
pushd build
zip -r ../jsonview.zip *
popd
Binary file removed icon.png
Binary file not shown.
Binary file removed icon256.png
Binary file not shown.
Binary file removed icon64.png
Binary file not shown.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"repository": "https://github.com/bhollis/jsonview",
"license": "MIT",
"scripts": {
"start": "tsc && rollup build/main.js --format iife --name 'main' --file main.js",
"start": "./build.sh",
"lint": "tslint -p tsconfig.json",
"watch": "npm-watch"
},
Expand Down Expand Up @@ -108,6 +108,7 @@
}
},
"devDependencies": {
"@types/chrome": "^0.0.65",
"npm-watch": "^0.3.0",
"rollup": "^0.58.2",
"tslint": "^5.10.0",
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
46 changes: 22 additions & 24 deletions lib/main.ts → src/background.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,16 @@
import { jsonToHTML, errorPage } from './jsonformatter';
import { safeStringEncodeNums } from './safe-encode-numbers';

declare const chrome: typeof browser;
console.log("JSONView init!");

// const jsonRequestIds = new Set<string>();
const jsonUrls = new Set<string>();

// TODO: Do something for chrome in concert with a content script?
// Or maybe save the content off somehow and load it up again in a redirected page

interface RequestDetails {
requestId: string;
url: string;
method: string;
frameId: number;
parentFrameId: number;
tabId: number;
type: browser.webRequest.ResourceType;
timeStamp: number;
originUrl: string;
statusLine: string;
responseHeaders?: browser.webRequest.HttpHeaders;
statusCode: number;
}

function listener(details: RequestDetails) {
function transformResponseToJSON(details: chrome.webRequest.WebResponseHeadersDetails) {
// const requestId = details.requestId;
const filter = chrome.webRequest.filterResponseData(details.requestId);
const filter = browser.webRequest.filterResponseData(details.requestId);

// TODO: figure out encoding I guess
const dec = new TextDecoder("utf-8");
Expand Down Expand Up @@ -58,18 +43,21 @@ function listener(details: RequestDetails) {
};
}

function detectJSON(event: RequestDetails) {
function detectJSON(event: chrome.webRequest.WebResponseHeadersDetails) {
console.log("JSONView headers", event);
if (!event.responseHeaders) {
return;
}
for (const header of event.responseHeaders) {
// TODO: look for weird x+json types
if (header.name === "Content-Type" && header.value && header.value.includes("application/json")) {
header.value = "text/html";
// TODO: this could be for chrome
// jsonRequestIds.add(event.requestId);
listener(event);
console.log("JSONView found JSON!");
if (typeof browser !== 'undefined' && 'filterResponseData' in browser.webRequest) {
header.value = "text/html";
transformResponseToJSON(event);
} else {
jsonUrls.add(event.url);
}
}
}

Expand All @@ -84,4 +72,14 @@ chrome.webRequest.onHeadersReceived.addListener(
["blocking", "responseHeaders"]
);

chrome.runtime.onMessage.addListener((message: any, sender: { url: string }, sendResponse: (response: any) => void) => {
if ('filterResponseData' in chrome.webRequest) {
sendResponse(false);
return;
}
console.log("JSONView message: ", message, sender);
sendResponse(jsonUrls.has(sender.url));
jsonUrls.delete(sender.url);
});

// TODO: as far as I can tell, there's no way to intercept local files
61 changes: 61 additions & 0 deletions src/collapse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

/**
* Add event handlers that allow for collapsing and expanding JSON structures, with the mouse or keyboard.
*/
export function installCollapseEventListeners() {
// Click handler for collapsing and expanding objects and arrays
function collapse(evt: Event) {
let collapser = evt.target as any;

while (collapser && (!collapser.classList || !collapser.classList.contains('collapser'))) {
collapser = collapser.nextSibling;
}
if (!collapser || !collapser.classList || !collapser.classList.contains('collapser')) {
return;
}

evt.stopPropagation();

collapser.classList.toggle('collapsed');

let collapsible = collapser;
while (collapsible && (!collapsible.classList || !collapsible.classList.contains('collapsible'))) {
collapsible = collapsible.nextSibling;
}
collapsible.classList.toggle('collapsed');
}

/*
* Collapses the whole json using keyboard
* TODO: Add a navigator support for each of the elements
*/
function collapseAll(evt: KeyboardEvent) {
let inputList;
let i;

// Ignore anything paired with a modifier key. See https://github.com/bhollis/jsonview/issues/69
if (evt.ctrlKey || evt.shiftKey || evt.altKey || evt.metaKey) {
return;
}

if (evt.keyCode === 37) { // Collapses the json on left arrow key up
inputList = document.querySelectorAll('.collapsible, .collapser');
for (i = 0; i < inputList.length; i++) {
if ((inputList[i].parentNode! as HTMLElement).id !== 'json') {
inputList[i].classList.add('collapsed');
}
}
evt.preventDefault();
} else if (evt.keyCode === 39) { // Expands the json on right arrow key up
inputList = document.querySelectorAll('.collapsed');
for (i = 0; i < inputList.length; i++) {
inputList[i].classList.remove('collapsed');
}
evt.preventDefault();
}
}

// collapse with event delegation
document.addEventListener('click', collapse, false);
document.addEventListener('keyup', collapseAll, false);
}
22 changes: 22 additions & 0 deletions src/content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { errorPage, jsonToHTML } from './jsonformatter';
import { safeStringEncodeNums } from './safe-encode-numbers';
import { installCollapseEventListeners } from './collapse';

chrome.runtime.sendMessage({}, (response: boolean) => {
if (!response) {
return;
}

const content = document.getElementsByTagName('pre')[0].innerText;
let outputDoc = '';

try {
const jsonObj = JSON.parse(safeStringEncodeNums(content));
outputDoc = jsonToHTML(jsonObj, document.URL);
} catch (e) {
outputDoc = errorPage(e, content, document.URL);
}

document.documentElement.innerHTML = outputDoc;
installCollapseEventListeners();
});
Binary file added src/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/icon128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/icon256.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/icon64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 15 additions & 5 deletions lib/jsonformatter.ts → src/jsonformatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,33 @@

/** Convert a whole JSON value / JSONP response into a formatted HTML document */
export function jsonToHTML(json: any, uri: string) {
return toHTML(jsonToHTMLBody(json), uri);
}

/** Convert a whole JSON value / JSONP response into an HTML body, without title and scripts */
export function jsonToHTMLBody(json: any) {
const output = `<div id="json">${valueToHTML(json, '<root>')}</div>`;
return toHTML(output, uri);
return output;
}

/** Produce an error document for when parsing fails. */
export function errorPage(error: Error, data: string, uri: string) {
return toHTML(errorPageBody(error, data), uri + ' - Error');
}

/** Produce an error content for when parsing fails. */
export function errorPageBody(error: Error, data: string) {
// Escape unicode nulls
data = data.replace("\u0000", "\uFFFD");

const errorInfo = massageError(error);

let output = `<div id="error">${browser.i18n.getMessage('errorParsing')}`;
let output = `<div id="error">${chrome.i18n.getMessage('errorParsing')}`;
if (errorInfo.message) {
output += `<div class="errormessage">${errorInfo.message}</div>`;
}
output += `</div><div id="json">${highlightError(data, errorInfo.line, errorInfo.column)}</div>`;
return toHTML(output, uri + ' - Error');
return output;
}

/**
Expand Down Expand Up @@ -185,8 +195,8 @@ function highlightError(data: string, lineNum?: number, columnNum?: number) {
function toHTML(content: string, title: string) {
return `<!DOCTYPE html>
<html><head><title>${htmlEncode(title)}</title>
<link rel="stylesheet" type="text/css" href="${browser.extension.getURL("data/default.css")}">
<script type="text/javascript" src="${browser.extension.getURL("data/default.js")}"></script>
<link rel="stylesheet" type="text/css" href="${chrome.runtime.getURL("data/default.css")}">
<script type="text/javascript" src="${chrome.runtime.getURL("data/default.js")}"></script>
</head><body>
${content}
</body></html>`;
Expand Down
12 changes: 9 additions & 3 deletions manifest.json → src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@
"256": "icon256.png"
},
"background": {
"scripts": ["main.js"]
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"permissions": [
"<all_urls>",
"webRequest",
Expand All @@ -24,8 +30,8 @@
}
},
"web_accessible_resources": [
"data/default.css",
"data/default.js"
"viewer.css",
"viewer.js"
],
"default_locale": "en"
}
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions src/viewer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { installCollapseEventListeners } from "./collapse";

document.addEventListener('DOMContentLoaded', installCollapseEventListeners, false);
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"outDir": "./build/",
"outDir": "./ts-out/",
"sourceMap": true,
"strictNullChecks": true,
"noImplicitAny": true,
Expand All @@ -14,6 +14,6 @@
"typeRoots": ["node_modules/@types", "node_modules/web-ext-types"]
},
"include": [
"./lib/**"
"./src/*.ts"
]
}
16 changes: 16 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,26 @@
# yarn lockfile v1


"@types/chrome@^0.0.65":
version "0.0.65"
resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.65.tgz#bd19a4cff5a7f91ce5e487cfb0c7cca59271a053"
dependencies:
"@types/filesystem" "*"

"@types/estree@0.0.38":
version "0.0.38"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.38.tgz#c1be40aa933723c608820a99a373a16d215a1ca2"

"@types/filesystem@*":
version "0.0.28"
resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.28.tgz#3fd7735830f2c7413cb5ac45780bc45904697b0e"
dependencies:
"@types/filewriter" "*"

"@types/filewriter@*":
version "0.0.28"
resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.28.tgz#c054e8af4d9dd75db4e63abc76f885168714d4b3"

"@types/node@*":
version "10.0.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.0.3.tgz#1f89840c7aac2406cc43a2ecad98fc02a8e130e4"
Expand Down

0 comments on commit 4d0df08

Please sign in to comment.