Skip to content
This repository has been archived by the owner on Jun 4, 2019. It is now read-only.

Dashboards + misc browser UI adjustments #8

Merged
merged 23 commits into from Nov 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 32 additions & 0 deletions UserDocs.md
Expand Up @@ -370,3 +370,35 @@ Refer to [Amazon's documentation](https://docs.aws.amazon.com/AmazonS3/latest/us
`he.put_file("local_directory/", "bucket/path/")` - this will perform a recursive copy, and is correct


## Catalog

### Summaries

Quilt summaries summarize data in your bucket.
Summaries combine several file types:

* Markdown (`.md`)
* [Vega specs](https://github.com/vega/vega) (`.json`)
* Jupyter notebooks (`.ipynb`)
* Images (`.jpe?g`, `.png`, `.gif`)
* HTML (`.html`)

Upload `quilt_summarize.json` to any directory where you want a summary
to appear.

`quilt_summarize.json` is a JSON list of supported files in your S3 bucket.
All files in the list are signed (for security) and rendered in order
when you visit the containing directory in the Quilt web catalog.

Paths are resolved relative to the containing `quilt_summarize.json` file.

Example:

```
[
"/vega_specs/chloropleth.json",
"./image.jpg",
"../notebooks/JupyterCon.ipynb",
"description.md"
]
```
2 changes: 1 addition & 1 deletion catalog/app/app.js
Expand Up @@ -74,8 +74,8 @@ const render = (messages) => {
credentialsSelector: AWSAuth.selectors.credentials,
region: config.aws.region,
}],
AWS.Signer.Provider,
AWS.S3.Provider,
AWS.Signer.Provider,
[AWS.ES.Provider, {
host: config.aws.elasticSearchUrl,
log: 'trace',
Expand Down
101 changes: 81 additions & 20 deletions catalog/app/components/Markdown/Markdown.js
@@ -1,16 +1,20 @@
import hljs from 'highlight.js';
import flow from 'lodash/flow';
import id from 'lodash/identity';
import memoize from 'lodash/memoize';
import PT from 'prop-types';
import React from 'react';
import { setPropTypes } from 'recompose';
import Remarkable from 'remarkable';
import { replaceEntities, escapeHtml } from 'remarkable/lib/common/utils';
import { replaceEntities, escapeHtml, unescapeMd } from 'remarkable/lib/common/utils';
import styled from 'styled-components';

import { composeComponent } from 'utils/reactTools';


// TODO: switch to pluggable react-aware renderer
// TODO: use react-router's Link component for local links

const highlight = (str, lang) => {
if (lang === 'none') {
return '';
Expand All @@ -36,34 +40,69 @@ const highlight = (str, lang) => {
const escape = flow(replaceEntities, escapeHtml);

/**
* Plugin for remarkable that disables image rendering.
* A Markdown (Remarkable) plugin. Takes a Remarkable instance and adjusts it.
*
* @typedef {function} MarkdownPlugin
*
* @param {Object} md Remarkable instance
* @param {Object} md Remarkable instance.
*/
const disableImg = (md) => {

/**
* Create a plugin for remarkable that does custom processing of image tags.
*
* @param {Object} options
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks slightly incorrect to me -- I think as it is, this declares a function of 3 parameters, when really you want a function with 1 parameter that has 2 destructure parameters. See "Documenting a destructuring parameter" in http://usejsdoc.org/tags-param.html

Also, if you could document the return type, I would appreciate that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

* @param {bool} options.disable
* Don't show images, render them as they are in markdown contents (escaped).
* @param {function} options.process
* Function that takes an image object ({ alt, src, title }) and returns a
* (possibly modified) image object.
*
* @returns {MarkdownPlugin}
*/
const imageHandler = ({
disable = false,
process = id,
}) => (md) => {
// eslint-disable-next-line no-param-reassign
md.renderer.rules.image = (tokens, idx) => {
const t = tokens[idx];
const src = escape(t.src);
const title = t.title ? ` "${escape(t.title)}"` : '';
const alt = t.alt ? escape(t.alt) : '';
return `<span>![${alt}](${src}${title})</span>`;
const t = process(tokens[idx]);

if (disable) {
const alt = t.alt ? escape(t.alt) : '';
const src = escape(t.src);
const title = t.title ? ` "${escape(t.title)}"` : '';
return `<span>![${alt}](${src}${title})</span>`;
}

const src = escapeHtml(t.src);
const alt = t.alt ? escape(unescapeMd(t.alt)) : '';
const title = t.title ? ` title="${escape(t.title)}"` : '';
return `<img src="${src}" alt="${alt}"${title} />`;
};
};

/**
* Plugin for remarkable that adjusts link rendering:
* Create a plugin for remarkable that does custom processing of links.
*
* - adds rel="nofollow" attribute
* @param {Object} options
* @param {bool} options.nofollow
* Add rel="nofollow" attribute if true (default).
* @param {function} options.process
* Function that takes a link object ({ href, title }) and returns a
* (possibly modified) link object.
*
* @param {Object} md Remarkable instance
* @returns {MarkdownPlugin}
*/
const adjustLinks = (md) => {
const linkHandler = ({
nofollow = true,
process = id,
}) => (md) => {
// eslint-disable-next-line no-param-reassign
md.renderer.rules.link_open = (tokens, idx) => {
const t = tokens[idx];
const t = process(tokens[idx]);
const title = t.title ? ` title="${escape(t.title)}"` : '';
return `<a href="${escapeHtml(t.href)}" rel="nofollow"${title}>`;
const rel = nofollow ? ' rel="nofollow"' : '';
return `<a href="${escapeHtml(t.href)}"${rel}${title}>`;
};
};

Expand All @@ -77,15 +116,24 @@ const adjustLinks = (md) => {
*
* @returns {Object} Remarakable instance
*/
const getRenderer = memoize(({ images }) => {
const getRenderer = memoize(({
images,
processImg,
processLink,
}) => {
const md = new Remarkable('full', {
highlight,
html: false,
linkify: true,
typographer: true,
});
md.use(adjustLinks);
if (!images) md.use(disableImg);
md.use(linkHandler({
process: processLink,
}));
md.use(imageHandler({
disable: !images,
process: processImg,
}));
return md;
});

Expand All @@ -110,14 +158,27 @@ export default composeComponent('Markdown',
data: PT.string,
className: PT.string,
images: PT.bool,
processImg: PT.func,
processLink: PT.func,
}),
({ data, className = '', images = true }) => (
({
data,
className = '',
images = true,
processImg,
processLink,
}) => (
<Style
className={`markdown ${className}`}
dangerouslySetInnerHTML={{
// would prefer to render in a saga but md.render() fails when called
// in a generator
__html: getRenderer({ images }).render(data),
__html:
getRenderer({
images,
processImg,
processLink,
}).render(data),
}}
/>
));