add mod pages to menu: options & docs (README.md rendered)

This commit is contained in:
dragonwocky 2021-04-20 00:16:46 +10:00
parent 5fe31ac703
commit a5c4d47a6d
10 changed files with 1018 additions and 333 deletions

3
extension/dep/markdown-it.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,9 @@
*/
'use strict';
import './dep/markdown-it.min.js';
export const ERROR = Symbol();
export const env = {};
@ -42,7 +45,12 @@ web.whenReady = (selectors = [], callback = () => {}) => {
*/
web.createElement = (html) => {
const template = document.createElement('template');
template.innerHTML = html.trim();
template.innerHTML = html.includes('<pre')
? html.trim()
: html
.split(/\n/)
.map((line) => line.trim())
.join('');
return template.content.firstElementChild;
};
@ -95,7 +103,7 @@ fs.getText = (path) => fetch(chrome.runtime.getURL(path)).then((res) => res.text
fs.isFile = async (path) => {
try {
await fetch(chrome.runtime.getURL(`repo/${path}`));
await fetch(chrome.runtime.getURL(path));
return true;
} catch {
return false;
@ -137,79 +145,88 @@ export const regexers = {
export const registry = {};
registry.validate = async (mod, err, check) =>
Promise.all(
[
check('name', mod.name, typeof mod.name === 'string'),
check('id', mod.id, typeof mod.id === 'string').then((id) =>
id === ERROR ? ERROR : regexers.uuid(id)
),
check('description', mod.description, typeof mod.description === 'string'),
check('version', mod.version, typeof mod.version === 'string').then((version) =>
version === ERROR ? ERROR : regexers.semver(version)
),
check('tags', mod.tags, Array.isArray(mod.tags)).then((tags) =>
tags === ERROR ? ERROR : tags.map((tag) => check('tag', tag, typeof tag === 'string'))
),
check('authors', mod.authors, Array.isArray(mod.authors)).then((authors) =>
authors === ERROR
registry.validate = async (mod, err, check) => {
let conditions = [
check('name', mod.name, typeof mod.name === 'string'),
check('id', mod.id, typeof mod.id === 'string').then((id) =>
id === ERROR ? ERROR : regexers.uuid(id)
),
check('version', mod.version, typeof mod.version === 'string').then((version) =>
version === ERROR ? ERROR : regexers.semver(version)
),
check('description', mod.description, typeof mod.description === 'string'),
check(
'preview',
mod.preview,
mod.preview === undefined || typeof mod.preview === 'string'
).then((preview) =>
preview ? (preview === ERROR ? ERROR : regexers.url(preview)) : undefined
),
check('tags', mod.tags, Array.isArray(mod.tags)).then((tags) =>
tags === ERROR ? ERROR : tags.map((tag) => check('tag', tag, typeof tag === 'string'))
),
check('authors', mod.authors, Array.isArray(mod.authors)).then((authors) =>
authors === ERROR
? ERROR
: authors.map((author) => [
check('author.name', author.name, typeof author.name === 'string'),
check(
'author.email',
author.email,
typeof author.email === 'string'
).then((email) => (email === ERROR ? ERROR : regexers.email(email))),
check('author.url', author.url, typeof author.url === 'string').then((url) =>
url === ERROR ? ERROR : regexers.url(url)
),
check('author.icon', author.icon, typeof author.icon === 'string').then((icon) =>
icon === ERROR ? ERROR : regexers.url(icon)
),
])
),
check(
'css',
mod.css,
mod.css && typeof mod.css === 'object' && !Array.isArray(mod.css)
).then((css) =>
css
? css === ERROR
? ERROR
: authors.map((author) => [
check('author.name', author.name, typeof author.name === 'string'),
check(
'author.email',
author.email,
typeof author.email === 'string'
).then((email) => (email === ERROR ? ERROR : regexers.email(email))),
check('author.url', author.url, typeof author.url === 'string').then((url) =>
url === ERROR ? ERROR : regexers.url(url)
),
check('author.icon', author.icon, typeof author.icon === 'string').then((icon) =>
icon === ERROR ? ERROR : regexers.url(icon)
),
])
),
check(
'css',
mod.css,
!!mod.css && typeof mod.css === 'object' && !Array.isArray(mod.css)
).then((css) => {
if (css === ERROR) return ERROR;
if (!css) return undefined;
return ['frame', 'client', 'menu']
.filter((dest) => css[dest])
.map(async (dest) =>
check(`css.${dest}`, css[dest], Array.isArray(css[dest])).then((files) =>
files === ERROR
? ERROR
: files.map(async (file) =>
check(
`css.${dest} file`,
file,
await fs.isFile(`${mod._dir}/${file}`, '.css')
)
)
)
);
}),
check(
'js',
mod.js,
!!mod.js && typeof mod.js === 'object' && !Array.isArray(mod.js)
).then(async (js) => {
: ['frame', 'client', 'menu']
.filter((dest) => css[dest])
.map(async (dest) =>
check(`css.${dest}`, css[dest], Array.isArray(css[dest])).then((files) =>
files === ERROR
? ERROR
: files.map(async (file) =>
check(
`css.${dest} file`,
file,
await fs.isFile(`repo/${mod._dir}/${file}`, '.css')
)
)
)
)
: undefined
),
check('js', mod.js, mod.js && typeof mod.js === 'object' && !Array.isArray(mod.js)).then(
async (js) => {
if (js === ERROR) return ERROR;
if (!js) return undefined;
return [
check('js.client', js.client, !js.client ?? Array.isArray(js.client)).then(
check('js.client', js.client, !js.client || Array.isArray(js.client)).then(
(client) => {
if (client === ERROR) return ERROR;
if (!client) return undefined;
return client.map(async (file) =>
check('js.client file', file, await fs.isFile(file, '.js'))
check(
'js.client file',
file,
await fs.isFile(`repo/${mod._dir}/${file}`, '.js')
)
);
}
),
check('js.electron', js.electron, !js.electron ?? Array.isArray(js.electron)).then(
check('js.electron', js.electron, !js.electron || Array.isArray(js.electron)).then(
(electron) => {
if (electron === ERROR) return ERROR;
if (!electron) return undefined;
@ -217,7 +234,7 @@ registry.validate = async (mod, err, check) =>
check(
'js.electron file',
file,
!!file && typeof file === 'object' && !Array.isArray(file)
file && typeof file === 'object' && !Array.isArray(file)
).then(async (file) =>
file === ERROR
? ERROR
@ -225,7 +242,7 @@ registry.validate = async (mod, err, check) =>
check(
'js.electron file source',
file.source,
await fs.isFile(file.source, '.js')
await fs.isFile(`repo/${mod._dir}/${file.source}`, '.js')
),
// referencing the file within the electron app
// existence can't be validated, so only format is
@ -240,23 +257,86 @@ registry.validate = async (mod, err, check) =>
}
),
];
}),
check(
'options',
mod.options,
!mod.options ?? (await fs.isFile(mod.options, '.json'))
).then(async (filepath) => {
if (filepath === ERROR) return ERROR;
if (!filepath) return undefined;
try {
const options = await fs.getJSON(`repo/${mod._dir}/${mod.options}`);
// todo: validate options
} catch {
err(`invalid options ${filepath}`);
}
}),
].flat(Infinity)
);
}
),
check('options', mod.options, Array.isArray(mod.options)).then((options) =>
options === ERROR
? ERROR
: options.map((option) => {
const conditions = [];
switch (option.type) {
case 'toggle':
conditions.push(
check('option.value', option.value, typeof option.value === 'boolean')
);
break;
case 'select':
conditions.push(
check(
'option.values',
option.values,
Array.isArray(option.values)
).then((value) =>
value === ERROR
? ERROR
: value.map((option) =>
check('option.values option', option, typeof option === 'string')
)
)
);
break;
case 'text':
conditions.push(
check('option.value', option.value, typeof option.value === 'string')
);
break;
case 'number':
conditions.push(
check('option.value', option.value, typeof option.value === 'number')
);
break;
case 'file':
conditions.push(
check(
'option.extensions',
option.extensions,
!option.extensions || Array.isArray(option.extensions)
).then((extensions) =>
extensions
? extensions === ERROR
? ERROR
: extensions.map((ext) =>
check('option.extension', ext, typeof ext === 'string')
)
: undefined
)
);
break;
default:
return check('option.type', option.type, false);
}
return [
conditions,
check(
'option.key',
option.key,
typeof option.key === 'string' && !option.key.match(/\s/)
),
check('option.label', option.label, typeof option.label === 'string'),
check(
'option.description',
option.description,
!option.description || typeof option.description === 'string'
),
];
})
),
];
do {
conditions = await Promise.all(conditions.flat(Infinity));
} while (conditions.some((condition) => Array.isArray(condition)));
return conditions;
};
registry.get = async (callback = () => {}) => {
registry._list = [];
@ -269,6 +349,7 @@ registry.get = async (callback = () => {}) => {
mod.tags = mod.tags ?? [];
mod.css = mod.css ?? [];
mod.js = mod.js ?? {};
mod.options = mod.options ?? [];
const check = (prop, value, condition) =>
Promise.resolve(condition ? value : err(`invalid ${prop} ${JSON.stringify(value)}`)),
@ -288,36 +369,10 @@ registry.errors = async (callback = () => {}) => {
return registry._errors;
};
export const markdown = {};
markdown.simple = (string) =>
string
.split('\n')
.map((line) =>
line
.trim()
.replace(/\s+/g, ' ')
// > quote
.replace(/^>\s+(.+)$/g, '<blockquote>$1</blockquote>')
// ~~strikethrough~~
.replace(/([^\\])?~~((?:(?!~~).)*[^\\])~~/g, '$1<s>$2</s>')
// __underline__
.replace(/([^\\])?__((?:(?!__).)*[^\\])__/g, '$1<u>$2</u>')
// **bold**
.replace(/([^\\])?\*\*((?:(?!\*\*).)*[^\\])\*\*/g, '$1<b>$2</b>')
// *italic*
.replace(/([^\\])?\*([^*]*[^\\*])\*/g, '$1<i>$2</i>')
// _italic_
.replace(/([^\\])?_([^_]*[^\\_])_/g, '$1<i>$2</i>')
// `code`
.replace(/([^\\])?`([^`]*[^\\`])`/g, '$1<code>$2</code>')
// ![image_title](source)
.replace(
/([^\\])?\!\[([^\]]*[^\\\]]?)\]\(([^)]*[^\\)])\)/g,
`$1<img alt="$2" src="$3" onerror="this.remove()">`
)
// [link](destination)
.replace(/([^\\])?\[([^\]]*[^\\\]]?)\]\(([^)]*[^\\)])\)/g, '$1<a href="$3">$2</a>')
)
.map((line) => (line.startsWith('<blockquote>') ? line : `<p>${line}</p>`))
.join('');
export const markdown = new markdownit({
linkify: true,
highlight(...args) {
console.log(args);
return '';
},
});

View File

@ -16,5 +16,6 @@ import(chrome.runtime.getURL('helpers.js')).then(({ web, registry }) => {
import(chrome.runtime.getURL(`repo/${mod._dir}/${script}`));
}
}
console.log(await registry.errors());
});
});

View File

@ -0,0 +1,322 @@
# markdown tester
## table
| option | extended description | type | values/defaults | platform-specific details |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------ | --------------- | ------------------------- |
| height of frameless dragarea | the rectangle added at the top of a window in "integrated titlebar" mode, used to drag/move the window. | number input | 15 | macOS: forced to 0 |
| width to wrap columns at | the size in pixels below which in-page columns are resized to appear full width so content isn't squished. | number input | 600 | |
| integrated scrollbars | use scrollbars that fit better into notion's ui instead of the default chrome ones. | toggle | yes | |
| snappy transitions | | toggle | no | |
| thicker bold text | | toggle | yes | |
| more readable line spacing | | toggle | no | |
| hide help button | | toggle | no | |
# Markdown: Syntax
- [Overview](#overview)
- [Philosophy](#philosophy)
- [Inline HTML](#html)
- [Automatic Escaping for Special Characters](#autoescape)
- [Block Elements](#block)
- [Paragraphs and Line Breaks](#p)
- [Headers](#header)
- [Blockquotes](#blockquote)
- [Lists](#list)
- [Code Blocks](#precode)
- [Horizontal Rules](#hr)
- [Span Elements](#span)
- [Links](#link)
- [Emphasis](#em)
- [Code](#code)
- [Images](#img)
- [Miscellaneous](#misc)
- [Backslash Escapes](#backslash)
- [Automatic Links](#autolink)
**Note:** This document is itself written using Markdown; you
can [see the source for it by adding '.text' to the URL](/projects/markdown/syntax.text).
---
## Overview
### Philosophy
Markdown is intended to be as easy-to-read and easy-to-write as is feasible.
Readability, however, is emphasized above all else. A Markdown-formatted
document should be publishable as-is, as plain text, without looking
like it's been marked up with tags or formatting instructions. While
Markdown's syntax has been influenced by several existing text-to-HTML
filters -- including [Setext](http://docutils.sourceforge.net/mirror/setext.html), [atx](http://www.aaronsw.com/2002/atx/), [Textile](http://textism.com/tools/textile/), [reStructuredText](http://docutils.sourceforge.net/rst.html),
[Grutatext](http://www.triptico.com/software/grutatxt.html), and [EtText](http://ettext.taint.org/doc/) -- the single biggest source of
inspiration for Markdown's syntax is the format of plain text email.
## Block Elements
### Paragraphs and Line Breaks
A paragraph is simply one or more consecutive lines of text, separated
by one or more blank lines. (A blank line is any line that looks like a
blank line -- a line containing nothing but spaces or tabs is considered
blank.) Normal paragraphs should not be indented with spaces or tabs.
The implication of the "one or more consecutive lines of text" rule is
that Markdown supports "hard-wrapped" text paragraphs. This differs
significantly from most other text-to-HTML formatters (including Movable
Type's "Convert Line Breaks" option) which translate every line break
character in a paragraph into a `<br />` tag.
When you _do_ want to insert a `<br />` break tag using Markdown, you
end a line with two or more spaces, then type return.
### Headers
Markdown supports two styles of headers, [Setext] [1] and [atx] [2].
Optionally, you may "close" atx-style headers. This is purely
cosmetic -- you can use this if you think it looks better. The
closing hashes don't even need to match the number of hashes
used to open the header. (The number of opening hashes
determines the header level.)
### Blockquotes
Markdown uses email-style `>` characters for blockquoting. If you're
familiar with quoting passages of text in an email message, then you
know how to create a blockquote in Markdown. It looks best if you hard
wrap the text and put a `>` before every line:
> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
>
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
> id sem consectetuer libero luctus adipiscing.
Markdown allows you to be lazy and only put the `>` before the first
line of a hard-wrapped paragraph:
> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
> id sem consectetuer libero luctus adipiscing.
Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by
adding additional levels of `>`:
> This is the first level of quoting.
>
> > This is nested blockquote.
>
> Back to the first level.
Blockquotes can contain other Markdown elements, including headers, lists,
and code blocks:
> ## This is a header.
>
> 1. This is the first list item.
> 2. This is the second list item.
>
> Here's some example code:
>
> return shell_exec("echo $input | $markdown_script");
Any decent text editor should make email-style quoting easy. For
example, with BBEdit, you can make a selection and choose Increase
Quote Level from the Text menu.
### Lists
Markdown supports ordered (numbered) and unordered (bulleted) lists.
Unordered lists use asterisks, pluses, and hyphens -- interchangably
-- as list markers:
- Red
- Green
- Blue
is equivalent to:
- Red
- Green
- Blue
and:
- Red
- Green
- Blue
Ordered lists use numbers followed by periods:
1. Bird
2. McHale
3. Parish
It's important to note that the actual numbers you use to mark the
list have no effect on the HTML output Markdown produces. The HTML
Markdown produces from the above list is:
If you instead wrote the list in Markdown like this:
1. Bird
1. McHale
1. Parish
or even:
3. Bird
1. McHale
1. Parish
you'd get the exact same HTML output. The point is, if you want to,
you can use ordinal numbers in your ordered Markdown lists, so that
the numbers in your source match the numbers in your published HTML.
But if you want to be lazy, you don't have to.
To make lists look nice, you can wrap items with hanging indents:
- Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
viverra nec, fringilla in, laoreet vitae, risus.
- Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
Suspendisse id sem consectetuer libero luctus adipiscing.
But if you want to be lazy, you don't have to:
- Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
viverra nec, fringilla in, laoreet vitae, risus.
- Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
Suspendisse id sem consectetuer libero luctus adipiscing.
List items may consist of multiple paragraphs. Each subsequent
paragraph in a list item must be indented by either 4 spaces
or one tab:
1. This is a list item with two paragraphs. Lorem ipsum dolor
sit amet, consectetuer adipiscing elit. Aliquam hendrerit
mi posuere lectus.
Vestibulum enim wisi, viverra nec, fringilla in, laoreet
vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
sit amet velit.
2. Suspendisse id sem consectetuer libero luctus adipiscing.
It looks nice if you indent every line of the subsequent
paragraphs, but here again, Markdown will allow you to be
lazy:
- This is a list item with two paragraphs.
This is the second paragraph in the list item. You're
only required to indent the first line. Lorem ipsum dolor
sit amet, consectetuer adipiscing elit.
- Another item in the same list.
To put a blockquote within a list item, the blockquote's `>`
delimiters need to be indented:
- A list item with a blockquote:
> This is a blockquote
> inside a list item.
To put a code block within a list item, the code block needs
to be indented _twice_ -- 8 spaces or two tabs:
- A list item with a code block:
<code goes here>
### Code Blocks
Pre-formatted code blocks are used for writing about programming or
markup source code. Rather than forming normal paragraphs, the lines
of a code block are interpreted literally. Markdown wraps a code block
in both `<pre>` and `<code>` tags.
To produce a code block in Markdown, simply indent every line of the
block by at least 4 spaces or 1 tab.
This is a normal paragraph:
This is a code block.
Here is an example of AppleScript:
tell application "Foo"
beep
end tell
A code block continues until it reaches a line that is not indented
(or the end of the article).
Within a code block, ampersands (`&`) and angle brackets (`<` and `>`)
are automatically converted into HTML entities. This makes it very
easy to include example HTML source code using Markdown -- just paste
it and indent it, and Markdown will handle the hassle of encoding the
ampersands and angle brackets. For example, this:
<div class="footer">
&copy; 2004 Foo Corporation
</div>
Regular Markdown syntax is not processed within code blocks. E.g.,
asterisks are just literal asterisks within a code block. This means
it's also easy to use Markdown to write about Markdown's own syntax.
```
tell application "Foo"
beep
end tell
```
## Span Elements
### Links
Markdown supports two style of links: _inline_ and _reference_.
In both styles, the link text is delimited by [square brackets].
To create an inline link, use a set of regular parentheses immediately
after the link text's closing square bracket. Inside the parentheses,
put the URL where you want the link to point, along with an _optional_
title for the link, surrounded in quotes. For example:
This is [an example](http://example.com/) inline link.
[This link](http://example.net/) has no title attribute.
### Emphasis
Markdown treats asterisks (`*`) and underscores (`_`) as indicators of
emphasis. Text wrapped with one `*` or `_` will be wrapped with an
HTML `<em>` tag; double `*`'s or `_`'s will be wrapped with an HTML
`<strong>` tag. E.g., this input:
_single asterisks_
_single underscores_
**double asterisks**
**double underscores**
### Code
To indicate a span of code, wrap it with backtick quotes (`` ` ``).
Unlike a pre-formatted code block, a code span indicates code within a
normal paragraph. For example:
Use the `printf()` function.

View File

@ -0,0 +1,2 @@
<!-- https://fontawesome.com/icons/file?style=solid -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm160-14.1v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z"/></svg>

After

Width:  |  Height:  |  Size: 343 B

View File

@ -33,28 +33,105 @@ header h1 a:not([data-active]) {
text-decoration: none;
}
main section[data-container='library'] {
main section[data-container] {
display: grid;
grid-gap: 1.25em;
grid-template-columns: 1fr 1fr 1fr;
grid-template-columns: 1fr;
}
main section[data-container='library'] article {
@media (min-width: 550px) {
main section[data-container] {
grid-template-columns: 1fr 1fr;
}
main section[data-container='page'] > .documentation--buttons {
grid-column: span 2;
}
}
@media (min-width: 850px) {
main section[data-container] {
grid-template-columns: 1fr 1fr 1fr;
}
main section[data-container='page'] > .documentation--buttons {
grid-column: span 3;
}
main section[data-container='page'] > .documentation--body {
grid-column: span 2;
}
}
@media (min-width: 1350px) {
main section[data-container] {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
main section[data-container='page'] > .documentation--buttons {
grid-column: span 4;
}
main section[data-container='page'] > .documentation--body {
grid-column: span 3;
}
}
@media (min-width: 2050px) {
main section[data-container] {
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
}
main section[data-container='page'] > .documentation--buttons {
grid-column: span 5;
}
main section[data-container='page'] > .documentation--body {
grid-column: span 4;
}
}
main section[data-container] article {
border-radius: 5px;
box-shadow: rgb(0 0 0 / 10%) 0px 20px 25px -5px, rgb(0 0 0 / 4%) 0px 10px 10px -5px;
border: 1px solid var(--theme--divider);
background: var(--theme--page);
}
main section[data-container='library'] article > img {
main section[data-container] article img {
max-width: 100%;
}
main section[data-container] article > img {
border-bottom: 1px solid var(--theme--divider);
}
main section[data-container='library'] article > div {
padding: 1em;
border-bottom: 1px solid var(--theme--divider);
main section[data-container] article > div {
padding: 1rem;
}
.library--title {
.documentation--buttons,
.library--expand {
margin: 0;
display: flex;
}
.library--expand a {
margin-left: auto;
}
.documentation--buttons a,
.library--expand a {
border-radius: 3px;
padding: 0.35rem 0.45rem;
text-decoration: none;
display: flex;
}
.documentation--buttons span,
.library--expand span {
color: var(--theme--text_property);
}
.documentation--buttons a:hover,
.library--expand a:hover {
background: var(--theme--button-hover);
}
.documentation--buttons svg,
.library--expand svg {
width: 1em;
height: 1em;
padding-top: 2px;
margin-right: 0.3rem;
}
.documentation--buttons svg polygon,
.documentation--buttons svg path,
.library--expand svg polygon,
.library--expand svg path {
fill: var(--theme--text_property);
}
.library--version {
font-weight: normal;
font-size: 0.8rem;
@ -63,58 +140,16 @@ main section[data-container='library'] article > div {
background: var(--theme--tag_default);
color: var(--theme--tag_default-text);
}
.library--toggle_label {
align-items: center;
display: flex;
cursor: pointer;
}
.library--toggle {
position: relative;
margin-left: auto;
}
.library--toggle {
width: 2rem;
height: 1.25rem;
display: block;
border-radius: 9999px;
background: var(--theme--toggle_off);
}
.library--toggle::after {
content: '';
transition: transform 200ms ease-out 0s, background 200ms ease-out 0s;
height: 0.85rem;
width: 0.8rem;
left: 0.18rem;
top: 0.175rem;
position: absolute;
border-radius: 9999px;
background: var(--theme--toggle_dot);
}
.library--toggle_label input[type='checkbox']:checked ~ .library--toggle {
background: var(--theme--toggle_on);
}
.library--toggle_label input[type='checkbox']:checked ~ .library--toggle::after {
transform: translateX(100%);
}
.library--toggle_label input[type='checkbox'] {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.library--description {
font-size: 0.8rem;
margin: 0.25rem 0;
}
.library--tags,
.library--authors {
padding: 0;
list-style-type: none;
display: flex;
flex-wrap: wrap;
margin: 0.5em 0;
}
.library--tags li {
@ -128,9 +163,7 @@ main section[data-container='library'] article > div {
font-size: 0.8rem;
text-decoration: none;
border-radius: 5px;
padding: 0.125rem 0.25rem 0.25rem 0.25rem;
background: var(--theme--tag_default);
color: var(--theme--tag_default-text);
padding: 0.125rem 0.25rem 0.25rem 0;
}
.library--authors img {
height: 1em;
@ -142,6 +175,206 @@ main section[data-container='library'] article > div {
object-fit: cover;
}
main section:not([data-active]) {
display: none;
.library--options {
border-top: 1px solid var(--theme--divider);
}
.library--toggle_label,
.library--select_label,
.library--text_label,
.library--number_label,
.library--file_label {
margin: 0.6rem 0;
display: block;
appearance: none;
font-size: 0.8rem;
}
.library--toggle_label *,
.library--select_label *,
.library--text_label *,
.library--number_label *,
.library--file_label * {
appearance: none;
font-size: 0.8rem;
}
.library--toggle_label > p,
.library--toggle_label > p,
.library--select_label > p,
.library--text_label > p,
.library--number_label > p,
.library--file_label > p {
margin: 0.6rem 0;
}
.library--toggle_label > :not(input) {
align-items: center;
display: flex;
cursor: pointer;
--toggle--buttonsground: var(--theme--toggle_off);
--toggle--translation: 0%;
}
.library--toggle_label > :not(input) .library--toggle {
position: relative;
margin: auto 0 auto auto;
width: 2.25rem;
height: 1.25rem;
display: block;
border-radius: 40px;
background: var(--toggle--buttonsground);
}
.library--toggle_label > :not(input) .library--toggle::after {
content: '';
transition: transform 200ms ease-out 0s, background 200ms ease-out 0s;
height: 0.8rem;
width: 0.8rem;
left: 0.325rem;
top: 0.225rem;
position: absolute;
border-radius: 100%;
background: var(--theme--toggle_dot);
transform: translateX(var(--toggle--translation));
}
.library--toggle_label input[type='checkbox']:checked + :not(input) {
--toggle--buttonsground: var(--theme--toggle_on);
--toggle--translation: 100%;
}
.library--toggle_label:focus-within .library--toggle,
.library--file_label:focus-within .library--file,
.library--select_label .library--select:focus-within {
outline: solid thin;
}
.library--toggle_label input[type='checkbox'],
.library--file_label input {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.library--text_label textarea {
resize: none;
min-height: calc(1em + 16px);
height: var(--txt--scroll-height);
}
.library--text_label textarea,
.library--number_label input,
.library--select_label .library--select,
.library--file_label .library--file {
width: 100%;
padding: 6px 8px;
background: var(--theme--input);
border-radius: 3px;
border: none;
box-shadow: var(--theme--input-border) 0px 0px 0px 1px inset;
--theme--input_icon: var(--theme_dark--input_icon);
}
.library--select_label .library--select select {
outline: none;
}
.library--select_label .library--select select option {
background: var(--theme_dark--tag_select);
}
.library--select_label .library--select,
.library--file_label .library--file {
padding: 0;
display: flex;
}
.library--select_label .library--select > :first-child,
.library--file_label .library--file > :first-child {
display: flex;
padding: 6px 8px;
background: var(--theme--input-border);
}
.library--select_label .library--select > :first-child svg,
.library--file_label .library--file > :first-child svg {
width: 0.9em;
margin: auto 0;
}
.library--select_label .library--select > :last-child,
.library--file_label .library--file > :last-child {
margin: auto 0;
padding: 6px 8px;
width: 100%;
height: 100%;
border: none;
background: none;
}
.library--title {
margin: 0;
}
.library--title h2 {
margin: 0;
}
.library--title h2 > span {
font-size: 1.25rem;
padding-bottom: 0.25rem;
}
.library--full_card,
.documentation--body {
max-height: calc(100vh - 10rem);
overflow: auto;
}
.documentation--body {
padding: 1rem 2rem;
font-size: 0.8rem;
}
.documentation--body table {
border-spacing: 0;
border: 1px solid var(--theme--sidebar);
}
.documentation--body table th {
text-align: left;
}
.documentation--body table th,
.documentation--body table td {
padding: 5px 8px 6px;
border: 1px solid var(--theme--sidebar);
}
.documentation--body h1 {
font-size: var(--theme--font_heading1-size);
margin: 1rem 0 0.5rem 0;
}
.documentation--body h2 {
font-size: var(--theme--font_heading2-size);
margin: 1rem 0 0.5rem 0;
}
.documentation--body h3 {
font-size: var(--theme--font_heading3-size);
margin: 1rem 0 0.5rem 0;
}
.documentation--body ul,
.documentation--body ol {
padding-left: 1.25rem;
}
.documentation--body li {
margin: 0.4rem 0;
}
.documentation--body ol li {
padding-left: 0.25rem;
}
.documentation--body blockquote {
border-left: 2px solid currentColor;
padding-left: 0.5rem;
margin: 0.5rem 0 0.5rem 0;
}
::-webkit-scrollbar {
background: transparent;
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: var(--theme--scrollbar_track);
}
::-webkit-scrollbar-corner {
background: var(--theme--scrollbar_track);
}
::-webkit-scrollbar-thumb {
background: var(--theme--scrollbar_thumb);
}
::-webkit-scrollbar-thumb:hover {
background: var(--theme--scrollbar_thumb-hover);
}

View File

@ -9,150 +9,13 @@
<body>
<header>
<p><img width="24" src="../../icons/colour.svg" /></p>
<h1><a href="#" data-target="library" data-active>library</a></h1>
<h1><a href="#" data-target="alerts">alerts</a></h1>
<h1><a href="#" data-target="documentation">documentation</a></h1>
<h1><a href="?" data-target="library" data-active>library</a></h1>
<h1><a href="?" data-target="alerts">alerts</a></h1>
<h1><a href="https://github.com/notion-enhancer/extension">github</a></h1>
<h1><a href="https://discord.gg/sFWPXtA">discord</a></h1>
</header>
<main>
<section data-container="library" data-active>
<article>
<div>
<label for="library.enable--ID1" class="library--toggle_label">
<h2 class="library--title">
custom inserts <span class="library--version">v0.2.0</span>
</h2>
<input type="checkbox" id="library.enable--ID1" checked />
<span class="library--toggle"></span>
</label>
<ul class="library--tags">
<li>#core</li>
<li>#extension</li>
<li>#customisation</li>
</ul>
<p class="library--description">
link files for small client-side tweaks. (not sure how to do something? check out
the
<a
href="https://github.com/notion-enhancer/notion-enhancer/blob/master/TWEAKS.md"
>tweaks</a
>
collection.)
</p>
<ul class="library--authors">
<li>
<a href="https://dragonwocky.me"
><img src="https://dragonwocky.me/avatar.jpg" /> <span>dragonwocky</span></a
>
</li>
<li>
<a href="https://dragonwocky.me"
><img src="https://dragonwocky.me/avatar.jpg" /> <span>dragonwocky</span></a
>
</li>
</ul>
</div>
</article>
<article>
<img
alt=""
class="library--preview"
src="https://raw.githubusercontent.com/notion-enhancer/notion-enhancer/dev/notion-enhancer%20v0.10.0%20banner.jpg"
/>
<div>
<label for="library.enable--ID2" class="library--toggle_label">
<h2 class="library--title">
dark+ <span class="library--version">v0.2.0</span>
</h2>
<input type="checkbox" id="library.enable--ID2" />
<span class="library--toggle"></span>
</label>
<ul class="library--tags">
<li>#core</li>
<li>#theme</li>
<li>#dark</li>
</ul>
<p class="library--description">a vivid-colour near-black theme</p>
<ul class="library--authors">
<li>
<a href="https://dragonwocky.me"
><img src="https://dragonwocky.me/avatar.jpg" /> <span>dragonwocky</span></a
>
</li>
</ul>
</div>
</article>
<article>
<div>
<label for="library.enable--ID3" class="library--toggle_label">
<h2 class="library--title">
custom inserts <span class="library--version">v0.2.0</span>
</h2>
<input type="checkbox" id="library.enable--ID3" checked />
<span class="library--toggle"></span>
</label>
<ul class="library--tags">
<li>#core</li>
<li>#extension</li>
<li>#customisation</li>
</ul>
<p class="library--description">
link files for small client-side tweaks. (not sure how to do something? check out
the
<a
href="https://github.com/notion-enhancer/notion-enhancer/blob/master/TWEAKS.md"
>tweaks</a
>
collection.)
</p>
<ul class="library--authors">
<li>
<a href="https://dragonwocky.me"
><img src="https://dragonwocky.me/avatar.jpg" /> <span>dragonwocky</span></a
>
</li>
<li>
<a href="https://dragonwocky.me"
><img src="https://dragonwocky.me/avatar.jpg" /> <span>dragonwocky</span></a
>
</li>
</ul>
</div>
</article>
<article>
<div>
<label for="library.enable--ID4" class="library--toggle_label">
<h2 class="library--title">
custom inserts <span class="library--version">v0.2.0</span>
</h2>
<input type="checkbox" id="library.enable--ID4" />
<span class="library--toggle"></span>
</label>
<ul class="library--tags">
<li>#core</li>
<li>#extension</li>
<li>#customisation</li>
</ul>
<p class="library--description">
link files for small client-side tweaks. (not sure how to do something? check out
the
<a
href="https://github.com/notion-enhancer/notion-enhancer/blob/master/TWEAKS.md"
>tweaks</a
>
collection.)
</p>
<ul class="library--authors">
<li>
<a href="https://dragonwocky.me"
><img src="https://dragonwocky.me/avatar.jpg" /> <span>dragonwocky</span></a
>
</li>
</ul>
</div>
</article>
</section>
<section data-container="alerts"></section>
<section data-container="documentation">documentation</section>
<section data-container="library" data-active></section>
</main>
<script src="./menu.js" type="module"></script>
</body>

View File

@ -6,7 +6,7 @@
'use strict';
import { web, fs, registry } from '../../helpers.js';
import { web, fs, registry, markdown } from '../../helpers.js';
for (let mod of await registry.get()) {
for (let sheet of mod.css?.menu || []) {
@ -14,20 +14,193 @@ for (let mod of await registry.get()) {
}
}
const tabs = ['library', 'alerts', 'documentation'].map((tab) => ({
title: document.querySelector(`header [data-target="${tab}"]`),
container: document.querySelector(`main [data-container="${tab}"]`),
}));
tabs.forEach((tab) => {
tab.title.addEventListener('click', (event) => {
tabs.forEach((_tab) => {
_tab.title.removeAttribute('data-active');
_tab.container.removeAttribute('data-active');
});
tab.title.dataset.active = true;
tab.container.dataset.active = true;
});
});
const components = {
preview({ preview }) {
if (!preview) return '';
const element = web.createElement(
`<img alt="" class="library--preview" src="${preview}" />`
);
return element;
},
name({ name, id, version }) {
const element = web.createElement(`<label for="enable--${id}" class="library--title library--toggle_label">
<input type="checkbox" id="enable--${id}" />
<h2><span>${name} <span class="library--version">v${version}</span></span>
<span class="library--toggle"></span></h2></label>`);
return element;
},
tags({ tags }) {
if (!tags || !tags.length) return '';
const element = web.createElement(
`<ul class="library--tags">${tags.map((tag) => `<li>#${tag}</li>`).join('')}</ul>`
);
return element;
},
description({ description }) {
const element = web.createElement(
`<p class="library--description">${markdown.renderInline(description)}</p>`
);
return element;
},
authors({ authors }) {
const element = web.createElement(
`<ul class="library--authors">${authors
.map(
(author) =>
`<li><a href="${author.url}"><img src="${author.icon}"/> <span>${author.name}</span></a></li>`
)
.join('')}</ul>`
);
return element;
},
expand({ id }) {
const element = web.createElement(`<p class="library--expand"><a href="?mod=${id}">
<!-- https://fontawesome.com/icons/long-arrow-alt-right?style=solid -->
<span><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor"
d="M313.941 216H12c-6.627 0-12 5.373-12 12v56c0 6.627 5.373 12 12 12h301.941v46.059c0 21.382 25.851 32.09 40.971 16.971l86.059-86.059c9.373-9.373 9.373-24.569 0-33.941l-86.059-86.059c-15.119-15.119-40.971-4.411-40.971 16.971V216z">
</path></svg></span> <span>settings & documentation</span></a></p>`);
return element;
},
toggle(id, { key, label, value }) {
const element = web.createElement(`<label for="toggle--${id}.${key}" class="library--toggle_label">
<input type="checkbox" id="toggle--${id}.${key}" />
<p><span>${label}</span><span class="library--toggle"></span></p></label>`);
return element;
},
select(id, { key, label, values }) {
const element = web.createElement(`<label for="select--${id}.${key}" class="library--select_label">
<p>${label}</p><p class="library--select"><span>
<!-- https://fontawesome.com/icons/caret-down?style=solid -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor" d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"></path></svg></span>
<select id="select--${id}.${key}">${values.map(
(value) => `<option value="${value}">${value}</option>`
)}</select></p></label>`);
return element;
},
text(id, { key, label, value }) {
const element = web.createElement(`<label for="text--${id}.${key}" class="library--text_label">
<p>${label}</p><textarea id="text--${id}.${key}" rows="1"></textarea></label>`);
element.querySelector('textarea').addEventListener('input', (ev) => {
ev.target.style.removeProperty('--txt--scroll-height');
ev.target.style.setProperty('--txt--scroll-height', ev.target.scrollHeight + 'px');
});
return element;
},
number(id, { key, label, value }) {
const element = web.createElement(`<label for="number--${id}.${key}" class="library--number_label">
<p>${label}</p><input id="number--${id}.${key}" type="number"></inpu></label>`);
return element;
},
file(id, { key, label, extensions }) {
const accept =
extensions && extensions.length ? ` accept="${extensions.join(',')}"` : '';
const element = web.createElement(`<label for="file--${id}.${key}" class="library--file_label">
<input type="file" id="file--${id}.${key}"${accept}/><p>${label}</p>
<p class="library--file"><span><!-- https://fontawesome.com/icons/file?style=solid -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor"
d="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm160-14.1v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z"
/></svg></span><span class="library--file_path">choose file...</span></p></label>`);
element.querySelector('input[type="file"]').addEventListener('change', (ev) => {
element.querySelector('.library--file_path').innerText = ev.target.files[0].name;
});
return element;
},
documentation_buttons({ _dir }) {
const element = web.createElement(`<p class="documentation--buttons">
<a href="?"><!-- https://fontawesome.com/icons/long-arrow-alt-left?style=solid -->
<span><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor"
d="M134.059 296H436c6.627 0 12-5.373 12-12v-56c0-6.627-5.373-12-12-12H134.059v-46.059c0-21.382-25.851-32.09-40.971-16.971L7.029 239.029c-9.373 9.373-9.373 24.569 0 33.941l86.059 86.059c15.119 15.119 40.971 4.411 40.971-16.971V296z">
</path></svg></span> <span>back to library</span></a>
<a href="https://github.com/notion-enhancer/extension/tree/main/repo/${encodeURIComponent(
_dir
)}"><!-- https://fontawesome.com/icons/code?style=solid -->
<span><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor"
d="M278.9 511.5l-61-17.7c-6.4-1.8-10-8.5-8.2-14.9L346.2 8.7c1.8-6.4 8.5-10 14.9-8.2l61 17.7c6.4 1.8 10 8.5 8.2 14.9L293.8 503.3c-1.9 6.4-8.5 10.1-14.9 8.2zm-114-112.2l43.5-46.4c4.6-4.9 4.3-12.7-.8-17.2L117 256l90.6-79.7c5.1-4.5 5.5-12.3.8-17.2l-43.5-46.4c-4.5-4.8-12.1-5.1-17-.5L3.8 247.2c-5.1 4.7-5.1 12.8 0 17.5l144.1 135.1c4.9 4.6 12.5 4.4 17-.5zm327.2.6l144.1-135.1c5.1-4.7 5.1-12.8 0-17.5L492.1 112.1c-4.8-4.5-12.4-4.3-17 .5L431.6 159c-4.6 4.9-4.3 12.7.8 17.2L523 256l-90.6 79.7c-5.1 4.5-5.5 12.3-.8 17.2l43.5 46.4c4.5 4.9 12.1 5.1 17 .6z">
</path></svg></span> <span>view source code</span></a>
</p>`);
return element;
},
},
generators = {
summary_card(mod) {
const article = web.createElement('<article class="library--summary_card"></article>'),
body = web.createElement('<div></div>');
article.append(components.preview(mod));
body.append(components.name(mod));
body.append(components.tags(mod));
body.append(components.description(mod));
body.append(components.authors(mod));
body.append(components.expand(mod));
article.append(body);
return article;
},
full_card(mod) {
const article = web.createElement('<article class="library--full_card"></article>'),
body = web.createElement('<div></div>');
article.append(components.preview(mod));
body.append(components.name(mod));
body.append(components.tags(mod));
body.append(components.description(mod));
body.append(components.authors(mod));
article.append(body);
if (mod.options && mod.options.length) {
const options = web.createElement(`<div class="library--options"></div>`);
mod.options.forEach((opt) => options.append(components[opt.type](mod.id, opt)));
article.append(options);
}
return article;
},
async documentation(mod) {
const content = (await fs.isFile(`repo/${mod._dir}/README.md`))
? markdown.render(await fs.getText(`repo/${mod._dir}/README.md`))
: '',
article = web.createElement(
`<article class="documentation--body">${content}</article>`
);
return article;
},
},
tabs = {
library: {
title: document.querySelector('header [data-target="library"]'),
async container() {
document.querySelector('[data-target][data-active]').removeAttribute('data-active');
this.title.dataset.active = true;
const $container = document.querySelector('[data-container]');
$container.dataset.container = 'library';
$container.innerHTML = '';
for (let mod of await registry.get()) $container.append(generators.summary_card(mod));
},
},
mod: {
title: document.querySelector('header [data-target="library"]'),
async container(mod) {
document.querySelector('[data-target][data-active]').removeAttribute('data-active');
this.title.dataset.active = true;
const $container = document.querySelector('[data-container]');
$container.dataset.container = 'page';
$container.innerHTML = '';
$container.append(components.documentation_buttons(mod));
$container.append(generators.full_card(mod));
$container.append(await generators.documentation(mod));
},
},
};
tabs.library.title.addEventListener('click', (ev) => tabs.library.container());
(async () => {
const search = new Map(
location.search
.slice(1)
.split('&')
.map((query) => query.split('='))
),
mod = (await registry.get()).find((mod) => mod.id === search.get('mod'));
if (mod) {
tabs.mod.container(mod);
} else tabs.library.container();
})();
// registry.errors().then((err) => {
// document.querySelector('[data-section="alerts"]').innerHTML = JSON.stringify(err);

View File

@ -1,8 +1,9 @@
{
"name": "menu",
"id": "a6621988-551d-495a-97d8-3c568bca2e9e",
"description": "the enhancer's graphical menu, related buttons and shortcuts.",
"version": "0.11.0",
"description": "the enhancer's [graphical](https://github.com) menu, related buttons and shortcuts.",
"preview": "https://raw.githubusercontent.com/notion-enhancer/notion-enhancer/dev/notion-enhancer%20v0.10.0%20banner.jpg",
"tags": ["core"],
"authors": [
{
@ -18,5 +19,37 @@
},
"js": {
"client": ["client.js"]
}
},
"options": [
{
"type": "toggle",
"key": "toggle",
"label": "toggle",
"value": true
},
{
"type": "select",
"key": "select",
"label": "select",
"values": ["option a", "option b", "option c"]
},
{
"type": "text",
"key": "text",
"label": "text",
"value": "default"
},
{
"type": "number",
"key": "number",
"label": "number",
"value": 0
},
{
"type": "file",
"key": "file",
"label": "file picker (.css only)",
"extensions": [".css"]
}
]
}

View File

@ -44,7 +44,7 @@ const enhancerMenu = {
return this._tab;
},
};
chrome.action.onClicked.addListener(enhancerMenu.open);
chrome.action.onClicked.addListener(() => enhancerMenu.open());
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
switch (request.type) {