Compare commits

...

13 Commits

Author SHA1 Message Date
Wenzheng Tang 8e3dd94c12
feat: add @icon metadata
Merge pull request #125 from Wupb/patch-1
2021-06-18 18:31:05 -04:00
Wupb b1a3456646
Update @icon metadata 2021-06-18 13:09:15 -07:00
Wupb d269ae9be9
Add @icon metadata 2021-06-18 11:46:01 -07:00
Xmader 20298e5c0e
v0.24.1 2021-06-06 12:48:20 -04:00
Xmader 49880d63da
fix: mp3 & midi download 2021-06-06 12:47:21 -04:00
Xmader 0c2106993d
chore: source tarball name consistency 2021-05-21 17:55:14 -04:00
Xmader 3ac3a51ad1
v0.24.0 2021-05-21 17:44:53 -04:00
Xmader 9c05dd5d3d
fix: btn position when logged in 2021-05-21 17:41:18 -04:00
Xmader ae08ff847a
chore: .eslintrc 2021-05-21 17:40:52 -04:00
Xmader 8cc089357f
fix: default btn position 2021-05-21 16:50:04 -04:00
Xmader 4e92c06791
refactor: btn color theme 2021-05-19 02:10:28 -04:00
Xmader 5d1639dddf
feat: `View in LibreScore` btn light color 2021-05-19 02:02:52 -04:00
Xmader 88fdfff054
feat: match the new design 2021-05-19 01:59:10 -04:00
10 changed files with 92 additions and 40 deletions

View File

@ -20,6 +20,8 @@
"no-useless-constructor": "off", "no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": "error", "@typescript-eslint/no-useless-constructor": "error",
"no-dupe-class-members": "off", "no-dupe-class-members": "off",
"no-void": "off",
"no-use-before-define": "off",
"@typescript-eslint/no-dupe-class-members": "error", "@typescript-eslint/no-dupe-class-members": "error",
"@typescript-eslint/no-floating-promises": "warn", "@typescript-eslint/no-floating-promises": "warn",
"@typescript-eslint/member-delimiter-style": "warn", "@typescript-eslint/member-delimiter-style": "warn",

View File

@ -78,7 +78,7 @@ jobs:
cp dist/main.js $ARTIFACTS_DIR/musescore-downloader.user.js cp dist/main.js $ARTIFACTS_DIR/musescore-downloader.user.js
cp dist/ext.zip $ARTIFACTS_DIR/musescore-downloader.webextension.zip cp dist/ext.zip $ARTIFACTS_DIR/musescore-downloader.webextension.zip
wget -q $CHROME_EXT_URL -P $ARTIFACTS_DIR/ wget -q $CHROME_EXT_URL -P $ARTIFACTS_DIR/
wget -q https://github.com/Xmader/musescore-downloader/archive/$REF.tar.gz -P $ARTIFACTS_DIR/ wget -q https://github.com/Xmader/musescore-downloader/archive/$REF.tar.gz -O $ARTIFACTS_DIR/source.tar.gz
- run: bash ./.github/workflows/get-signed-ext.sh - run: bash ./.github/workflows/get-signed-ext.sh
env: env:
EXT_ID: musescore-downloader EXT_ID: musescore-downloader

49
dist/main.js vendored
View File

@ -5,7 +5,7 @@
// @supportURL https://github.com/Xmader/musescore-downloader/issues // @supportURL https://github.com/Xmader/musescore-downloader/issues
// @updateURL https://msdl.librescore.org/install.user.js // @updateURL https://msdl.librescore.org/install.user.js
// @downloadURL https://msdl.librescore.org/install.user.js // @downloadURL https://msdl.librescore.org/install.user.js
// @version 0.23.15 // @version 0.24.1
// @description download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱 // @description download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱
// @author Xmader // @author Xmader
// @match https://musescore.com/*/* // @match https://musescore.com/*/*
@ -26575,7 +26575,7 @@ Please pipe the document into a Node stream.\
} }
/* eslint-disable no-extend-native */ /* eslint-disable no-extend-native */
const TYPE_REG = /id=(\d+)&type=(img|mp3|midi)/; const TYPE_REG = /type=(img|mp3|midi)/;
/** /**
* I know this is super hacky. * I know this is super hacky.
*/ */
@ -26596,8 +26596,9 @@ Please pipe the document into a Node stream.\
const token = (_a = init === null || init === void 0 ? void 0 : init.headers) === null || _a === void 0 ? void 0 : _a.Authorization; const token = (_a = init === null || init === void 0 ? void 0 : init.headers) === null || _a === void 0 ? void 0 : _a.Authorization;
if (typeof url === 'string' && token) { if (typeof url === 'string' && token) {
const m = url.match(TYPE_REG); const m = url.match(TYPE_REG);
console$1.debug(url, token, m);
if (m) { if (m) {
const type = m[2]; const type = m[1];
// eslint-disable-next-line no-unused-expressions // eslint-disable-next-line no-unused-expressions
(_b = l[type]) === null || _b === void 0 ? void 0 : _b.call(l, token); (_b = l[type]) === null || _b === void 0 ? void 0 : _b.call(l, token);
} }
@ -27056,7 +27057,7 @@ Please pipe the document into a Node stream.\
return _getLink(res); return _getLink(res);
}); });
var btnListCss = "div {\n width: 422px;\n right: 0;\n margin: 0 18px 18px 0;\n\n text-align: center;\n align-items: center;\n font-family: 'Open Sans', 'Roboto', 'Helvetica neue', Helvetica, sans-serif;\n position: absolute;\n z-index: 9999;\n background: #f6f6f6;\n min-width: 230px;\n\n /* pass the scroll event through the btns background */\n pointer-events: none;\n}\n\n@media screen and (max-width: 950px) {\n div {\n width: auto !important;\n }\n}\n\nbutton {\n width: 205px !important;\n min-width: 205px;\n height: 38px;\n\n color: #fff;\n background: #1f74bd;\n\n cursor: pointer;\n pointer-events: auto;\n\n margin-bottom: 4px;\n margin-right: 4px;\n padding: 4px 12px;\n\n justify-content: start;\n align-self: center;\n\n font-size: 16px;\n border-radius: 2px;\n border: 0;\n\n display: inline-flex;\n position: relative;\n\n font-family: inherit;\n}\n\n/* fix `View in LibreScore` button text overflow */\nbutton:last-of-type {\n width: unset !important;\n}\n\nsvg {\n display: inline-block;\n margin-right: 5px;\n width: 20px;\n height: 20px;\n margin-top: auto;\n margin-bottom: auto;\n}\n\nspan {\n margin-top: auto;\n margin-bottom: auto;\n}"; var btnListCss = "div {\n width: 422px;\n right: 0;\n margin: 0 18px 18px 0;\n\n text-align: center;\n align-items: center;\n font-family: 'Inter', 'Helvetica neue', Helvetica, sans-serif;\n position: absolute;\n z-index: 9999;\n background: #f6f6f6;\n min-width: 230px;\n\n /* pass the scroll event through the btns background */\n pointer-events: none;\n}\n\n@media screen and (max-width: 950px) {\n div {\n width: auto !important;\n }\n}\n\nbutton {\n width: 178px !important;\n min-width: 178px;\n height: 40px;\n\n color: #fff;\n background: #2e68c0;\n\n cursor: pointer;\n pointer-events: auto;\n\n margin-bottom: 8px;\n margin-right: 8px;\n padding: 4px 12px;\n\n justify-content: start;\n align-self: center;\n\n font-size: 16px;\n border-radius: 6px;\n border: 0;\n\n display: inline-flex;\n position: relative;\n\n font-family: inherit;\n}\n\n/* fix `View in LibreScore` button text overflow */\nbutton:last-of-type {\n width: unset !important;\n}\n\nbutton:hover {\n background: #1a4f9f;\n}\n\n/* light theme btn */\nbutton.light {\n color: #2e68c0;\n background: #e1effe;\n}\n\nbutton.light:hover {\n background: #c3ddfd;\n}\n\nsvg {\n display: inline-block;\n margin-right: 5px;\n width: 20px;\n height: 20px;\n margin-top: auto;\n margin-bottom: auto;\n}\n\nspan {\n margin-top: auto;\n margin-bottom: auto;\n}";
var ICON; var ICON;
(function (ICON) { (function (ICON) {
@ -27076,15 +27077,17 @@ Please pipe the document into a Node stream.\
throw new Error('btn parent not found'); throw new Error('btn parent not found');
return btnParent; return btnParent;
}; };
const buildDownloadBtn = (icon) => { const buildDownloadBtn = (icon, lightTheme = false) => {
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.type = 'button'; btn.type = 'button';
if (lightTheme)
btn.className = 'light';
// build icon svg element // build icon svg element
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('viewBox', '0 0 24 24');
const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
svgPath.setAttribute('d', icon); svgPath.setAttribute('d', icon);
svgPath.setAttribute('fill', '#fff'); svgPath.setAttribute('fill', lightTheme ? '#2e68c0' : '#fff');
svg.append(svgPath); svg.append(svgPath);
const textNode = document.createElement('span'); const textNode = document.createElement('span');
btn.append(svg, textNode); btn.append(svg, textNode);
@ -27103,6 +27106,22 @@ Please pipe the document into a Node stream.\
return getScrollParent(node.parentNode); return getScrollParent(node.parentNode);
} }
} }
function onPageRendered(getEl) {
return new Promise((resolve) => {
var _a;
const observer = new MutationObserver(() => {
try {
const el = getEl();
if (el) {
observer.disconnect();
resolve(el);
}
}
catch (_a) { }
});
observer.observe((_a = document.querySelector('div > section')) !== null && _a !== void 0 ? _a : document.body, { childList: true, subtree: true });
});
}
var BtnListMode; var BtnListMode;
(function (BtnListMode) { (function (BtnListMode) {
BtnListMode[BtnListMode["InPage"] = 0] = "InPage"; BtnListMode[BtnListMode["InPage"] = 0] = "InPage";
@ -27115,7 +27134,7 @@ Please pipe the document into a Node stream.\
} }
add(options) { add(options) {
var _a; var _a;
const btnTpl = buildDownloadBtn((_a = options.icon) !== null && _a !== void 0 ? _a : ICON.DOWNLOAD); const btnTpl = buildDownloadBtn((_a = options.icon) !== null && _a !== void 0 ? _a : ICON.DOWNLOAD, options.lightTheme);
const setText = (btn) => { const setText = (btn) => {
const textNode = btn.querySelector('span'); const textNode = btn.querySelector('span');
return (str) => { return (str) => {
@ -27167,10 +27186,9 @@ Please pipe the document into a Node stream.\
const newParent = document.createElement('div'); const newParent = document.createElement('div');
newParent.append(...this.list.map(e => cloneBtn(e))); newParent.append(...this.list.map(e => cloneBtn(e)));
shadow.append(newParent); shadow.append(newParent);
// default position // default position
newParent.style.top = '0px'; newParent.style.top = `${window.innerHeight - newParent.getBoundingClientRect().height}px`;
try { void onPageRendered(this.getBtnParent).then((anchorDiv) => {
const anchorDiv = this.getBtnParent();
const pos = () => this._positionBtns(anchorDiv, newParent); const pos = () => this._positionBtns(anchorDiv, newParent);
pos(); pos();
// reposition btns when window resizes // reposition btns when window resizes
@ -27178,10 +27196,7 @@ Please pipe the document into a Node stream.\
// reposition btns when scrolling // reposition btns when scrolling
const scroll = getScrollParent(anchorDiv); const scroll = getScrollParent(anchorDiv);
scroll.addEventListener('scroll', pos, { passive: true }); scroll.addEventListener('scroll', pos, { passive: true });
} });
catch (err) {
console$1.error(err);
}
return btnParent; return btnParent;
} }
/** /**
@ -27429,7 +27444,7 @@ Please pipe the document into a Node stream.\
action: BtnAction.process(() => downloadPDF(scoreinfo, new SheetInfoInPage(document)), fallback, 3 * 60 * 1000 /* 3min */), action: BtnAction.process(() => downloadPDF(scoreinfo, new SheetInfoInPage(document)), fallback, 3 * 60 * 1000 /* 3min */),
}); });
btnList.add({ btnList.add({
name: i18n('DOWNLOAD')('MusicXML'), name: i18n('DOWNLOAD')('MXL'),
action: BtnAction.mscoreWindow(scoreinfo, (w, score) => __awaiter(void 0, void 0, void 0, function* () { action: BtnAction.mscoreWindow(scoreinfo, (w, score) => __awaiter(void 0, void 0, void 0, function* () {
const mxl = yield score.saveMxl(); const mxl = yield score.saveMxl();
const data = new Blob([mxl]); const data = new Blob([mxl]);
@ -27509,7 +27524,9 @@ Please pipe the document into a Node stream.\
action: BtnAction.openUrl(() => getLibreScoreLink(scoreinfo)), action: BtnAction.openUrl(() => getLibreScoreLink(scoreinfo)),
tooltip: 'BETA', tooltip: 'BETA',
icon: ICON.LIBRESCORE, icon: ICON.LIBRESCORE,
lightTheme: true,
}); });
// eslint-disable-next-line @typescript-eslint/no-floating-promises
btnList.commit(BtnListMode.InPage); btnList.commit(BtnListMode.InPage);
}; };
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "musescore-downloader", "name": "musescore-downloader",
"version": "0.23.15", "version": "0.24.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "musescore-downloader", "name": "musescore-downloader",
"version": "0.23.15", "version": "0.24.1",
"description": "download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱", "description": "download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱",
"main": "dist/main.js", "main": "dist/main.js",
"bin": "dist/cli.js", "bin": "dist/cli.js",

View File

@ -5,7 +5,7 @@ div {
text-align: center; text-align: center;
align-items: center; align-items: center;
font-family: 'Open Sans', 'Roboto', 'Helvetica neue', Helvetica, sans-serif; font-family: 'Inter', 'Helvetica neue', Helvetica, sans-serif;
position: absolute; position: absolute;
z-index: 9999; z-index: 9999;
background: #f6f6f6; background: #f6f6f6;
@ -22,25 +22,25 @@ div {
} }
button { button {
width: 205px !important; width: 178px !important;
min-width: 205px; min-width: 178px;
height: 38px; height: 40px;
color: #fff; color: #fff;
background: #1f74bd; background: #2e68c0;
cursor: pointer; cursor: pointer;
pointer-events: auto; pointer-events: auto;
margin-bottom: 4px; margin-bottom: 8px;
margin-right: 4px; margin-right: 8px;
padding: 4px 12px; padding: 4px 12px;
justify-content: start; justify-content: start;
align-self: center; align-self: center;
font-size: 16px; font-size: 16px;
border-radius: 2px; border-radius: 6px;
border: 0; border: 0;
display: inline-flex; display: inline-flex;
@ -54,6 +54,20 @@ button:last-of-type {
width: unset !important; width: unset !important;
} }
button:hover {
background: #1a4f9f;
}
/* light theme btn */
button.light {
color: #2e68c0;
background: #e1effe;
}
button.light:hover {
background: #c3ddfd;
}
svg { svg {
display: inline-block; display: inline-block;
margin-right: 5px; margin-right: 5px;

View File

@ -25,16 +25,17 @@ const getBtnContainer = (): HTMLDivElement => {
return btnParent return btnParent
} }
const buildDownloadBtn = (icon: ICON) => { const buildDownloadBtn = (icon: ICON, lightTheme = false) => {
const btn = document.createElement('button') const btn = document.createElement('button')
btn.type = 'button' btn.type = 'button'
if (lightTheme) btn.className = 'light'
// build icon svg element // build icon svg element
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
svg.setAttribute('viewBox', '0 0 24 24') svg.setAttribute('viewBox', '0 0 24 24')
const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path') const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path')
svgPath.setAttribute('d', icon) svgPath.setAttribute('d', icon)
svgPath.setAttribute('fill', '#fff') svgPath.setAttribute('fill', lightTheme ? '#2e68c0' : '#fff')
svg.append(svgPath) svg.append(svgPath)
const textNode = document.createElement('span') const textNode = document.createElement('span')
@ -57,12 +58,28 @@ function getScrollParent (node: HTMLElement): HTMLElement {
} }
} }
function onPageRendered (getEl: () => HTMLElement) {
return new Promise<HTMLElement>((resolve) => {
const observer = new MutationObserver(() => {
try {
const el = getEl()
if (el) {
observer.disconnect()
resolve(el)
}
} catch { }
})
observer.observe(document.querySelector('div > section') ?? document.body, { childList: true, subtree: true })
})
}
interface BtnOptions { interface BtnOptions {
readonly name: string; readonly name: string;
readonly action: BtnAction; readonly action: BtnAction;
readonly disabled?: boolean; readonly disabled?: boolean;
readonly tooltip?: string; readonly tooltip?: string;
readonly icon?: ICON; readonly icon?: ICON;
readonly lightTheme?: boolean;
} }
export enum BtnListMode { export enum BtnListMode {
@ -76,7 +93,7 @@ export class BtnList {
constructor (private getBtnParent: () => HTMLDivElement = getBtnContainer) { } constructor (private getBtnParent: () => HTMLDivElement = getBtnContainer) { }
add (options: BtnOptions): BtnElement { add (options: BtnOptions): BtnElement {
const btnTpl = buildDownloadBtn(options.icon ?? ICON.DOWNLOAD) const btnTpl = buildDownloadBtn(options.icon ?? ICON.DOWNLOAD, options.lightTheme)
const setText = (btn: BtnElement) => { const setText = (btn: BtnElement) => {
const textNode = btn.querySelector('span') const textNode = btn.querySelector('span')
return (str: string): void => { return (str: string): void => {
@ -139,10 +156,10 @@ export class BtnList {
newParent.append(...this.list.map(e => cloneBtn(e))) newParent.append(...this.list.map(e => cloneBtn(e)))
shadow.append(newParent) shadow.append(newParent)
// default position // default position
newParent.style.top = '0px' newParent.style.top = `${window.innerHeight - newParent.getBoundingClientRect().height}px`
try {
const anchorDiv = this.getBtnParent() void onPageRendered(this.getBtnParent).then((anchorDiv: HTMLDivElement) => {
const pos = () => this._positionBtns(anchorDiv, newParent) const pos = () => this._positionBtns(anchorDiv, newParent)
pos() pos()
@ -152,9 +169,7 @@ export class BtnList {
// reposition btns when scrolling // reposition btns when scrolling
const scroll = getScrollParent(anchorDiv) const scroll = getScrollParent(anchorDiv)
scroll.addEventListener('scroll', pos, { passive: true }) scroll.addEventListener('scroll', pos, { passive: true })
} catch (err) { })
console.error(err)
}
return btnParent return btnParent
} }

View File

@ -6,7 +6,7 @@ import { console } from './utils'
type FileType = 'img' | 'mp3' | 'midi' type FileType = 'img' | 'mp3' | 'midi'
const TYPE_REG = /id=(\d+)&type=(img|mp3|midi)/ const TYPE_REG = /type=(img|mp3|midi)/
/** /**
* I know this is super hacky. * I know this is super hacky.
@ -31,8 +31,9 @@ const magicHookConstr = (() => {
const token = init?.headers?.Authorization const token = init?.headers?.Authorization
if (typeof url === 'string' && token) { if (typeof url === 'string' && token) {
const m = url.match(TYPE_REG) const m = url.match(TYPE_REG)
console.debug(url, token, m)
if (m) { if (m) {
const type = m[2] const type = m[1]
// eslint-disable-next-line no-unused-expressions // eslint-disable-next-line no-unused-expressions
l[type]?.(token) l[type]?.(token)
} }

View File

@ -38,7 +38,7 @@ const main = (): void => {
}) })
btnList.add({ btnList.add({
name: i18n('DOWNLOAD')('MusicXML'), name: i18n('DOWNLOAD')('MXL'),
action: BtnAction.mscoreWindow(scoreinfo, async (w, score) => { action: BtnAction.mscoreWindow(scoreinfo, async (w, score) => {
const mxl = await score.saveMxl() const mxl = await score.saveMxl()
const data = new Blob([mxl]) const data = new Blob([mxl])
@ -137,8 +137,10 @@ const main = (): void => {
action: BtnAction.openUrl(() => getLibreScoreLink(scoreinfo)), action: BtnAction.openUrl(() => getLibreScoreLink(scoreinfo)),
tooltip: 'BETA', tooltip: 'BETA',
icon: ICON.LIBRESCORE, icon: ICON.LIBRESCORE,
lightTheme: true,
}) })
// eslint-disable-next-line @typescript-eslint/no-floating-promises
btnList.commit(BtnListMode.InPage) btnList.commit(BtnListMode.InPage)
} }

View File

@ -8,6 +8,7 @@
// @version %VERSION% // @version %VERSION%
// @description download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱 // @description download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱
// @author Xmader // @author Xmader
// @icon https://librescore.org/img/icons/logo.svg
// @match https://musescore.com/*/* // @match https://musescore.com/*/*
// @match https://s.musescore.com/*/* // @match https://s.musescore.com/*/*
// @license MIT // @license MIT