Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added themes.json and some extras #92

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Contributing

## Adding a New Theme

To add your theme, copy your CSS file into the `themes` directory. The name of your file has to match the format `prism-{your-theme-id}.css`. Make sure your theme follows your guidelines.

Next, you have to register your theme in `themes.json`. Add a new entry like this:

```json
"{your-theme-id}": {
"title": "Your title",
"owner": "Your GitHub user name"
}
```

Additionally, you can also specify who the original owner is (`originalOwner`) or on what theme your theme is based (`basedOn`).
You aren't required to give a GitHub user name. You can also use a different name and link where the link is optional:

```json
"{your-theme-id}": {
"title": "Your title",
"owner": {
"name": "My real name",
"link": "https://github.com/someUserName"
},
"originalOwner": {
"name": "Famous artist"
}
}
```

At last, you have to rebuild the project by running:

```bash
npm i && npm run build
```

Before making a pull request, you can use the following command to verify that all our checks pass and that your theme follows all our guidelines:

```bash
npm test
```

Thank you so much for contributing!!


## Theme guidelines and requirements

Your theme will be used by Prism and its plugins which can also add CSS rules.
These guidelines will make sure that your theme is compatible with Prism and its plugins.

### Font size of code blocks

Some of Prism's plugins assume that the `pre` and `code` elements have the same font size.

To make sure that this is that case, all themes are __required__ to either include a rule which ensures that the property `font-size: 1em` is applied to all elements which match `pre > code[class*="language-"]` or to not change the font size of code blocks.

If you want to change the font size of code blocks, use the `font-size` property on the `pre` element. Example:

```css
pre[class*="language-"] {
font-size: 90%;
}
```

### Overflow of `pre` elements

The `overflow` property of `pre` elements is __required__ to be left unset or set to `auto`.

This is required because other `overflow` values will cause problems with layouts based on `float` or flexboxes.

One notable exception is the original Coy theme because its shadows are impossible to implement otherwise.
23 changes: 4 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,7 @@ To use one of the themes, just include the theme's CSS file in your page. Exampl

## Adding a New Theme

To add your own theme, copy it into the `themes` directory and add your themes to the list of available themes in the readme.
The links for your themes have to be `themes/prism-<your theme>.css` for the theme itself and `screenshots/prism-<your theme>.png` for the screenshot.

The screenshot will be created for you by running the following command:

```bash
npm i && npx gulp screenshot
```

Before making a pull request, you can the following command to verify that all checks pass:

```bash
npm test
```

Thank you so much for contributing!!
See our [Contributing](CONTRIBUTING.md) guide.

## Available themes

Expand All @@ -54,7 +39,7 @@ Thank you so much for contributing!!
[![Xonokai](screenshots/prism-xonokai.png)](themes/prism-xonokai.css)

* [__Ateliersulphurpool-light__](themes/prism-base16-ateliersulphurpool.light.css) (by [Bram de Haan](https://github.com/atelierbram))<br />
[![Ateliersulpherpool-light](screenshots/prism-base16-ateliersulphurpool.light.png)](themes/prism-base16-ateliersulphurpool.light.css)
[![Ateliersulphurpool-light](screenshots/prism-base16-ateliersulphurpool.light.png)](themes/prism-base16-ateliersulphurpool.light.css)

* [__Hopscotch__](themes/prism-hopscotch.css) (by [Jan T. Sott](https://github.com/idleberg))<br />
[![Hopscotch](screenshots/prism-hopscotch.png)](themes/prism-hopscotch.css)
Expand Down Expand Up @@ -83,14 +68,14 @@ Thank you so much for contributing!!
* [__VS__](themes/prism-vs.css) (by [andrewlock](https://github.com/andrewlock))<br />
[![VS](screenshots/prism-vs.png)](themes/prism-vs.css)

* [__Darcula__](themes/prism-darcula.css) (by [service-paradis](https://github.com/service-paradis), based on Jetbrains Darcula theme)<br />
* [__Darcula__](themes/prism-darcula.css) (by [service-paradis](https://github.com/service-paradis), based on Jetbrain's Darcula theme)<br />
[![Darcula](screenshots/prism-darcula.png)](themes/prism-darcula.css)

* [__a11y Dark__](themes/prism-a11y-dark.css) (by [ericwbailey](https://github.com/ericwbailey))<br />
[![a11y Dark](screenshots/prism-a11y-dark.png)](themes/prism-a11y-dark.css)

* [__Dracula__](themes/prism-dracula.css) (by [Byverdu](https://github.com/byverdu))<br />
[![a11y Dark](screenshots/prism-dracula.png)](themes/prism-dracula.css)
[![Dracula](screenshots/prism-dracula.png)](themes/prism-dracula.css)

* [__Synthwave '84__](themes/prism-synthwave84.css) (originally by [Robb Owen](https://github.com/robb0wen), adapted by [Marc Backes](https://github.com/themarcba))<br />
[![Synthwave '84](screenshots/prism-synthwave84.png)](themes/prism-synthwave84.css)
Expand Down
193 changes: 177 additions & 16 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,33 @@ const fs = require('fs').promises;
const { parallel } = require('gulp');
const captureWebsite = require('capture-website');
const path = require('path');
const { JSDOM } = require('jsdom');


/**
* @type {Object<string, ThemesEntry>}
*
* @typedef ThemesEntry
* @property {string} title
* @property {Entity} owner
* @property {Entity} [originalOwner]
* @property {Entity} [basedOn]
*
* @typedef {string | { name: string; link?: string }} Entity
*/
const themeCatalog = require('./themes.json').themes;


const themesDir = path.join(__dirname, 'themes');
const screenshotDir = path.join(__dirname, 'screenshots');
const getThemePath = theme => path.join(themesDir, `prism-${theme}.css`);
const getScreenshotPath = theme => path.join(screenshotDir, `prism-${theme}.png`);

/**
* Returns the names of all themes. This includes the `prism-` prefix.
*/
async function getThemes() {
return (await fs.readdir(themesDir)).map(f => (/^.+(?=\.css$)/.exec(f) || [''])[0]).filter(f => f);
return Object.keys(themeCatalog);
}

/**
Expand Down Expand Up @@ -41,7 +58,7 @@ async function screenshotMissingThemes() {
* @param {boolean} overwrite
*/
async function screenshotTheme(theme, overwrite) {
const file = `${screenshotDir}/${theme}.png`;
const file = getScreenshotPath(theme);

if (await fs.stat(file).then(s => s.isFile()).catch(() => false)) {
if (overwrite) {
Expand All @@ -56,39 +73,183 @@ async function screenshotTheme(theme, overwrite) {
scaleFactor: 1,
element: 'pre',
styles: [
await fs.readFile(`${themesDir}/${theme}.css`, 'utf-8')
await fs.readFile(getThemePath(theme), 'utf-8')
]
});
}

/**
* Updates the "Available themes" section in `README.md`.
*/
async function updateReadme() {
const themes = await getThemes();

/**
* Returns the credit string of a theme.
*
* @param {ThemesEntry} entry
* @returns {string}
*/
function getCredit(entry) {
if (entry.basedOn) {
return `by ${printEntity(entry.owner)}, based on ${printEntity(entry.basedOn)}`;
} else if (entry.originalOwner) {
return `originally by ${printEntity(entry.originalOwner)}, adapted by ${printEntity(entry.owner)}`;
} else {
return `by ${printEntity(entry.owner)}`;
}
}
/**
* @param {Entity} entity
*/
function printEntity(entity) {
if (typeof entity === 'string') {
return `[${entity}](https://github.com/${entity})`;
} else if (entity.link) {
return `[${entity.name}](${entity.link})`;
} else {
return entity.name;
}
}

const md = themes.map(theme => {
const css = `themes/prism-${theme}.css`;
const screenshot = `screenshots/prism-${theme}.png`;

const entry = themeCatalog[theme];
const title = entry.title;
const credit = getCredit(entry);

return `* [__${title}__](${css}) (${credit})<br />\n[![${title}](${screenshot})](${css})`;
}).join('\n\n');

const readmePath = path.join(__dirname, 'README.md');
let readme = await fs.readFile(readmePath, 'utf-8');
readme = readme.replace(/(## Available themes)[\s\S]*/, (m, header) => {
return `${header}\n\n${md}\n`;
});

await fs.writeFile(readmePath, readme, 'utf-8');
}

/**
* Checks that all themes have a screenshot.
*/
async function checkScreenshots() {
for (const theme of await getThemes()) {
const file = `${screenshotDir}/${theme}.png`;
if (!await fs.stat(file).then(s => s.isFile()).catch(() => false)) {
throw new Error(`The theme "${theme}" doesn't have a screenshot.`);
const themes = new Set(await getThemes());
const screenshots = new Set(await fs.readdir(screenshotDir));
screenshots.delete('code.html');

for (const theme of themes) {
if (!screenshots.delete(`prism-${theme}.png`)) {
throw new Error(`The theme "${theme}" does not have a screenshot.`);
}
}

if (screenshots.size > 0) {
throw new Error(`There are screenshots without a theme: "${[...screenshots].join('", "')}"`);
}
}

/**
* Checks that all themes are in the list of available themes.
* Checks that all themes have a CSS file.
*/
async function checkAvailableThemes() {
const readme = await fs.readFile(path.join(__dirname, 'README.md'), 'utf-8');
async function checkCSS() {
const themes = new Set(await getThemes());
const cssFiles = new Set(await fs.readdir(themesDir));

for (const theme of await getThemes()) {
if (!readme.includes(theme + ".css")) {
throw new Error(`The theme "${theme}" is not included in the list of available themes.`);
for (const theme of themes) {
if (!cssFiles.delete(`prism-${theme}.css`)) {
throw new Error(`The theme "${theme}" does not have a screenshot.`);
}
if (!readme.includes(theme + ".png")) {
throw new Error(`The screenshot of "${theme}" is not included in the list of available themes.`);
}

if (cssFiles.size > 0) {
throw new Error(`There are CSS files without a theme: "${[...cssFiles].join('", "')}"`);
}
}

/**
* @type {Object<string, Object<string, string | RegExp>>}
*/
const requirements = {
'pre': {
/* Code block: pre */

'overflow': 'auto'
},
'pre > code': {
/* Code block: code */

'font-size': /^(?:1em|)$/
},
':not(pre) > code': {
/* Inline: code */

// none
}
};

async function checkRequirements() {
const getSource = css => {
return `
<!DOCTYPE html>
<html>
<head>
<style>${css}</style>
</head>
<body>
<!-- Code block -->
<pre class="language-javascript"><code class="language-javascript">var a = 0;</code></pre>

<!-- Inline code -->
<code class="language-javascript">a++</code>
</body>
</html>
`;
};

let pass = true;

for (const theme of await getThemes()) {
const dom = new JSDOM(getSource(await fs.readFile(getThemePath(theme), 'utf-8')));

for (const selector in requirements) {
const properties = requirements[selector];

for (const element of dom.window.document.querySelectorAll(selector)) {
const style = dom.window.getComputedStyle(element);

for (const property in properties) {
const expected = properties[property];
const actual = style[property];

let valid;
if (typeof expected === 'string') {
valid = expected === actual;
} else {
valid = expected.test(actual);
}

if (!valid) {
pass = false;
console.error(`${theme} does not meet the requirement for "${selector}":\n` +
` Expected the ${property} property to be ${expected} but found "${actual}"`);
}
}
}
}
}

if (!pass) {
throw new Error('Some checks failed.');
}
}


exports['update-readme'] = updateReadme;
exports.screenshot = screenshotMissingThemes;
exports['screenshot-all'] = screenshotAllThemes;
exports.check = parallel(checkScreenshots, checkAvailableThemes)
exports.build = parallel(screenshotMissingThemes, updateReadme);

exports.check = parallel(checkScreenshots, checkCSS, checkRequirements)
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"description": "Additional themes for the Prism syntax highlighting library.",
"main": "README.md",
"scripts": {
"build": "npx gulp build",
"lint": "stylelint \"themes/*.css\"",
"lint-fix": "stylelint \"themes/*.css\" --fix",
"test": "npm run lint && gulp check"
Expand All @@ -21,6 +22,7 @@
"devDependencies": {
"capture-website": "^0.4.0",
"gulp": "^4.0.2",
"jsdom": "^15.2.1",
"stylelint": "^12.0.0",
"stylelint-config-standard": "^19.0.0"
}
Expand Down
Loading