Add: a draggable list to customize injection order (#1364)

+ implement messageBox.close()
+ fix require() with root urls in /dir/page.html
+ limit messageBox focus shift to config-dialog
+ flatten vendor dirs and simplify build-vendor:
  + replace the unicode symbol with ASCII `->`
  + flatten dirs by default to simplify writing the rules and improve their readability
  + rename and sort functions in the order they run
  + use `node-fetch` instead of the gargantuan `make-fetch-happen`
  + use `glob` which is already installed by other deps

Co-authored-by: tophf <tophf@gmx.com>
This commit is contained in:
eight 2022-01-14 20:44:48 +08:00 committed by GitHub
parent 8ddeef221b
commit ddc09f3511
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1303 additions and 1521 deletions

View File

@ -1667,6 +1667,14 @@
"styleIncludeLabel": { "styleIncludeLabel": {
"message": "Custom included sites" "message": "Custom included sites"
}, },
"styleInjectionOrder": {
"message": "Style injection order",
"description": "Tooltip for the button in the manager to open the dialog and also the title of this dialog"
},
"styleInjectionOrderHint": {
"message": "Drag'n'drop a style to change its position. Styles are injected sequentially in the order shown below so a style further down the list can override the earlier styles.",
"description": "Hint in the injection order dialog in the manager"
},
"styleExcludeLabel": { "styleExcludeLabel": {
"message": "Custom excluded sites" "message": "Custom excluded sites"
}, },

View File

@ -59,6 +59,7 @@ const styleMan = (() => {
const DELETE_IF_NULL = ['id', 'customName', 'md5Url', 'originalMd5']; const DELETE_IF_NULL = ['id', 'customName', 'md5Url', 'originalMd5'];
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */ /** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
let ready = init(); let ready = init();
let order = {};
chrome.runtime.onConnect.addListener(handleLivePreview); chrome.runtime.onConnect.addListener(handleLivePreview);
// function handleColorScheme() { // function handleColorScheme() {
@ -70,6 +71,17 @@ const styleMan = (() => {
} }
}); });
prefs.subscribe(['injectionOrder'], (key, value) => {
order = {};
value.forEach((uid, i) => {
const id = uuidIndex.get(uid);
if (id) {
order[id] = i;
}
});
msg.broadcast({method: 'styleSort', order});
});
//#endregion //#endregion
//#region Exports //#region Exports
@ -148,7 +160,11 @@ const styleMan = (() => {
async getSectionsByUrl(url, id, isInitialApply) { async getSectionsByUrl(url, id, isInitialApply) {
if (ready.then) await ready; if (ready.then) await ready;
if (isInitialApply && prefs.get('disableAll')) { if (isInitialApply && prefs.get('disableAll')) {
return {disableAll: true}; return {
cfg: {
disableAll: true,
},
};
} }
const sender = CHROME && this && this.sender || {}; const sender = CHROME && this && this.sender || {};
if (sender.frameId === 0) { if (sender.frameId === 0) {
@ -170,7 +186,7 @@ const styleMan = (() => {
} }
return id return id
? cache.sections[id] ? {[id]: cache.sections[id]} : {} ? cache.sections[id] ? {[id]: cache.sections[id]} : {}
: cache.sections; : Object.assign({cfg: {order}}, cache.sections);
}, },
/** @returns {Promise<StyleObj>} */ /** @returns {Promise<StyleObj>} */

View File

@ -56,6 +56,7 @@
return NOP; return NOP;
} }
return API.styles.getSectionsByUrl(url, id).then(sections => { return API.styles.getSectionsByUrl(url, id).then(sections => {
delete sections.cfg;
const tasks = []; const tasks = [];
for (const section of Object.values(sections)) { for (const section of Object.values(sections)) {
const styleId = section.id; const styleId = section.id;

View File

@ -13,11 +13,19 @@
let hasStyles = false; let hasStyles = false;
let isDisabled = false; let isDisabled = false;
let isTab = !chrome.tabs || location.pathname !== '/popup.html'; let isTab = !chrome.tabs || location.pathname !== '/popup.html';
let order = {};
const isFrame = window !== parent; const isFrame = window !== parent;
const isFrameAboutBlank = isFrame && location.href === 'about:blank'; const isFrameAboutBlank = isFrame && location.href === 'about:blank';
const isUnstylable = !chrome.app && document instanceof XMLDocument; const isUnstylable = !chrome.app && document instanceof XMLDocument;
const styleInjector = StyleInjector({ const styleInjector = StyleInjector({
compare: (a, b) => a.id - b.id, compare: (a, b) => {
const ia = order[a.id];
const ib = order[b.id];
if (ia === ib) return 0;
if (ia == null) return 1;
if (ib == null) return -1;
return ia - ib;
},
onUpdate: onInjectorUpdate, onUpdate: onInjectorUpdate,
}); });
// dynamic iframes don't have a URL yet so we'll use their parent's URL (hash isn't inherited) // dynamic iframes don't have a URL yet so we'll use their parent's URL (hash isn't inherited)
@ -100,7 +108,11 @@
parentStyles && await new Promise(onFrameElementInView) && parentStyles || parentStyles && await new Promise(onFrameElementInView) && parentStyles ||
!isFrameAboutBlank && chrome.app && !chrome.tabs && tryCatch(getStylesViaXhr) || !isFrameAboutBlank && chrome.app && !chrome.tabs && tryCatch(getStylesViaXhr) ||
await API.styles.getSectionsByUrl(matchUrl, null, true); await API.styles.getSectionsByUrl(matchUrl, null, true);
isDisabled = styles.disableAll; if (styles.cfg) {
isDisabled = styles.cfg.disableAll;
order = styles.cfg.order || {};
delete styles.cfg;
}
hasStyles = !isDisabled; hasStyles = !isDisabled;
if (hasStyles) { if (hasStyles) {
window[SYM] = styles; window[SYM] = styles;
@ -166,10 +178,16 @@
} }
break; break;
case 'styleSort':
order = request.order;
styleInjector.sort();
break;
case 'urlChanged': case 'urlChanged':
if (!hasStyles && isDisabled || matchUrl === request.url) break; if (!hasStyles && isDisabled || matchUrl === request.url) break;
matchUrl = request.url; matchUrl = request.url;
API.styles.getSectionsByUrl(matchUrl).then(sections => { API.styles.getSectionsByUrl(matchUrl).then(sections => {
delete sections.cfg;
hasStyles = true; hasStyles = true;
styleInjector.replace(sections); styleInjector.replace(sections);
}); });

View File

@ -78,6 +78,8 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
_addRemoveElements(enable); _addRemoveElements(enable);
if (enable) _toggleObservers(true); if (enable) _toggleObservers(true);
}, },
sort: _sort,
}; };
function _add(style) { function _add(style) {

View File

@ -0,0 +1,58 @@
#message-box.injection-order > div {
height: 100%;
max-width: 80vw;
}
#message-box.injection-order #message-box-contents {
padding: 0;
overflow: hidden;
display: flex;
flex-direction: column;
--border: 1px solid rgba(128, 128, 128, .25);
}
.injection-order header {
padding: 1rem;
width: 0;
min-width: 100%;
box-sizing: border-box;
}
.injection-order ol {
height: 100%;
padding: 1px 0 0; /* 1px for keyboard-focused element's outline */
margin: 0;
font-size: 14px;
overflow-y: auto;
border-top: var(--border);
}
.injection-order a {
display: block;
color: #000;
text-decoration: none;
transition: transform .25s ease-in-out;
z-index: 1;
user-select: none;
padding: 0.3em .5em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: move;
}
.injection-order a.enabled {
font-weight: bold;
}
.injection-order a:hover {
text-decoration: underline;
}
.injection-order a:not(:first-child) {
border-top: var(--border);
}
.injection-order a::before {
content: "\2261";
padding: 0.6em;
font-weight: normal;
}
.injection-order .draggable-list-target {
position: relative;
background: lightcyan;
transition: none;
z-index: 100;
}

View File

@ -0,0 +1,79 @@
/* global $create messageBoxProxy */// dom.js
/* global API */// msg.js
/* global DraggableList */
/* global prefs */
/* global t */// localization.js
'use strict';
/* exported InjectionOrder */
async function InjectionOrder(show = true) {
if (!show) {
return messageBoxProxy.close();
}
const entries = (await getOrderedStyles()).map(makeEntry);
const ol = $create('ol');
let maxTranslateY;
ol.append(...entries.map(l => l.el));
ol.on('d:dragstart', ({detail: d}) => {
d.origin.dataTransfer.setDragImage(new Image(), 0, 0);
maxTranslateY = ol.scrollHeight + ol.offsetTop - d.dragTarget.offsetHeight - d.dragTarget.offsetTop;
});
ol.on('d:dragmove', ({detail: d}) => {
d.origin.stopPropagation(); // preserves dropEffect
d.origin.dataTransfer.dropEffect = 'move';
const y = Math.min(d.currentPos.y - d.startPos.y, maxTranslateY);
d.dragTarget.style.transform = `translateY(${y}px)`;
});
ol.on('d:dragend', ({detail: d}) => {
const [item] = entries.splice(d.originalIndex, 1);
entries.splice(d.spliceIndex, 0, item);
ol.insertBefore(d.dragTarget, d.insertBefore);
prefs.set('injectionOrder', entries.map(l => l.style._id));
});
DraggableList(ol, {scrollContainer: ol});
await messageBoxProxy.show({
title: t('styleInjectionOrder'),
contents: $create('fragment', [
$create('header', t('styleInjectionOrderHint')),
ol,
]),
className: 'injection-order center-dialog',
blockScroll: true,
buttons: [t('confirmClose')],
});
async function getOrderedStyles() {
const [styles] = await Promise.all([
API.styles.getAll(),
prefs.ready,
]);
const styleSet = new Set(styles);
const uuidIndex = new Map();
for (const s of styleSet) {
uuidIndex.set(s._id, s);
}
const orderedStyles = [];
for (const uid of prefs.get('injectionOrder')) {
const s = uuidIndex.get(uid);
if (s) {
uuidIndex.delete(uid);
orderedStyles.push(s);
styleSet.delete(s);
}
}
orderedStyles.push(...styleSet);
return orderedStyles;
}
function makeEntry(style) {
return {
style,
el: $create('a', {
className: style.enabled ? 'enabled' : '',
href: '/edit.html?id=' + style.id,
target: '_blank',
}, style.name),
};
}
}

View File

@ -12,6 +12,19 @@ window.messageBox = {
_resolve: null, _resolve: null,
}; };
messageBox.close = async isAnimated => {
window.off('keydown', messageBox.listeners.key, true);
window.off('scroll', messageBox.listeners.scroll);
window.off('mouseup', messageBox.listeners.mouseUp);
window.off('mousemove', messageBox.listeners.mouseMove);
if (isAnimated) {
await animateElement(messageBox.element, 'fadeout');
}
messageBox.element.remove();
messageBox.element = null;
messageBox._resolve = null;
};
/** /**
* @param {Object} params * @param {Object} params
* @param {String} params.title * @param {String} params.title
@ -46,7 +59,8 @@ messageBox.show = async ({
messageBox._originalFocus = document.activeElement; messageBox._originalFocus = document.activeElement;
// focus the first focusable child but skip the first external link which is usually `feedback` // focus the first focusable child but skip the first external link which is usually `feedback`
if ((moveFocus(messageBox.element, 0) || {}).target === '_blank') { if ((moveFocus(messageBox.element, 0) || {}).target === '_blank' &&
/config-dialog/.test(className)) {
moveFocus(messageBox.element, 1); moveFocus(messageBox.element, 1);
} }
// suppress focus outline when invoked via click // suppress focus outline when invoked via click
@ -143,9 +157,7 @@ messageBox.show = async ({
function resolveWith(value) { function resolveWith(value) {
setTimeout(messageBox._resolve, 0, value); setTimeout(messageBox._resolve, 0, value);
unbindGlobalListeners(); messageBox.close(true);
animateElement(messageBox.element, 'fadeout')
.then(removeSelf);
if (messageBox.element.contains(document.activeElement)) { if (messageBox.element.contains(document.activeElement)) {
messageBox._originalFocus.focus(); messageBox._originalFocus.focus();
} }
@ -153,8 +165,7 @@ messageBox.show = async ({
function createElement() { function createElement() {
if (messageBox.element) { if (messageBox.element) {
unbindGlobalListeners(); messageBox.close();
removeSelf();
} }
const id = 'message-box'; const id = 'message-box';
messageBox.element = messageBox.element =
@ -186,19 +197,6 @@ messageBox.show = async ({
} }
window.on('keydown', messageBox.listeners.key, true); window.on('keydown', messageBox.listeners.key, true);
} }
function unbindGlobalListeners() {
window.off('keydown', messageBox.listeners.key, true);
window.off('scroll', messageBox.listeners.scroll);
window.off('mouseup', messageBox.listeners.mouseUp);
window.off('mousemove', messageBox.listeners.mouseMove);
}
function removeSelf() {
messageBox.element.remove();
messageBox.element = null;
messageBox._resolve = null;
}
}; };
/** /**

View File

@ -15,7 +15,7 @@
window.on('click', showTooltipNote); window.on('click', showTooltipNote);
window.on('resize', () => debounce(addTooltipsToEllipsized, 100)); window.on('resize', () => debounce(addTooltipsToEllipsized, 100));
// Removing transition-suppressor rule // Removing transition-suppressor rule
const {sheet} = $('link[href^="global.css"]'); const {sheet} = $('link[href$="global.css"]');
for (let i = 0, rule; (rule = sheet.cssRules[i]); i++) { for (let i = 0, rule; (rule = sheet.cssRules[i]); i++) {
if (/#\\1\s?transition-suppressor/.test(rule.selectorText)) { if (/#\\1\s?transition-suppressor/.test(rule.selectorText)) {
sheet.deleteRule(i); sheet.deleteRule(i);

View File

@ -86,7 +86,7 @@
const tag = isCss ? 'link' : 'script'; const tag = isCss ? 'link' : 'script';
const attr = isCss ? 'href' : 'src'; const attr = isCss ? 'href' : 'src';
if (!isCss && !url.endsWith('.js')) url += '.js'; if (!isCss && !url.endsWith('.js')) url += '.js';
if (url.startsWith('/')) url = url.slice(1); if (url[0] === '/' && location.pathname.indexOf('/', 1) < 0) url = url.slice(1);
let el = document.head.querySelector(`${tag}[${attr}$="${url}"]`); let el = document.head.querySelector(`${tag}[${attr}$="${url}"]`);
if (!el) { if (!el) {
el = document.createElement(tag); el = document.createElement(tag);

View File

@ -17,6 +17,7 @@
* @namespace PrefsValues * @namespace PrefsValues
*/ */
const defaults = { const defaults = {
// TODO: sort everything aphabetically
'openEditInWindow': false, // new editor opens in a own browser window 'openEditInWindow': false, // new editor opens in a own browser window
'openEditInWindow.popup': false, // new editor opens in a simplified browser window without omnibox 'openEditInWindow.popup': false, // new editor opens in a simplified browser window without omnibox
'windowPosition': {}, // detached window position 'windowPosition': {}, // detached window position
@ -132,6 +133,8 @@
'popupWidth': 246, // popup width in pixels 'popupWidth': 246, // popup width in pixels
'updateInterval': 24, // user-style automatic update interval, hours (0 = disable) 'updateInterval': 24, // user-style automatic update interval, hours (0 = disable)
'injectionOrder': [],
}; };
const knownKeys = Object.keys(defaults); const knownKeys = Object.keys(defaults);
/** @type {PrefsValues} */ /** @type {PrefsValues} */

View File

@ -313,8 +313,8 @@
</a> </a>
</label> </label>
</div> </div>
<button id="manage-options-button" i18n-text="openOptions"></button> <button id="manage-options-button" i18n-text="openOptions"></button>
<button id="injection-order-button" i18n-title="styleInjectionOrder">↑↓</button>
</details> </details>
</div> </div>

View File

@ -1165,3 +1165,4 @@ a:hover {
margin-left: -2px; margin-left: -2px;
} }
} }

View File

@ -61,6 +61,7 @@ newUI.renderClass();
installed.on('mouseover', Events.lazyAddEntryTitle, {passive: true}); installed.on('mouseover', Events.lazyAddEntryTitle, {passive: true});
installed.on('mouseout', Events.lazyAddEntryTitle, {passive: true}); installed.on('mouseout', Events.lazyAddEntryTitle, {passive: true});
$('#manage-options-button').onclick = () => router.updateHash('#stylus-options'); $('#manage-options-button').onclick = () => router.updateHash('#stylus-options');
$('#injection-order-button').onclick = () => router.updateHash('#injection-order');
$('#sync-styles').onclick = () => router.updateHash('#stylus-options'); $('#sync-styles').onclick = () => router.updateHash('#stylus-options');
$$('#header a[href^="http"]').forEach(a => (a.onclick = Events.external)); $$('#header a[href^="http"]').forEach(a => (a.onclick = Events.external));
document.on('visibilitychange', handleVisibilityChange); document.on('visibilitychange', handleVisibilityChange);
@ -101,6 +102,7 @@ newUI.renderClass();
msg.onExtension(onRuntimeMessage); msg.onExtension(onRuntimeMessage);
window.on('closeOptions', () => router.updateHash('')); window.on('closeOptions', () => router.updateHash(''));
router.watch({hash: '#stylus-options'}, toggleEmbeddedOptions); router.watch({hash: '#stylus-options'}, toggleEmbeddedOptions);
router.watch({hash: '#injection-order'}, toggleInjectionOrder);
function onRuntimeMessage(msg) { function onRuntimeMessage(msg) {
switch (msg.method) { switch (msg.method) {
@ -137,3 +139,18 @@ async function toggleEmbeddedOptions(state) {
el.remove(); el.remove();
} }
} }
async function toggleInjectionOrder(state) {
const shown = $('.injection-order');
if (state && !shown) {
await require([
'/vendor/draggable-list/draggable-list.iife.min.js',
'/injection-order/injection-order.css',
'/injection-order/injection-order', /* global InjectionOrder */
]);
await InjectionOrder();
router.updateHash('');
} else if (!state && shown) {
await InjectionOrder(false);
}
}

2113
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@
"codemirror": "WARNING! Always use an exact version and test it for a while before releasing" "codemirror": "WARNING! Always use an exact version and test it for a while before releasing"
}, },
"dependencies": { "dependencies": {
"@eight04/draggable-list": "^0.3.0",
"codemirror": "5.63.3", "codemirror": "5.63.3",
"db-to-cloud": "^0.7.0", "db-to-cloud": "^0.7.0",
"jsonlint": "^1.6.3", "jsonlint": "^1.6.3",
@ -21,12 +22,11 @@
}, },
"devDependencies": { "devDependencies": {
"archiver": "^4.0.1", "archiver": "^4.0.1",
"endent": "^1.4.0",
"eslint": "^7.20.0", "eslint": "^7.20.0",
"fs-extra": "^9.0.0", "fs-extra": "^9.0.0",
"make-fetch-happen": "^8.0.7", "glob": "^7.2.0",
"node-fetch": "^2.6.6",
"sync-version": "^1.0.1", "sync-version": "^1.0.1",
"tiny-glob": "^0.2.6",
"web-ext": "^6.5.0" "web-ext": "^6.5.0"
}, },
"scripts": { "scripts": {

View File

@ -1,14 +1,16 @@
/* eslint-env node */
'use strict'; 'use strict';
const fetch = require('node-fetch');
const fs = require('fs');
const fse = require('fs-extra');
const glob = require('glob').sync;
const path = require('path'); const path = require('path');
const endent = require('endent'); const KEEP_DIRECTORIES = null;
const fetch = require('make-fetch-happen');
const fse = require('fs-extra');
const glob = require('tiny-glob');
const files = { const files = {
'codemirror': [ 'codemirror': [
KEEP_DIRECTORIES,
'addon/comment/comment.js', 'addon/comment/comment.js',
'addon/dialog', 'addon/dialog',
'addon/edit/closebrackets.js', 'addon/edit/closebrackets.js',
@ -35,30 +37,33 @@ const files = {
'theme/*', 'theme/*',
], ],
'jsonlint': [ 'jsonlint': [
'lib/jsonlint.js → jsonlint.js', 'lib/jsonlint.js',
'README.md LICENSE', 'README.md -> LICENSE',
], ],
'less-bundle': [ 'less-bundle': [
'dist/less.min.js → less.min.js', 'dist/less.min.js',
], ],
'lz-string-unsafe': [ 'lz-string-unsafe': [
'lz-string-unsafe.min.js', 'lz-string-unsafe.min.js',
], ],
'stylelint-bundle': [ 'stylelint-bundle': [
'dist/stylelint-bundle.min.js → stylelint-bundle.min.js', 'dist/stylelint-bundle.min.js',
'https://github.com/stylelint/stylelint/raw/{VERSION}/LICENSE → LICENSE', 'https://github.com/stylelint/stylelint/raw/{VERSION}/LICENSE',
], ],
'stylus-lang-bundle': [ 'stylus-lang-bundle': [
'dist/stylus-renderer.min.js → stylus-renderer.min.js', 'dist/stylus-renderer.min.js',
], ],
'usercss-meta': [ 'usercss-meta': [
'dist/usercss-meta.min.js → usercss-meta.min.js', 'dist/usercss-meta.min.js',
], ],
'db-to-cloud': [ 'db-to-cloud': [
'dist/db-to-cloud.min.js → db-to-cloud.min.js', 'dist/db-to-cloud.min.js',
], ],
'webext-launch-web-auth-flow': [ 'webext-launch-web-auth-flow': [
'dist/webext-launch-web-auth-flow.min.js → webext-launch-web-auth-flow.min.js', 'dist/webext-launch-web-auth-flow.min.js',
],
'@eight04/draggable-list': [
'dist/draggable-list.iife.min.js',
], ],
}; };
@ -66,96 +71,97 @@ main().catch(console.error);
async function main() { async function main() {
fse.emptyDirSync('vendor'); fse.emptyDirSync('vendor');
for (const pkg in files) { await Promise.all(Object.keys(files).map(async pkg => {
console.log('\x1b[32m%s\x1b[0m', `Building ${pkg}...`); console.log(`Building ${pkg}...`);
// other files const pkgName = getFileName(pkg);
const [fetched, copied] = await buildFiles(pkg, files[pkg]); const flatPkg = pkg === pkgName || files[pkgName]
// README ? pkg.replace(/\//g, '-')
await fse.outputFile(`vendor/${pkg}/README.md`, generateReadme(pkg, fetched, copied)); : pkgName;
// LICENSE const res = await buildFiles(pkg, flatPkg, files[pkg]);
await copyLicense(pkg); buildLicense(pkg, flatPkg);
} buildReadme(pkg, flatPkg, res);
console.log('\x1b[32m%s\x1b[0m', 'updating codemirror themes list...'); }));
await fse.outputFile('edit/codemirror-themes.js', await generateThemeList()); console.log('Building CodeMirror theme list...');
buildThemeList();
} }
async function generateThemeList() { async function buildFiles(pkg, flatPkg, patterns) {
const themes = (await fse.readdir('vendor/codemirror/theme')) const keepDirs = patterns.includes(KEEP_DIRECTORIES);
.filter(name => name.endsWith('.css')) let fetched = '';
.map(name => name.replace('.css', '')) let copied = '';
.sort(); for (let pattern of patterns) {
return endent` if (pattern === KEEP_DIRECTORIES) continue;
pattern = pattern.replace('{VERSION}', require(`${pkg}/package.json`).version);
const [src, dest = !keepDirs && getFileName(src)] = pattern.split(/\s*->\s*/);
if (/^https?:/.test(src)) {
fse.outputFileSync(`vendor/${flatPkg}/${dest}`, await (await fetch(src)).text());
fetched += `* ${dest}: ${src}\n`;
} else {
const files = glob(`node_modules/${pkg}/${src}`);
if (!files.length) {
throw new Error(`Pattern ${src} matches no files`);
}
for (const file of files) {
fse.copySync(file, dest
? `vendor/${flatPkg}/${dest}`
: `vendor/${path.relative('node_modules', file).replace(pkg + '/', flatPkg + '/')}`);
const relSrc = path.relative(`node_modules/${pkg}`, file).replace(/\\/g, '/');
copied += dest && dest !== relSrc
? `* ${dest}: ${
keepDirs || getFileName(dest) !== getFileName(relSrc)
? relSrc
: relSrc.replace(/[^/]+$/, '*')
}\n`
: `* ${relSrc}\n`;
}
}
}
return {fetched, copied};
}
function buildLicense(pkg, flatPkg) {
const LICENSE = `vendor/${flatPkg}/LICENSE`;
if (!fs.existsSync(LICENSE)) {
const [src] = glob(`node_modules/${pkg}/LICEN[SC]E*`);
if (!src) throw new Error(`Cannot find license file for ${pkg}`);
fse.copySync(src, LICENSE);
}
}
function buildReadme(pkg, flatPkg, {fetched, copied}) {
const {name, version} = require(`${pkg}/package.json`);
fse.outputFileSync(`vendor/${flatPkg}/README.md`, [
`## ${name} v${version}`,
fetched && `Files downloaded from URL:\n${fetched}`,
copied && `Files copied from NPM (node_modules):\n${copied}`,
].filter(Boolean).join('\n\n'));
}
function buildThemeList() {
fse.outputFileSync('edit/codemirror-themes.js', deindent(`\
/* Do not edit. This file is auto-generated by build-vendor.js */ /* Do not edit. This file is auto-generated by build-vendor.js */
'use strict'; 'use strict';
/* exported CODEMIRROR_THEMES */ /* exported CODEMIRROR_THEMES */
const CODEMIRROR_THEMES = [ const CODEMIRROR_THEMES = [
${ ${
themes.map(t => ` '${t.replace(/'/g, '\\$&')}',\n`).join('') fs.readdirSync('vendor/codemirror/theme')
}]; .filter(name => name.endsWith('.css'))
` + '\n'; .map(name => name.replace('.css', ''))
} .sort()
.map(t => ` '${t.replace(/'/g, '\\$&')}',`).join('\n')
async function copyLicense(pkg) {
try {
await fse.access(`vendor/${pkg}/LICENSE`);
return;
} catch (err) {
// pass
}
for (const file of await glob(`node_modules/${pkg}/LICEN[SC]E*`)) {
await fse.copy(file, `vendor/${pkg}/LICENSE`);
return;
}
throw new Error(`cannot find license file for ${pkg}`);
}
async function buildFiles(pkg, patterns) {
const fetchedFiles = [];
const copiedFiles = [];
for (let pattern of patterns) {
pattern = pattern.replace('{VERSION}', require(`${pkg}/package.json`).version);
const [src, dest] = pattern.split(/\s*→\s*/);
if (src.startsWith('http')) {
const content = await (await fetch(src)).text();
await fse.outputFile(`vendor/${pkg}/${dest}`, content);
fetchedFiles.push([src, dest]);
} else {
let dirty = false;
for (const file of await glob(`node_modules/${pkg}/${src}`)) {
if (dest) {
await fse.copy(file, `vendor/${pkg}/${dest}`);
} else {
await fse.copy(file, path.join('vendor', path.relative('node_modules', file)));
}
copiedFiles.push([path.relative(`node_modules/${pkg}`, file), dest]);
dirty = true;
}
if (!dirty) {
throw new Error(`Pattern ${src} matches no files`);
}
} }
} ];
return [fetchedFiles, copiedFiles]; `));
} }
function generateReadme(lib, fetched, copied) { function deindent(str) {
const pkg = require(`${lib}/package.json`); const indent = str.match(/^\s*/)[0];
let txt = `## ${pkg.name} v${pkg.version}\n\n`; return indent
if (fetched.length) { ? str.replace(new RegExp('^' + indent, 'gm'), '')
txt += `Following files are downloaded from HTTP:\n\n${generateList(fetched)}\n\n`; : str;
}
if (copied.length) {
txt += `Following files are copied from npm (node_modules):\n\n${generateList(copied)}\n`;
}
return txt;
} }
function generateList(list) { function getFileName(path) {
return list.map(([src, dest]) => { return path.split('/').pop();
if (dest) {
return `* ${dest}: ${src}`;
}
return `* ${src}`;
}).join('\n');
} }

View File

@ -1,99 +1,98 @@
## codemirror v5.63.3 ## codemirror v5.63.3
Following files are copied from npm (node_modules): Files copied from NPM (node_modules):
* addon/comment/comment.js
* addon\comment\comment.js * addon/dialog
* addon\dialog * addon/edit/closebrackets.js
* addon\edit\closebrackets.js * addon/edit/matchbrackets.js
* addon\edit\matchbrackets.js * addon/fold/brace-fold.js
* addon\fold\brace-fold.js * addon/fold/comment-fold.js
* addon\fold\comment-fold.js * addon/fold/foldcode.js
* addon\fold\foldcode.js * addon/fold/foldgutter.css
* addon\fold\foldgutter.css * addon/fold/foldgutter.js
* addon\fold\foldgutter.js * addon/fold/indent-fold.js
* addon\fold\indent-fold.js * addon/hint/css-hint.js
* addon\hint\css-hint.js * addon/hint/show-hint.css
* addon\hint\show-hint.css * addon/hint/show-hint.js
* addon\hint\show-hint.js * addon/lint/css-lint.js
* addon\lint\css-lint.js * addon/lint/json-lint.js
* addon\lint\json-lint.js * addon/lint/lint.css
* addon\lint\lint.css * addon/lint/lint.js
* addon\lint\lint.js * addon/scroll/annotatescrollbar.js
* addon\scroll\annotatescrollbar.js * addon/search/matchesonscrollbar.css
* addon\search\matchesonscrollbar.css * addon/search/matchesonscrollbar.js
* addon\search\matchesonscrollbar.js * addon/search/searchcursor.js
* addon\search\searchcursor.js * addon/selection/active-line.js
* addon\selection\active-line.js * keymap/emacs.js
* keymap\emacs.js * keymap/sublime.js
* keymap\sublime.js * keymap/vim.js
* keymap\vim.js * lib/codemirror.css
* lib\codemirror.css * lib/codemirror.js
* lib\codemirror.js * mode/css
* mode\css * mode/javascript
* mode\javascript * mode/stylus
* mode\stylus * theme/3024-day.css
* theme\3024-day.css * theme/3024-night.css
* theme\3024-night.css * theme/abbott.css
* theme\abbott.css * theme/abcdef.css
* theme\abcdef.css * theme/ambiance-mobile.css
* theme\ambiance-mobile.css * theme/ambiance.css
* theme\ambiance.css * theme/ayu-dark.css
* theme\ayu-dark.css * theme/ayu-mirage.css
* theme\ayu-mirage.css * theme/base16-dark.css
* theme\base16-dark.css * theme/base16-light.css
* theme\base16-light.css * theme/bespin.css
* theme\bespin.css * theme/blackboard.css
* theme\blackboard.css * theme/cobalt.css
* theme\cobalt.css * theme/colorforth.css
* theme\colorforth.css * theme/darcula.css
* theme\darcula.css * theme/dracula.css
* theme\dracula.css * theme/duotone-dark.css
* theme\duotone-dark.css * theme/duotone-light.css
* theme\duotone-light.css * theme/eclipse.css
* theme\eclipse.css * theme/elegant.css
* theme\elegant.css * theme/erlang-dark.css
* theme\erlang-dark.css * theme/gruvbox-dark.css
* theme\gruvbox-dark.css * theme/hopscotch.css
* theme\hopscotch.css * theme/icecoder.css
* theme\icecoder.css * theme/idea.css
* theme\idea.css * theme/isotope.css
* theme\isotope.css * theme/juejin.css
* theme\juejin.css * theme/lesser-dark.css
* theme\lesser-dark.css * theme/liquibyte.css
* theme\liquibyte.css * theme/lucario.css
* theme\lucario.css * theme/material-darker.css
* theme\material-darker.css * theme/material-ocean.css
* theme\material-ocean.css * theme/material-palenight.css
* theme\material-palenight.css * theme/material.css
* theme\material.css * theme/mbo.css
* theme\mbo.css * theme/mdn-like.css
* theme\mdn-like.css * theme/midnight.css
* theme\midnight.css * theme/monokai.css
* theme\monokai.css * theme/moxer.css
* theme\moxer.css * theme/neat.css
* theme\neat.css * theme/neo.css
* theme\neo.css * theme/night.css
* theme\night.css * theme/nord.css
* theme\nord.css * theme/oceanic-next.css
* theme\oceanic-next.css * theme/panda-syntax.css
* theme\panda-syntax.css * theme/paraiso-dark.css
* theme\paraiso-dark.css * theme/paraiso-light.css
* theme\paraiso-light.css * theme/pastel-on-dark.css
* theme\pastel-on-dark.css * theme/railscasts.css
* theme\railscasts.css * theme/rubyblue.css
* theme\rubyblue.css * theme/seti.css
* theme\seti.css * theme/shadowfox.css
* theme\shadowfox.css * theme/solarized.css
* theme\solarized.css * theme/ssms.css
* theme\ssms.css * theme/the-matrix.css
* theme\the-matrix.css * theme/tomorrow-night-bright.css
* theme\tomorrow-night-bright.css * theme/tomorrow-night-eighties.css
* theme\tomorrow-night-eighties.css * theme/ttcn.css
* theme\ttcn.css * theme/twilight.css
* theme\twilight.css * theme/vibrant-ink.css
* theme\vibrant-ink.css * theme/xq-dark.css
* theme\xq-dark.css * theme/xq-light.css
* theme\xq-light.css * theme/yeti.css
* theme\yeti.css * theme/yonce.css
* theme\yonce.css * theme/zenburn.css
* theme\zenburn.css

View File

@ -1,5 +1,4 @@
## db-to-cloud v0.7.0 ## db-to-cloud v0.7.0
Following files are copied from npm (node_modules): Files copied from NPM (node_modules):
* db-to-cloud.min.js: dist/*
* db-to-cloud.min.js: dist\db-to-cloud.min.js

22
vendor/draggable-list/LICENSE vendored Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2021 eight
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

4
vendor/draggable-list/README.md vendored Normal file
View File

@ -0,0 +1,4 @@
## @eight04/draggable-list v0.3.0
Files copied from NPM (node_modules):
* draggable-list.iife.min.js: dist/*

View File

@ -0,0 +1 @@
var DraggableList=function(){"use strict";const t="draggable-list-transformed";return function(e,{bound:r,scrollContainer:n}={}){for(const t of e.children)t.draggable=!0;new MutationObserver((t=>{for(const e of t)for(const t of e.addedNodes)t.draggable=!0})).observe(e,{childList:!0});let a=null,o=0,s=0,l=null,d=[],i=null,c=!1,g=0;function f(t,r,n){const o={origin:t,startPos:a,currentPos:l,dragTarget:i};n&&Object.assign(o,n),e.dispatchEvent(new CustomEvent(r,{detail:o}))}e.addEventListener("dragstart",(t=>{if(t.target.parentNode!==e)return;i=t.target,c=!1;const r=n?n.scrollLeft:0,u=n?n.scrollTop:0;a={x:t.pageX+r,y:t.pageY+u},o=[...e.children].indexOf(t.target),s=o,l=a,d=[...e.children].map((t=>{const e=t.getBoundingClientRect();return{top:e.top+window.scrollY+u,bottom:e.bottom+window.scrollY+u}})),g=o+1<d.length?d[o+1].top-d[o].top:o>0?d[o].bottom-d[o-1].bottom:0,i.classList.add("draggable-list-target"),e.classList.add("draggable-list-dragging"),f(t,"d:dragstart")})),e.addEventListener("dragenter",(t=>{i&&(t.preventDefault(),f(t,"d:dragmove"))})),e.addEventListener("dragover",(a=>{if(!i)return;a.preventDefault();const c=n?n.scrollLeft:0,u=n?n.scrollTop:0,p={x:a.pageX+c,y:a.pageY+u},m=function(t,e,r,n){if(r<t[0].top&&n)return e;for(let n=0;n<e;n++)if(!(t[n].bottom<r))return n;if(r>t[t.length-1].bottom&&n)return e;for(let n=t.length-1;n>e;n--)if(!(t[n].top>r))return n;return e}(d,o,p.y,r);!function(e,r,n,a,o){function s(r,n,a,o){for(let s=n;s<=a;s++)r&&!e[s].classList.contains(t)?(e[s].classList.add(t),e[s].style.transform=o):!r&&e[s].classList.contains(t)&&(e[s].classList.remove(t),e[s].style="")}a>n?(s(!1,n,Math.min(r-1,a-1)),r<e.length-1&&s(!0,Math.max(n+1,r+1),a,"translateY(".concat(-o,"px)"))):(s(!1,Math.max(r+1,a+1),n),r>0&&s(!0,a,Math.min(n-1,r-1),"translateY(".concat(o,"px)")))}(e.children,o,s,m,g),s=m,l=p,f(a,"d:dragmove")})),document.addEventListener("dragend",(r=>{if(i){for(const r of e.children)r.classList.remove(t),r.style="";i.classList.remove("draggable-list-target"),e.classList.remove("draggable-list-dragging"),f(r,"d:dragend",{originalIndex:o,spliceIndex:s,insertBefore:s<o?e.children[s]:e.children[s+1],dropped:c}),i=null}})),e.addEventListener("drop",(t=>{i&&(c=!0,t.preventDefault())}))}}();

View File

@ -1,6 +1,5 @@
## jsonlint v1.6.3 ## jsonlint v1.6.3
Following files are copied from npm (node_modules): Files copied from NPM (node_modules):
* jsonlint.js: lib/*
* jsonlint.js: lib\jsonlint.js
* LICENSE: README.md * LICENSE: README.md

View File

@ -1,5 +1,4 @@
## less-bundle v0.1.0 ## less-bundle v0.1.0
Following files are copied from npm (node_modules): Files copied from NPM (node_modules):
* less.min.js: dist/*
* less.min.js: dist\less.min.js

View File

@ -1,5 +1,4 @@
## lz-string-unsafe v1.4.4-fork-1 ## lz-string-unsafe v1.4.4-fork-1
Following files are copied from npm (node_modules): Files copied from NPM (node_modules):
* lz-string-unsafe.min.js * lz-string-unsafe.min.js

View File

@ -1,9 +1,8 @@
## stylelint-bundle v13.8.0 ## stylelint-bundle v13.8.0
Following files are downloaded from HTTP: Files downloaded from URL:
* LICENSE: https://github.com/stylelint/stylelint/raw/13.8.0/LICENSE * LICENSE: https://github.com/stylelint/stylelint/raw/13.8.0/LICENSE
Following files are copied from npm (node_modules):
* stylelint-bundle.min.js: dist\stylelint-bundle.min.js Files copied from NPM (node_modules):
* stylelint-bundle.min.js: dist/*

View File

@ -1,5 +1,4 @@
## stylus-lang-bundle v0.54.7 ## stylus-lang-bundle v0.54.7
Following files are copied from npm (node_modules): Files copied from NPM (node_modules):
* stylus-renderer.min.js: dist/*
* stylus-renderer.min.js: dist\stylus-renderer.min.js

View File

@ -1,5 +1,4 @@
## usercss-meta v0.12.0 ## usercss-meta v0.12.0
Following files are copied from npm (node_modules): Files copied from NPM (node_modules):
* usercss-meta.min.js: dist/*
* usercss-meta.min.js: dist\usercss-meta.min.js

View File

@ -1,5 +1,4 @@
## webext-launch-web-auth-flow v0.1.1 ## webext-launch-web-auth-flow v0.1.1
Following files are copied from npm (node_modules): Files copied from NPM (node_modules):
* webext-launch-web-auth-flow.min.js: dist/*
* webext-launch-web-auth-flow.min.js: dist\webext-launch-web-auth-flow.min.js