From 075451992550f6cf577dee69f36238ad9dc8e158 Mon Sep 17 00:00:00 2001 From: Elliott-Liu <16389800+Elliott-Liu@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:26:02 +0000 Subject: [PATCH 1/7] Replace `p` with `div` A `div` is semantically more appropriate for this rather than a `p` tag. --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index d0ed300..d51680b 100755 --- a/index.html +++ b/index.html @@ -135,7 +135,7 @@

FAQ

Link Generator

The link generator is the core feature of ScriptDude, as it builds the bridge between the web and the scriptable app. Just enter your source url, documentation url and a name for your script and we will provide you with a shareable link that makes the installation of your script a breeze.

-

+

@@ -145,7 +145,7 @@

Link Generator

-

+

From c47efc3015e944e3b327dfc4391d29fa2a48bd94 Mon Sep 17 00:00:00 2001 From: Elliott-Liu <16389800+Elliott-Liu@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:51:00 +0000 Subject: [PATCH 2/7] Add `package.json` with details --- package.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..41827cb --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "scriptdu.de", + "version": "1.0.0", + "description": "🧑‍🚀 ScriptDude is a downloader and updater for Scriptable scripts.", + "scripts": { + "format": "prettier --write \"./**/*.{css,html,ts,js,json}\"" + }, + "keywords": [ + "scriptable", + "iOS", + "MacOS", + "automation" + ], + "author": "kevinkub", + "license": "MIT", + "homepage": "https://scriptdu.de/", + "bugs": { + "url": "https://github.com/kevinkub/scriptdu.de/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/kevinkub/scriptdu.de.git" + } +} From 6b5c55aebe1c3b3eb1102f88a423c0c44d2b4b76 Mon Sep 17 00:00:00 2001 From: Elliott-Liu <16389800+Elliott-Liu@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:51:15 +0000 Subject: [PATCH 3/7] Add .prettierrc config --- .prettierrc | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..c7e4c9b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "arrowParens": "always", + "printWidth": 140, + "proseWrap": "never", + "semi": true, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "es5", + "useTabs": true +} From c6ebb9550fe40d22c786510e284da8437e47b18a Mon Sep 17 00:00:00 2001 From: Elliott-Liu <16389800+Elliott-Liu@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:52:55 +0000 Subject: [PATCH 4/7] Format website with Prettier --- index.html | 502 ++++++++++++++++++++++++++++++-------------------- mvp.css | 418 ++++++++++++++++++++--------------------- web-script.js | 242 ++++++++++++------------ 3 files changed, 636 insertions(+), 526 deletions(-) diff --git a/index.html b/index.html index d51680b..25438b2 100755 --- a/index.html +++ b/index.html @@ -1,198 +1,304 @@ - - - - - - - ScriptDude - - - - - -
- -
- -
-

Install Scriptname

-

Welcome to ScriptDude. ScriptDude makes installing Scriptable scripts easy. If you have already installed a script with ScriptDude, just click the Install button. If this is your first time downloading with ScriptDude, please follow the Setup Instructions first.

-

InstallSetup Instructions

-
- -
-
-
-
-

Share this script

-

You can share this script by linking to this page.

-
- - - -
-
- -
-

An automatic downloader and updater for Scriptable scripts

-

As a user, ScriptDude helps you to download and update scripts easily. As a developer, ScriptDude offers you the possiblity to install your script with a single click and keep your users up to date with your latest innovations.

-
-

Install ScriptDudeBrowse Scriptables.net ↗

-
- -
-
-
-
-

Installation

-

Installation of ScriptDude is just three steps.

-
- - - - -
-
-
-
-

FAQ

-
-
- Why should I use ScriptDude as a user? - ScriptDude enables you to download scripts from any source with just a single click (if the provider chose to use ScriptDude). In addition to that, ScriptDude keeps track of all the scripts you have installed with it and checks them for updates, as soon as you run ScriptDude. This gives you the possibility to keep up to date with the latest updates of your favourite Scriptable scripts. -
-
- Why should I use ScriptDude as a developer? - Our goal is to make the installation of third party scripts as easy and as seemless as possible. By using ScriptDude, your users will be guided through the process of installing your script. Copy-and-paste issues are elliminated, there is no need to add a tutorial for each and every of your scripts and last, but not least, updates are a built-in functionality of ScriptDude - so users will more likely update your scripts to the latest version, minimizing maintenance and support work. -
- -
-
-
-
-

Link Generator

-

The link generator is the core feature of ScriptDude, as it builds the bridge between the web and the scriptable app. Just enter your source url, documentation url and a name for your script and we will provide you with a shareable link that makes the installation of your script a breeze.

-
-
-
- - - - - - - -
-
-
- -
-
-
-

Imprint & data privacy

-
-
- Imprint - -
-
- Data privacy -

English version: Data privacy

- We do not store any personal data, we do not use cookies, we do not analyze your traffic. However our server keeps access logs for up to 90 days. -

German version: Datenschutzerklärung

-

Allgemeiner Hinweis und Pflichtinformationen

-

Benennung der verantwortlichen Stelle

-

Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist:

- -

Die verantwortliche Stelle entscheidet allein oder gemeinsam mit anderen über die Zwecke und Mittel der Verarbeitung von personenbezogenen Daten (z.B. Namen, Kontaktdaten o. Ä.).

-

Widerruf Ihrer Einwilligung zur Datenverarbeitung

-

Nur mit Ihrer ausdrücklichen Einwilligung sind einige Vorgänge der Datenverarbeitung möglich. Ein Widerruf Ihrer bereits erteilten Einwilligung ist jederzeit möglich. Für den Widerruf genügt eine formlose Mitteilung per E-Mail. Die Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom Widerruf unberührt.

-

Recht auf Beschwerde bei der zuständigen Aufsichtsbehörde

-

Als Betroffener steht Ihnen im Falle eines datenschutzrechtlichen Verstoßes ein Beschwerderecht bei der zuständigen Aufsichtsbehörde zu. Zuständige Aufsichtsbehörde bezüglich datenschutzrechtlicher Fragen ist der Landesdatenschutzbeauftragte des Bundeslandes, in dem sich der Sitz unseres Unternehmens befindet. Der folgende Link stellt eine Liste der Datenschutzbeauftragten sowie deren Kontaktdaten bereit: https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html.

-

Recht auf Datenübertragbarkeit

-

Ihnen steht das Recht zu, Daten, die wir auf Grundlage Ihrer Einwilligung oder in Erfüllung eines Vertrags automatisiert verarbeiten, an sich oder an Dritte aushändigen zu lassen. Die Bereitstellung erfolgt in einem maschinenlesbaren Format. Sofern Sie die direkte Übertragung der Daten an einen anderen Verantwortlichen verlangen, erfolgt dies nur, soweit es technisch machbar ist.

-

Recht auf Auskunft, Berichtigung, Sperrung, Löschung

-

Sie haben jederzeit im Rahmen der geltenden gesetzlichen Bestimmungen das Recht auf unentgeltliche Auskunft über Ihre gespeicherten personenbezogenen Daten, Herkunft der Daten, deren Empfänger und den Zweck der Datenverarbeitung und ggf. ein Recht auf Berichtigung, Sperrung oder Löschung dieser Daten. Diesbezüglich und auch zu weiteren Fragen zum Thema personenbezogene Daten können Sie sich jederzeit über die im Impressum aufgeführten Kontaktmöglichkeiten an uns wenden.

-

SSL- bzw. TLS-Verschlüsselung

-

Aus Sicherheitsgründen und zum Schutz der Übertragung vertraulicher Inhalte, die Sie an uns als Seitenbetreiber senden, nutzt unsere Website eine SSL-bzw. TLS-Verschlüsselung. Damit sind Daten, die Sie über diese Website übermitteln, für Dritte nicht mitlesbar. Sie erkennen eine verschlüsselte Verbindung an der „https://“ Adresszeile Ihres Browsers und am Schloss-Symbol in der Browserzeile.

-

Server-Log-Dateien

-

In Server-Log-Dateien erhebt und speichert der Provider der Website automatisch Informationen, die Ihr Browser automatisch an uns übermittelt. Dies sind:

-
    -
  • Besuchte Seite auf unserer Domain
  • -
  • Datum und Uhrzeit der Serveranfrage
  • -
  • Browsertyp und Browserversion
  • -
  • Verwendetes Betriebssystem
  • -
  • Referrer URL
  • -
  • Hostname des zugreifenden Rechners
  • -
  • IP-Adresse
  • -
-

Es findet keine Zusammenführung dieser Daten mit anderen Datenquellen statt. Grundlage der Datenverarbeitung bildet Art. 6 Abs. 1 lit. b DSGVO, der die Verarbeitung von Daten zur Erfüllung eines Vertrags oder vorvertraglicher Maßnahmen gestattet.

-

Quelle: Datenschutz-Konfigurator von mein-datenschutzbeauftragter.de

-
- -
-
- - + + + + + + + ScriptDude + + + + + +
+ +
+ +
+

Install Scriptname

+

+ Welcome to ScriptDude. ScriptDude makes installing Scriptable scripts easy. If you have already installed a script with ScriptDude, + just click the Install button. If this is your first time downloading with ScriptDude, please follow the Setup Instructions first. +

+

+ InstallSetup Instructions +

+
+ +
+
+
+
+

Share this script

+

You can share this script by linking to this page.

+
+ + + +
+
+ +
+

An automatic downloader and updater for Scriptable scripts

+

+ As a user, ScriptDude helps you to download and update scripts easily. As a developer, ScriptDude offers you the possiblity to + install your script with a single click and keep your users up to date with your latest innovations. +

+
+

+ Install ScriptDudeBrowse Scriptables.net ↗ +

+
+ +
+
+
+
+

Installation

+

Installation of ScriptDude is just three steps.

+
+ + + + +
+
+
+
+

FAQ

+
+
+ Why should I use ScriptDude as a user? + ScriptDude enables you to download scripts from any source with just a single click (if the provider chose to use ScriptDude). In + addition to that, ScriptDude keeps track of all the scripts you have installed with it and checks them for updates, as soon as you + run ScriptDude. This gives you the possibility to keep up to date with the latest updates of your favourite Scriptable scripts. +
+
+ Why should I use ScriptDude as a developer? + Our goal is to make the installation of third party scripts as easy and as seemless as possible. By using ScriptDude, your users + will be guided through the process of installing your script. Copy-and-paste issues are elliminated, there is no need to add a + tutorial for each and every of your scripts and last, but not least, updates are a built-in functionality of ScriptDude - so users + will more likely update your scripts to the latest version, minimizing maintenance and support work. +
+
+
+
+
+

Link Generator

+

+ The link generator is the core feature of ScriptDude, as it builds the bridge between the web and the scriptable app. Just enter + your source url, documentation url and a name for your script and we will provide you with a shareable link that makes the + installation of your script a breeze. +

+
+
+
+ + + + + + + +
+
+
+ +
+
+
+

Imprint & data privacy

+
+
+ Imprint + + + +
+
+ Data privacy +

+ English version: Data privacy +

+ We do not store any personal data, we do not use cookies, we do not analyze your traffic. However our server keeps access logs for + up to 90 days. +

+ German version: Datenschutzerklärung +

+

Allgemeiner Hinweis und Pflichtinformationen

+

Benennung der verantwortlichen Stelle

+

Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist:

+ + + +

+ Die verantwortliche Stelle entscheidet allein oder gemeinsam mit anderen über die Zwecke und Mittel der Verarbeitung von + personenbezogenen Daten (z.B. Namen, Kontaktdaten o. Ä.). +

+

Widerruf Ihrer Einwilligung zur Datenverarbeitung

+

+ Nur mit Ihrer ausdrücklichen Einwilligung sind einige Vorgänge der Datenverarbeitung möglich. Ein Widerruf Ihrer bereits + erteilten Einwilligung ist jederzeit möglich. Für den Widerruf genügt eine formlose Mitteilung per E-Mail. Die Rechtmäßigkeit + der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom Widerruf unberührt. +

+

Recht auf Beschwerde bei der zuständigen Aufsichtsbehörde

+

+ Als Betroffener steht Ihnen im Falle eines datenschutzrechtlichen Verstoßes ein Beschwerderecht bei der zuständigen + Aufsichtsbehörde zu. Zuständige Aufsichtsbehörde bezüglich datenschutzrechtlicher Fragen ist der Landesdatenschutzbeauftragte + des Bundeslandes, in dem sich der Sitz unseres Unternehmens befindet. Der folgende Link stellt eine Liste der + Datenschutzbeauftragten sowie deren Kontaktdaten bereit: + https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html. +

+

Recht auf Datenübertragbarkeit

+

+ Ihnen steht das Recht zu, Daten, die wir auf Grundlage Ihrer Einwilligung oder in Erfüllung eines Vertrags automatisiert + verarbeiten, an sich oder an Dritte aushändigen zu lassen. Die Bereitstellung erfolgt in einem maschinenlesbaren Format. Sofern + Sie die direkte Übertragung der Daten an einen anderen Verantwortlichen verlangen, erfolgt dies nur, soweit es technisch machbar + ist. +

+

Recht auf Auskunft, Berichtigung, Sperrung, Löschung

+

+ Sie haben jederzeit im Rahmen der geltenden gesetzlichen Bestimmungen das Recht auf unentgeltliche Auskunft über Ihre + gespeicherten personenbezogenen Daten, Herkunft der Daten, deren Empfänger und den Zweck der Datenverarbeitung und ggf. ein + Recht auf Berichtigung, Sperrung oder Löschung dieser Daten. Diesbezüglich und auch zu weiteren Fragen zum Thema + personenbezogene Daten können Sie sich jederzeit über die im Impressum aufgeführten Kontaktmöglichkeiten an uns wenden. +

+

SSL- bzw. TLS-Verschlüsselung

+

+ Aus Sicherheitsgründen und zum Schutz der Übertragung vertraulicher Inhalte, die Sie an uns als Seitenbetreiber senden, nutzt + unsere Website eine SSL-bzw. TLS-Verschlüsselung. Damit sind Daten, die Sie über diese Website übermitteln, für Dritte nicht + mitlesbar. Sie erkennen eine verschlüsselte Verbindung an der „https://“ Adresszeile Ihres Browsers und am Schloss-Symbol in der + Browserzeile. +

+

Server-Log-Dateien

+

+ In Server-Log-Dateien erhebt und speichert der Provider der Website automatisch Informationen, die Ihr Browser automatisch an + uns übermittelt. Dies sind: +

+
    +
  • Besuchte Seite auf unserer Domain
  • +
  • Datum und Uhrzeit der Serveranfrage
  • +
  • Browsertyp und Browserversion
  • +
  • Verwendetes Betriebssystem
  • +
  • Referrer URL
  • +
  • Hostname des zugreifenden Rechners
  • +
  • IP-Adresse
  • +
+

+ Es findet keine Zusammenführung dieser Daten mit anderen Datenquellen statt. Grundlage der Datenverarbeitung bildet Art. 6 Abs. + 1 lit. b DSGVO, der die Verarbeitung von Daten zur Erfüllung eines Vertrags oder vorvertraglicher Maßnahmen gestattet. +

+

+ Quelle: Datenschutz-Konfigurator von + mein-datenschutzbeauftragter.de +

+
+
+
+ + diff --git a/mvp.css b/mvp.css index 9ea9501..43bb75c 100644 --- a/mvp.css +++ b/mvp.css @@ -1,26 +1,26 @@ /* MVP.css v1.6.2 - https://github.com/andybrewer/mvp */ :root { - --border-radius: 5px; - --box-shadow: 2px 2px 10px; - --color: #118bee; - --color-accent: #118bee15; - --color-bg: #fff; - --color-bg-secondary: #e9e9e9; - --color-secondary: #920de9; - --color-secondary-accent: #920de90b; - --color-shadow: #f4f4f4; - --color-text: #000; - --color-text-secondary: #999; - --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; - --hover-brightness: 1.2; - --justify-important: center; - --justify-normal: left; - --line-height: 1.5; - --width-card: 285px; - --width-card-medium: 460px; - --width-card-wide: 800px; - --width-content: 1080px; + --border-radius: 5px; + --box-shadow: 2px 2px 10px; + --color: #118bee; + --color-accent: #118bee15; + --color-bg: #fff; + --color-bg-secondary: #e9e9e9; + --color-secondary: #920de9; + --color-secondary-accent: #920de90b; + --color-shadow: #f4f4f4; + --color-text: #000; + --color-text-secondary: #999; + --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + --hover-brightness: 1.2; + --justify-important: center; + --justify-normal: left; + --line-height: 1.5; + --width-card: 285px; + --width-card-medium: 460px; + --width-card-wide: 800px; + --width-content: 1080px; } /* @@ -41,159 +41,159 @@ /* Layout */ article aside { - background: var(--color-secondary-accent); - border-left: 4px solid var(--color-secondary); - padding: 0.01rem 0.8rem; + background: var(--color-secondary-accent); + border-left: 4px solid var(--color-secondary); + padding: 0.01rem 0.8rem; } body { - background: var(--color-bg); - color: var(--color-text); - font-family: var(--font-family); - line-height: var(--line-height); - margin: 0; - overflow-x: hidden; - padding: 1rem 0; + background: var(--color-bg); + color: var(--color-text); + font-family: var(--font-family); + line-height: var(--line-height); + margin: 0; + overflow-x: hidden; + padding: 1rem 0; } footer, header, main { - margin: 0 auto; - max-width: var(--width-content); - padding: 2rem 1rem; + margin: 0 auto; + max-width: var(--width-content); + padding: 2rem 1rem; } hr { - background-color: var(--color-bg-secondary); - border: none; - height: 1px; - margin: 4rem 0; + background-color: var(--color-bg-secondary); + border: none; + height: 1px; + margin: 4rem 0; } section { - display: flex; - flex-wrap: wrap; - justify-content: var(--justify-important); + display: flex; + flex-wrap: wrap; + justify-content: var(--justify-important); } section aside { - display: flex; - justify-content: start; - flex-direction: column; - border: 1px solid var(--color-bg-secondary); - border-radius: var(--border-radius); - box-shadow: var(--box-shadow) var(--color-shadow); - margin: 1rem; - padding: 1.25rem; - width: var(--width-card); - text-align: center; + display: flex; + justify-content: start; + flex-direction: column; + border: 1px solid var(--color-bg-secondary); + border-radius: var(--border-radius); + box-shadow: var(--box-shadow) var(--color-shadow); + margin: 1rem; + padding: 1.25rem; + width: var(--width-card); + text-align: center; } section aside:hover { - box-shadow: var(--box-shadow) var(--color-bg-secondary); + box-shadow: var(--box-shadow) var(--color-bg-secondary); } section aside img { - max-width: 100%; + max-width: 100%; } [hidden] { - display: none; + display: none; } /* Headers */ article header, div header, main header { - padding-top: 0; + padding-top: 0; } header { - text-align: var(--justify-important); + text-align: var(--justify-important); } header a b, header a em, header a i, header a strong { - margin-left: 0.5rem; - margin-right: 0.5rem; + margin-left: 0.5rem; + margin-right: 0.5rem; } header nav img { - margin: 1rem 0; + margin: 1rem 0; } section header { - padding-top: 0; - width: 100%; + padding-top: 0; + width: 100%; } /* Nav */ nav { - align-items: center; - display: flex; - font-weight: bold; - justify-content: space-between; - margin-bottom: 7rem; + align-items: center; + display: flex; + font-weight: bold; + justify-content: space-between; + margin-bottom: 7rem; } nav ul { - list-style: none; - padding: 0; + list-style: none; + padding: 0; } nav ul li { - display: inline-block; - margin: 0 0.5rem; - position: relative; - text-align: left; + display: inline-block; + margin: 0 0.5rem; + position: relative; + text-align: left; } /* Nav Dropdown */ nav ul li:hover ul { - display: block; + display: block; } nav ul li ul { - background: var(--color-bg); - border: 1px solid var(--color-bg-secondary); - border-radius: var(--border-radius); - box-shadow: var(--box-shadow) var(--color-shadow); - display: none; - height: auto; - left: -2px; - padding: .5rem 1rem; - position: absolute; - top: 1.7rem; - white-space: nowrap; - width: auto; + background: var(--color-bg); + border: 1px solid var(--color-bg-secondary); + border-radius: var(--border-radius); + box-shadow: var(--box-shadow) var(--color-shadow); + display: none; + height: auto; + left: -2px; + padding: 0.5rem 1rem; + position: absolute; + top: 1.7rem; + white-space: nowrap; + width: auto; } nav ul li ul li, nav ul li ul li a { - display: block; + display: block; } /* Typography */ code, samp { - background-color: var(--color-accent); - border-radius: var(--border-radius); - color: var(--color-text); - display: inline-block; - margin: 0 0.1rem; - padding: 0 0.5rem; + background-color: var(--color-accent); + border-radius: var(--border-radius); + color: var(--color-text); + display: inline-block; + margin: 0 0.1rem; + padding: 0 0.5rem; } details { - margin: 1.3rem 0; + margin: 1.3rem 0; } details summary { - font-weight: bold; - cursor: pointer; + font-weight: bold; + cursor: pointer; } h1, @@ -202,64 +202,64 @@ h3, h4, h5, h6 { - line-height: var(--line-height); + line-height: var(--line-height); } mark { - padding: 0.1rem; + padding: 0.1rem; } ol li, ul li { - padding: 0.2rem 0; + padding: 0.2rem 0; } p { - margin: 0.75rem 0; - padding: 0; + margin: 0.75rem 0; + padding: 0; } pre { - margin: 1rem 0; - max-width: var(--width-card-wide); - padding: 1rem 0; + margin: 1rem 0; + max-width: var(--width-card-wide); + padding: 1rem 0; } pre code, pre samp { - display: block; - max-width: var(--width-card-wide); - padding: 0.5rem 2rem; - white-space: pre-wrap; + display: block; + max-width: var(--width-card-wide); + padding: 0.5rem 2rem; + white-space: pre-wrap; } small { - color: var(--color-text-secondary); + color: var(--color-text-secondary); } sup { - background-color: var(--color-secondary); - border-radius: var(--border-radius); - color: var(--color-bg); - font-size: xx-small; - font-weight: bold; - margin: 0.2rem; - padding: 0.2rem 0.3rem; - position: relative; - top: -2px; + background-color: var(--color-secondary); + border-radius: var(--border-radius); + color: var(--color-bg); + font-size: xx-small; + font-weight: bold; + margin: 0.2rem; + padding: 0.2rem 0.3rem; + position: relative; + top: -2px; } /* Links */ a { - color: var(--color-secondary); - display: inline-block; - font-weight: bold; - text-decoration: none; + color: var(--color-secondary); + display: inline-block; + font-weight: bold; + text-decoration: none; } a:hover { - filter: brightness(var(--hover-brightness)); - text-decoration: underline; + filter: brightness(var(--hover-brightness)); + text-decoration: underline; } a b, @@ -267,190 +267,190 @@ a em, a i, a strong, button { - border-radius: var(--border-radius); - display: inline-block; - font-size: medium; - font-weight: bold; - line-height: var(--line-height); - margin: 0.5rem 0; - padding: 1rem 2rem; + border-radius: var(--border-radius); + display: inline-block; + font-size: medium; + font-weight: bold; + line-height: var(--line-height); + margin: 0.5rem 0; + padding: 1rem 2rem; } button { - font-family: var(--font-family); + font-family: var(--font-family); } button:hover { - cursor: pointer; - filter: brightness(var(--hover-brightness)); + cursor: pointer; + filter: brightness(var(--hover-brightness)); } a b, a strong, button { - background-color: var(--color); - border: 2px solid var(--color); - color: var(--color-bg); + background-color: var(--color); + border: 2px solid var(--color); + color: var(--color-bg); } a em, a i { - border: 2px solid var(--color); - border-radius: var(--border-radius); - color: var(--color); - display: inline-block; - padding: 1rem 2rem; + border: 2px solid var(--color); + border-radius: var(--border-radius); + color: var(--color); + display: inline-block; + padding: 1rem 2rem; } /* Images */ figure { - margin: 0; - padding: 0; + margin: 0; + padding: 0; } figure img { - max-width: 100%; + max-width: 100%; } figure figcaption { - color: var(--color-text-secondary); + color: var(--color-text-secondary); } /* Forms */ button:disabled, input:disabled { - background: var(--color-bg-secondary); - border-color: var(--color-bg-secondary); - color: var(--color-text-secondary); - cursor: not-allowed; + background: var(--color-bg-secondary); + border-color: var(--color-bg-secondary); + color: var(--color-text-secondary); + cursor: not-allowed; } button[disabled]:hover { - filter: none; + filter: none; } form { - border: 1px solid var(--color-bg-secondary); - border-radius: var(--border-radius); - box-shadow: var(--box-shadow) var(--color-shadow); - display: block; - max-width: var(--width-card-wide); - min-width: var(--width-card); - padding: 1.5rem; - text-align: var(--justify-normal); + border: 1px solid var(--color-bg-secondary); + border-radius: var(--border-radius); + box-shadow: var(--box-shadow) var(--color-shadow); + display: block; + max-width: var(--width-card-wide); + min-width: var(--width-card); + padding: 1.5rem; + text-align: var(--justify-normal); } form header { - margin: 1.5rem 0; - padding: 1.5rem 0; + margin: 1.5rem 0; + padding: 1.5rem 0; } input, label, select, textarea { - display: block; - font-size: inherit; - max-width: var(--width-card-wide); + display: block; + font-size: inherit; + max-width: var(--width-card-wide); } input[type="checkbox"], input[type="radio"] { - display: inline-block; + display: inline-block; } -input[type="checkbox"]+label, -input[type="radio"]+label { - display: inline-block; - font-weight: normal; - position: relative; - top: 1px; +input[type="checkbox"] + label, +input[type="radio"] + label { + display: inline-block; + font-weight: normal; + position: relative; + top: 1px; } input, select, textarea { - border: 1px solid var(--color-bg-secondary); - border-radius: var(--border-radius); - margin-bottom: 1rem; - padding: 0.4rem 0.8rem; + border: 1px solid var(--color-bg-secondary); + border-radius: var(--border-radius); + margin-bottom: 1rem; + padding: 0.4rem 0.8rem; } input[readonly], textarea[readonly] { - background-color: var(--color-bg-secondary); + background-color: var(--color-bg-secondary); } label { - font-weight: bold; - margin-bottom: 0.2rem; + font-weight: bold; + margin-bottom: 0.2rem; } /* Tables */ table { - border: 1px solid var(--color-bg-secondary); - border-radius: var(--border-radius); - border-spacing: 0; - display: inline-block; - max-width: 100%; - overflow-x: auto; - padding: 0; - white-space: nowrap; + border: 1px solid var(--color-bg-secondary); + border-radius: var(--border-radius); + border-spacing: 0; + display: inline-block; + max-width: 100%; + overflow-x: auto; + padding: 0; + white-space: nowrap; } table td, table th, table tr { - padding: 0.4rem 0.8rem; - text-align: var(--justify-important); + padding: 0.4rem 0.8rem; + text-align: var(--justify-important); } table thead { - background-color: var(--color); - border-collapse: collapse; - border-radius: var(--border-radius); - color: var(--color-bg); - margin: 0; - padding: 0; + background-color: var(--color); + border-collapse: collapse; + border-radius: var(--border-radius); + color: var(--color-bg); + margin: 0; + padding: 0; } table thead th:first-child { - border-top-left-radius: var(--border-radius); + border-top-left-radius: var(--border-radius); } table thead th:last-child { - border-top-right-radius: var(--border-radius); + border-top-right-radius: var(--border-radius); } table thead th:first-child, table tr td:first-child { - text-align: var(--justify-normal); + text-align: var(--justify-normal); } table tr:nth-child(even) { - background-color: var(--color-accent); + background-color: var(--color-accent); } /* Quotes */ blockquote { - display: block; - font-size: x-large; - line-height: var(--line-height); - margin: 1rem auto; - max-width: var(--width-card-medium); - padding: 1.5rem 1rem; - text-align: var(--justify-important); + display: block; + font-size: x-large; + line-height: var(--line-height); + margin: 1rem auto; + max-width: var(--width-card-medium); + padding: 1.5rem 1rem; + text-align: var(--justify-important); } blockquote footer { - color: var(--color-text-secondary); - display: block; - font-size: small; - line-height: var(--line-height); - padding: 1.5rem 0; + color: var(--color-text-secondary); + display: block; + font-size: small; + line-height: var(--line-height); + padding: 1.5rem 0; } .no-italic { - font-style: normal; + font-style: normal; } diff --git a/web-script.js b/web-script.js index 9f9ea0f..611c45b 100644 --- a/web-script.js +++ b/web-script.js @@ -1,134 +1,138 @@ -document.addEventListener('DOMContentLoaded', async function() { - - // Checkmarks in installation - addCheckmarkToLinks('#installation a') - - // Copy install code - let installCode; - await fetch('install.js').then(res => res.text()).then((source) => installCode = source); - setupCopyListener('#copyInstaller', installCode, "The installation code has been copied to your pasteboard."); - - // Installation page - if(document.location.search) { - [].slice.call(document.querySelectorAll('[x-hide-install]')).map(el => el.style.display = 'none'); - query = parseQuery(document.location.search); - - document.querySelector('#scriptName').innerText = query.name; - - const pageOrigin = new URL(document.location.href).origin; - const pageUrl = appendParamsToUrl(pageOrigin, query); - - const pageQuery = pageUrl.replace(`${pageOrigin}?`, ''); - document.querySelector('#scriptdudeInstaller').href += '&' + pageQuery; - - const directLink = (document.querySelector('#link').innerText = pageUrl); - setupCopyListener('#copyLinkToDirectLink', directLink, "The link has been copied to your pasteboard."); - addCheckmarkToLinks('#copyLinkToDirectLink') - - const markdown = `[![Download with ScriptDude](https://scriptdu.de/download.svg)](${pageUrl})`; - document.querySelector('#markdown').innerText = markdown; - setupCopyListener('#copyLinkToButtonMarkdown', markdown, "Markdown has been copied to your pasteboard."); - addCheckmarkToLinks('#copyLinkToButtonMarkdown') - - const html = `Download with ScriptDude`; - const safeHtml = 'Download with ScriptDude'; - document.querySelector('#html').innerText = html; - setupCopyListener('#copyLinkToButtonHtml', html, "HTML link has been copied to your pasteboard."); - addCheckmarkToLinks('#copyLinkToButtonHtml') - - document.querySelector('#markdown-preview').innerHTML = safeHtml; - document.querySelector('#html-preview').innerHTML = safeHtml; - } else { - [].slice.call(document.querySelectorAll('[x-hide-home]')).map(el => el.style.display = 'none'); - } - -}, false); +document.addEventListener( + "DOMContentLoaded", + async function () { + // Checkmarks in installation + addCheckmarkToLinks("#installation a"); + + // Copy install code + let installCode; + await fetch("install.js") + .then((res) => res.text()) + .then((source) => (installCode = source)); + setupCopyListener("#copyInstaller", installCode, "The installation code has been copied to your pasteboard."); + + // Installation page + if (document.location.search) { + [].slice.call(document.querySelectorAll("[x-hide-install]")).map((el) => (el.style.display = "none")); + query = parseQuery(document.location.search); + + document.querySelector("#scriptName").innerText = query.name; + + const pageOrigin = new URL(document.location.href).origin; + const pageUrl = appendParamsToUrl(pageOrigin, query); + + const pageQuery = pageUrl.replace(`${pageOrigin}?`, ""); + document.querySelector("#scriptdudeInstaller").href += "&" + pageQuery; + + const directLink = (document.querySelector("#link").innerText = pageUrl); + setupCopyListener("#copyLinkToDirectLink", directLink, "The link has been copied to your pasteboard."); + addCheckmarkToLinks("#copyLinkToDirectLink"); + + const markdown = `[![Download with ScriptDude](https://scriptdu.de/download.svg)](${pageUrl})`; + document.querySelector("#markdown").innerText = markdown; + setupCopyListener("#copyLinkToButtonMarkdown", markdown, "Markdown has been copied to your pasteboard."); + addCheckmarkToLinks("#copyLinkToButtonMarkdown"); + + const html = `Download with ScriptDude`; + const safeHtml = 'Download with ScriptDude'; + document.querySelector("#html").innerText = html; + setupCopyListener("#copyLinkToButtonHtml", html, "HTML link has been copied to your pasteboard."); + addCheckmarkToLinks("#copyLinkToButtonHtml"); + + document.querySelector("#markdown-preview").innerHTML = safeHtml; + document.querySelector("#html-preview").innerHTML = safeHtml; + } else { + [].slice.call(document.querySelectorAll("[x-hide-home]")).map((el) => (el.style.display = "none")); + } + }, + false +); function addCheckmarkToLinks(selector) { - const links = document.querySelectorAll(selector); - links.forEach((el) => { - el.addEventListener('click', () => { - const emChild = el.querySelector('em'); - if (emChild) { - if (!emChild.innerHTML.includes('✅')) { - emChild.innerHTML += ' '; - } - } - }); - }); + const links = document.querySelectorAll(selector); + links.forEach((el) => { + el.addEventListener("click", () => { + const emChild = el.querySelector("em"); + if (emChild) { + if (!emChild.innerHTML.includes("✅")) { + emChild.innerHTML += ' '; + } + } + }); + }); } function setupCopyListener(selector, copyValue, alertMessage) { - document.querySelector(selector).addEventListener('click', (event) => { - Clipboard.copy(copyValue); - if (alertMessage) alert(alertMessage); - event.preventDefault(); - }); + document.querySelector(selector).addEventListener("click", (event) => { + Clipboard.copy(copyValue); + if (alertMessage) alert(alertMessage); + event.preventDefault(); + }); } function appendParamsToUrl(baseUrl, queryParameters) { - const queryString = Object.entries(queryParameters) - .map((parameter) => { - const [key, value] = parameter; - let encodedValue = encodeURIComponent(value); - if (key === "name") encodedValue.replace("+", "%20"); - return `${encodeURIComponent(key)}=${encodedValue}`; - }) - .join("&"); - return `${baseUrl}?${queryString}`; + const queryString = Object.entries(queryParameters) + .map((parameter) => { + const [key, value] = parameter; + let encodedValue = encodeURIComponent(value); + if (key === "name") encodedValue.replace("+", "%20"); + return `${encodeURIComponent(key)}=${encodedValue}`; + }) + .join("&"); + return `${baseUrl}?${queryString}`; } // Code from https://stackoverflow.com/a/13419367 function parseQuery(queryString) { - var query = {}; - var pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&'); - for (var i = 0; i < pairs.length; i++) { - var pair = pairs[i].split('='); - query[decodeURIComponent(pair[0])] = decodeURIComponent((pair[1] || '').replaceAll('+', '%20')); - } - return query; + var query = {}; + var pairs = (queryString[0] === "?" ? queryString.substr(1) : queryString).split("&"); + for (var i = 0; i < pairs.length; i++) { + var pair = pairs[i].split("="); + query[decodeURIComponent(pair[0])] = decodeURIComponent((pair[1] || "").replaceAll("+", "%20")); + } + return query; } // Code from https://gist.github.com/rproenca/64781c6a1329b48a455b645d361a9aa3 -window.Clipboard = (function(window, document, navigator) { - var textArea, copy; - - function isOS() { - return navigator.userAgent.match(/ipad|iphone/i); - } - - function createTextArea(text) { - textArea = document.createElement('textArea'); - textArea.value = text; - document.body.appendChild(textArea); - } - - function selectText() { - var range, selection; - if (isOS()) { - range = document.createRange(); - range.selectNodeContents(textArea); - selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); - textArea.setSelectionRange(0, 999999); - } else { - textArea.select(); - } - } - - function copyToClipboard() { - document.execCommand('copy'); - document.body.removeChild(textArea); - } - - copy = function(text) { - createTextArea(text); - selectText(); - copyToClipboard(); - }; - - return { - copy: copy - }; -})(window, document, navigator); \ No newline at end of file +window.Clipboard = (function (window, document, navigator) { + var textArea, copy; + + function isOS() { + return navigator.userAgent.match(/ipad|iphone/i); + } + + function createTextArea(text) { + textArea = document.createElement("textArea"); + textArea.value = text; + document.body.appendChild(textArea); + } + + function selectText() { + var range, selection; + if (isOS()) { + range = document.createRange(); + range.selectNodeContents(textArea); + selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + textArea.setSelectionRange(0, 999999); + } else { + textArea.select(); + } + } + + function copyToClipboard() { + document.execCommand("copy"); + document.body.removeChild(textArea); + } + + copy = function (text) { + createTextArea(text); + selectText(); + copyToClipboard(); + }; + + return { + copy: copy, + }; +})(window, document, navigator); From d4135117d1d6f4c6f5d10fe4ddf01feb50579acd Mon Sep 17 00:00:00 2001 From: Elliott-Liu <16389800+Elliott-Liu@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:54:10 +0000 Subject: [PATCH 5/7] Format install script with Prettier --- install.js | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/install.js b/install.js index 1df7e08..da529ad 100644 --- a/install.js +++ b/install.js @@ -1,30 +1,30 @@ -const url = 'https://scriptdu.de/script.js'; +const url = "https://scriptdu.de/script.js"; class ScriptDudeInstaller { - - constructor(hoehe, breite) { - this.fileManager = FileManager.iCloud() - this.documentsDirectory = this.fileManager.documentsDirectory() - } - - hashCode(input) { - return Array.from(input).reduce((accumulator, currentChar) => Math.imul(31, accumulator) + currentChar.charCodeAt(0), 0) - } - - async installScript(name, sourceUrl, documentationUrl, icon, color, showMessage) { - let filePath = this.fileManager.joinPath(this.documentsDirectory, name + '.js'); - let req = new Request(sourceUrl); - let code = await req.loadString(); - let hash = this.hashCode(code); - let codeToStore = Data.fromString(`// Variables used by Scriptable.\n// These must be at the very top of the file. Do not edit.\n// icon-color: ${color}; icon-glyph: ${icon};\n// This script was downloaded using ScriptDude.\n// Do not remove these lines, if you want to benefit from automatic updates.\n// source: ${sourceUrl}; docs: ${documentationUrl}; hash: ${hash};\n\n${code}`); - this.fileManager.write(filePath, codeToStore); - let selfFilePath = this.fileManager.joinPath(this.documentsDirectory, Script.name() + '.js'); - this.fileManager.remove(selfFilePath); - let callback = new CallbackURL("scriptable:///run"); - callback.addParameter("scriptName", "ScriptDude"); - callback.open(); - } - + constructor(hoehe, breite) { + this.fileManager = FileManager.iCloud(); + this.documentsDirectory = this.fileManager.documentsDirectory(); + } + + hashCode(input) { + return Array.from(input).reduce((accumulator, currentChar) => Math.imul(31, accumulator) + currentChar.charCodeAt(0), 0); + } + + async installScript(name, sourceUrl, documentationUrl, icon, color, showMessage) { + let filePath = this.fileManager.joinPath(this.documentsDirectory, name + ".js"); + let req = new Request(sourceUrl); + let code = await req.loadString(); + let hash = this.hashCode(code); + let codeToStore = Data.fromString( + `// Variables used by Scriptable.\n// These must be at the very top of the file. Do not edit.\n// icon-color: ${color}; icon-glyph: ${icon};\n// This script was downloaded using ScriptDude.\n// Do not remove these lines, if you want to benefit from automatic updates.\n// source: ${sourceUrl}; docs: ${documentationUrl}; hash: ${hash};\n\n${code}` + ); + this.fileManager.write(filePath, codeToStore); + let selfFilePath = this.fileManager.joinPath(this.documentsDirectory, Script.name() + ".js"); + this.fileManager.remove(selfFilePath); + let callback = new CallbackURL("scriptable:///run"); + callback.addParameter("scriptName", "ScriptDude"); + callback.open(); + } } -await new ScriptDudeInstaller().installScript('ScriptDude', url, 'https://scriptdu.de/', 'user-astronaut', 'blue', false); \ No newline at end of file +await new ScriptDudeInstaller().installScript("ScriptDude", url, "https://scriptdu.de/", "user-astronaut", "blue", false); From 9c364ea050bea647b8920abec295a6fa10b63150 Mon Sep 17 00:00:00 2001 From: Elliott-Liu <16389800+Elliott-Liu@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:54:26 +0000 Subject: [PATCH 6/7] Format main script with Prettier --- script.js | 654 +++++++++++++++++++++++++++--------------------------- 1 file changed, 322 insertions(+), 332 deletions(-) diff --git a/script.js b/script.js index fecf835..c3d0051 100644 --- a/script.js +++ b/script.js @@ -6,341 +6,331 @@ // source: https://scriptdu.de/script.js; docs: https://scriptdu.de/; hash: -1503760114; class ScriptDude { - - constructor() { - try { - this.fileManager = FileManager.iCloud() - } catch(e) { - this.fileManager = FileManager.local() - } - this.documentsDirectory = this.fileManager.documentsDirectory() - this.updateableScripts = []; - this.uptodateScripts = []; - this.table = new UITable(); - } - - ensureCorrectScriptNaming() { - const scriptName = 'ScriptDude'; - if(Script.name() != scriptName) { - let alert = new Alert(); - alert.title = 'Wrong script name'; - alert.message = `In order to work properly, this script needs to be named "${scriptName}". Please rename the script by clicking on its title and run it again.`; - alert.addCancelAction("Okay"); - alert.presentAlert(); - Script.complete(); - throw "Wrong script name"; - } - } - - makeUrlUpdateable(url) { - // Strip revision from gist - if(url.startsWith("https://gist.githubusercontent.com/")) { - let parts = url.split('/'); - if(parts.length == 8) { - url = parts.filter((el, i) => i != 6).join('/'); - } - } - return url; - } + constructor() { + try { + this.fileManager = FileManager.iCloud(); + } catch (e) { + this.fileManager = FileManager.local(); + } + this.documentsDirectory = this.fileManager.documentsDirectory(); + this.updateableScripts = []; + this.uptodateScripts = []; + this.table = new UITable(); + } - hashCode(input) { - return Array.from(input).reduce((accumulator, currentChar) => Math.imul(31, accumulator) + currentChar.charCodeAt(0), 0) - } - - render() { - this.table.removeAllRows(); - this.getPackageUI().map(row => this.table.addRow(row)); - this.table.reload(); - } - - getPackageUI() { - let rows = []; - if(this.updateableScripts.length) { - rows.push(...this.getPackageUISection("Updates", this.updateableScripts)); - } - if(this.uptodateScripts.length) { - rows.push(...this.getPackageUISection("Installed", this.uptodateScripts)); - } - let manualInstall = new UITableRow(); - let manualInstallButton = manualInstall.addButton("Install from URL"); - manualInstallButton.onTap = () => { - this.getInstallationUI(); - } - rows.push(manualInstall); - let scriptablesLink = new UITableRow(); - let scriptablesLinkButton = scriptablesLink.addButton("Browse scriptables.net"); - scriptablesLinkButton.onTap = () => { - Safari.open('https://scriptables.net/'); - } - rows.push(scriptablesLink); - return rows; - } - - getPackageUISection(title, scripts) { - let rows = []; - let header = new UITableRow(); - let text = header.addText(title); - text.titleFont = Font.headline(); - rows.push(header, ...scripts.map(this.getPackageUIRow.bind(this)), new UITableRow()); - return rows; - } + ensureCorrectScriptNaming() { + const scriptName = "ScriptDude"; + if (Script.name() != scriptName) { + let alert = new Alert(); + alert.title = "Wrong script name"; + alert.message = `In order to work properly, this script needs to be named "${scriptName}". Please rename the script by clicking on its title and run it again.`; + alert.addCancelAction("Okay"); + alert.presentAlert(); + Script.complete(); + throw "Wrong script name"; + } + } - mergeHeaders(codeOld, codeNew) - { - let icon = null, color = null; - - if (!!codeOld) - { - let lines = codeOld.split("\n", 20); - for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { - const line = lines[lineIndex]; - let match = line.match(/icon-color: ([\w\-]*);/); - if (match) - color = match[1]; - match = line.match(/icon-glyph: ([\w\-]*);/); - if (match) - icon = match[1]; - if (icon != null && color != null) - break; - } - } + makeUrlUpdateable(url) { + // Strip revision from gist + if (url.startsWith("https://gist.githubusercontent.com/")) { + let parts = url.split("/"); + if (parts.length == 8) { + url = parts.filter((el, i) => i != 6).join("/"); + } + } + return url; + } - let scriptableHeader = ""; // detect Scriptable header - if (codeNew.trim().startsWith('// Variables used by Scriptable')) - { - let lines = codeNew.split("\n"); // HINT: to improve speed, we could use just the first 20 lines of the code -> code.split("\n", 20); - for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { - let line = lines[lineIndex]; - if (line.indexOf('// Variables used by Scriptable.') != -1 || line.indexOf('// These must be at the very top of the file') != -1) - { - scriptableHeader += line + "\n"; - codeNew = codeNew.substr(line.length + 1); // Remove scriptable header from original code - } - else if (line.match(/\/\/( [\w\-]*: [\w\-]*;)+/)) // Find "key: value;" list used by scriptable - { - codeNew = codeNew.substr(line.length + 1); // Remove scriptable header from original code - let match = line.match(/icon-color: ([\w\-]*);/); - if (match) - line = line.replace(match[1], color); - match = line.match(/icon-glyph: ([\w\-]*);/); - if (match) - line = line.replace(match[1], icon); + hashCode(input) { + return Array.from(input).reduce((accumulator, currentChar) => Math.imul(31, accumulator) + currentChar.charCodeAt(0), 0); + } - scriptableHeader += line + "\n"; - } - else - { - break; - } - } - } - else - { - scriptableHeader = `// Variables used by Scriptable.\n// These must be at the very top of the file. Do not edit.\n// icon-color: ${color || 'blue'}; icon-glyph: ${icon || 'circle'};\n`; - } - return [scriptableHeader, codeNew]; - } + render() { + this.table.removeAllRows(); + this.getPackageUI().map((row) => this.table.addRow(row)); + this.table.reload(); + } - getPackageUIRow(script) { - const iconWidth = 60; - let row = new UITableRow(); - let text = row.addText(script.name, script.source.split('/')[2]); - text.subtitleFont = Font.systemFont(10) - if(script.updateAvailable) { - let updateButton = row.addButton("Update"); - updateButton.titleColor = Color.red() - updateButton.rightAligned(); - updateButton.onTap = () => { - this.updateScript(script); - } - updateButton.widthWeight = iconWidth; - } - let documentationButton = row.addButton("Docs"); - documentationButton.rightAligned(); - documentationButton.onTap = () => { - Safari.open(script.docs) - } - documentationButton.widthWeight = iconWidth; - let width = Device.screenSize().width - (script.updateAvailable ? iconWidth*2 : iconWidth); - text.widthWeight = width; - return row; - } - - async installScript(name, sourceUrl, documentationUrl, icon, color, showMessage) { - sourceUrl = this.makeUrlUpdateable(sourceUrl); - let filePath = this.fileManager.joinPath(this.documentsDirectory, name + '.js'); - if(this.fileManager.fileExists(filePath)) { - let error = new Alert(); - error.title = `A script with the name ${name} does already exist.`; - error.presentAlert(); - return; - } - if(false != showMessage) { - let warning = new Alert(); - warning.title = "Warning"; - warning.message = `Scriptable scripts can access sensitive data on your device. Make sure to check downloaded scripts before running them for the first time. Do you want to continue downloading "${name}" from "${sourceUrl}"?`; - warning.addAction("Continue"); - warning.addCancelAction("Cancel"); - let result = await warning.presentAlert(); - if(-1 == result) { - return; - } - } - let req = new Request(sourceUrl); - let codeOriginal = await req.loadString(); - let hash = this.hashCode(codeOriginal); - let [scriptableHeader, code] = this.mergeHeaders(null, codeOriginal); + getPackageUI() { + let rows = []; + if (this.updateableScripts.length) { + rows.push(...this.getPackageUISection("Updates", this.updateableScripts)); + } + if (this.uptodateScripts.length) { + rows.push(...this.getPackageUISection("Installed", this.uptodateScripts)); + } + let manualInstall = new UITableRow(); + let manualInstallButton = manualInstall.addButton("Install from URL"); + manualInstallButton.onTap = () => { + this.getInstallationUI(); + }; + rows.push(manualInstall); + let scriptablesLink = new UITableRow(); + let scriptablesLinkButton = scriptablesLink.addButton("Browse scriptables.net"); + scriptablesLinkButton.onTap = () => { + Safari.open("https://scriptables.net/"); + }; + rows.push(scriptablesLink); + return rows; + } - let codeToStore = Data.fromString(`${scriptableHeader}// This script was downloaded using ScriptDude.\n// Do not remove these lines, if you want to benefit from automatic updates.\n// source: ${sourceUrl}; docs: ${documentationUrl}; hash: ${hash};\n\n${code}`); - this.fileManager.write(filePath, codeToStore); - this.showLoadingIndicator(); - this.updateScriptsData().then(() => { this.render() }); - } - - async getInstallationUI() { - let install = new Alert() - install.title = "Install" - install.message = "ScriptDude makes downloading and updating Scriptable scripts easy."; - install.addTextField("Script Name") - install.addTextField("Source URL") - install.addTextField("Documentation URL") - install.addAction("Install") - install.addCancelAction("Cancel") - let result = await install.presentAlert() - if(0 == result) { - let name = install.textFieldValue(0) - let source = install.textFieldValue(1) - let documentation = install.textFieldValue(2) - await this.installScript(name, source, documentation) - } - } - - async run() - { - if(config.runsInWidget) { - await this.updateScriptsData(); - Script.setWidget(this.getWidget()) - } else { - this.ensureCorrectScriptNaming(); - this.showLoadingIndicator(); - await this.updateScriptsData(); - this.render(); - this.table.present(true); - this.checkForInstallationRequestFromWeb(); - } - } - - getWidget() { - let list = new ListWidget(); - let header = list.addText("🧑‍🚀 ScriptDude".toUpperCase()); - header.font = Font.mediumSystemFont(13); - list.addSpacer(); - let number = list.addText(this.updateableScripts.length+""); - number.font = Font.largeTitle(); - number.rightAlignText(); - let title = list.addText("Updates".toUpperCase()); - title.font = Font.mediumSystemFont(13); - title.rightAlignText(); - list.refreshAfterDate = new Date(Date.now() + 60*60*1000); - return list; - } - - checkForInstallationRequestFromWeb() { - try { - let data = args.queryParameters; - if(data.name && data.source && data.docs) { - this.installScript(data.name, data.source, data.docs, data.icon, data.color, true); - } - } catch(e) { - // Input malformed - } - } - - showLoadingIndicator() { - let row = new UITableRow(); - row.addText("Loading", "Please be patient, ScriptDude is collecting information about your scripts and checks for available updates."); - row.height = 100; - this.table.removeAllRows(); - this.table.addRow(row); - this.table.reload(); - } - - updateScript(script) - { - let [scriptableHeader, code] = this.mergeHeaders(script.content, script.updatePayload.code); - let codeToStore = Data.fromString(`${scriptableHeader}// This script was downloaded using ScriptDude.\n// Do not remove these lines, if you want to benefit from automatic updates.\n// source: ${script.source}; docs: ${script.docs}; hash: ${script.updatePayload.hash};\n\n${code}`); - this.fileManager.write(script.path, codeToStore); - this.showLoadingIndicator(); - this.updateScriptsData().then(() => { this.render() }) - } - - async updateScriptsData() { - let files = this.fileManager.listContents(this.documentsDirectory) - let managedScripts = files - // Convert to full paths - .map(fileName => this.fileManager.joinPath(this.documentsDirectory, fileName)) - // Remove directories - .filter(filePath => !this.fileManager.isDirectory(filePath)) - // Add file name and content metadata - .map(filePath => { - return { - path: filePath, - name: this.fileManager.fileName(filePath), - content: this.fileManager.read(filePath).toRawString() - }; - }) - // Filter for scripts that show up in Scriptable - .filter(file => file.content && file.content.trimLeft().startsWith("// Variables used by Scriptable.")) - // Add source and origin metadata - .map(file => { - let potentialScriptData = file.content - .split("\n", 50) // Scan first max. 50 lines - .filter(line => line.length != 0) // Skip empty lines - .filter(line => line.indexOf('//') != -1) // Take only comments - .map(line => line.substr(2).trim()) // Remove comment slashes - .filter(line => - line.indexOf('source:') != -1 - && line.indexOf('hash:') != -1 - && line.indexOf('docs:') != -1 - ); - if(!!potentialScriptData && potentialScriptData.length > 0) { - let customMetadata = potentialScriptData[0] - .split(';') - .map(keyValue => keyValue.split(':').map(text => text.trim())) - .filter(keyValue => keyValue[0].length) - .reduce((dict, addable) => { - dict[addable.shift()] = addable.join(':'); - return dict; - }, {}); - if(!!customMetadata['source'] - && !!customMetadata['hash']) { - file.source = this.makeUrlUpdateable(customMetadata['source']); - file.hash = customMetadata['hash']; - file.docs = customMetadata['docs'] || ''; - } - } - return file; - }) - // Filter for scripts managed by Scriptstore - .filter(file => !!file.source && !!file.hash); - - managedScripts = await Promise.all(managedScripts.map(async (script) => { - let req = new Request(script.source); - let code = await req.loadString(); - let hash = this.hashCode(code); - script.updateAvailable = hash != script.hash; - script.updatePayload = { - hash: hash, - code: code - } - return script; - })); - managedScripts = managedScripts.sort((a, b) => a.name < b.name ? -1 : 1); - this.updateableScripts = managedScripts.filter(script => script.updateAvailable); - this.uptodateScripts = managedScripts.filter(script => !script.updateAvailable); - } - - } - - await new ScriptDude().run(); - Script.complete(); \ No newline at end of file + getPackageUISection(title, scripts) { + let rows = []; + let header = new UITableRow(); + let text = header.addText(title); + text.titleFont = Font.headline(); + rows.push(header, ...scripts.map(this.getPackageUIRow.bind(this)), new UITableRow()); + return rows; + } + + mergeHeaders(codeOld, codeNew) { + let icon = null, + color = null; + + if (!!codeOld) { + let lines = codeOld.split("\n", 20); + for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { + const line = lines[lineIndex]; + let match = line.match(/icon-color: ([\w\-]*);/); + if (match) color = match[1]; + match = line.match(/icon-glyph: ([\w\-]*);/); + if (match) icon = match[1]; + if (icon != null && color != null) break; + } + } + + let scriptableHeader = ""; // detect Scriptable header + if (codeNew.trim().startsWith("// Variables used by Scriptable")) { + let lines = codeNew.split("\n"); // HINT: to improve speed, we could use just the first 20 lines of the code -> code.split("\n", 20); + for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { + let line = lines[lineIndex]; + if (line.indexOf("// Variables used by Scriptable.") != -1 || line.indexOf("// These must be at the very top of the file") != -1) { + scriptableHeader += line + "\n"; + codeNew = codeNew.substr(line.length + 1); // Remove scriptable header from original code + } else if (line.match(/\/\/( [\w\-]*: [\w\-]*;)+/)) { + // Find "key: value;" list used by scriptable + codeNew = codeNew.substr(line.length + 1); // Remove scriptable header from original code + let match = line.match(/icon-color: ([\w\-]*);/); + if (match) line = line.replace(match[1], color); + match = line.match(/icon-glyph: ([\w\-]*);/); + if (match) line = line.replace(match[1], icon); + + scriptableHeader += line + "\n"; + } else { + break; + } + } + } else { + scriptableHeader = `// Variables used by Scriptable.\n// These must be at the very top of the file. Do not edit.\n// icon-color: ${ + color || "blue" + }; icon-glyph: ${icon || "circle"};\n`; + } + return [scriptableHeader, codeNew]; + } + + getPackageUIRow(script) { + const iconWidth = 60; + let row = new UITableRow(); + let text = row.addText(script.name, script.source.split("/")[2]); + text.subtitleFont = Font.systemFont(10); + if (script.updateAvailable) { + let updateButton = row.addButton("Update"); + updateButton.titleColor = Color.red(); + updateButton.rightAligned(); + updateButton.onTap = () => { + this.updateScript(script); + }; + updateButton.widthWeight = iconWidth; + } + let documentationButton = row.addButton("Docs"); + documentationButton.rightAligned(); + documentationButton.onTap = () => { + Safari.open(script.docs); + }; + documentationButton.widthWeight = iconWidth; + let width = Device.screenSize().width - (script.updateAvailable ? iconWidth * 2 : iconWidth); + text.widthWeight = width; + return row; + } + + async installScript(name, sourceUrl, documentationUrl, icon, color, showMessage) { + sourceUrl = this.makeUrlUpdateable(sourceUrl); + let filePath = this.fileManager.joinPath(this.documentsDirectory, name + ".js"); + if (this.fileManager.fileExists(filePath)) { + let error = new Alert(); + error.title = `A script with the name ${name} does already exist.`; + error.presentAlert(); + return; + } + if (false != showMessage) { + let warning = new Alert(); + warning.title = "Warning"; + warning.message = `Scriptable scripts can access sensitive data on your device. Make sure to check downloaded scripts before running them for the first time. Do you want to continue downloading "${name}" from "${sourceUrl}"?`; + warning.addAction("Continue"); + warning.addCancelAction("Cancel"); + let result = await warning.presentAlert(); + if (-1 == result) { + return; + } + } + let req = new Request(sourceUrl); + let codeOriginal = await req.loadString(); + let hash = this.hashCode(codeOriginal); + let [scriptableHeader, code] = this.mergeHeaders(null, codeOriginal); + + let codeToStore = Data.fromString( + `${scriptableHeader}// This script was downloaded using ScriptDude.\n// Do not remove these lines, if you want to benefit from automatic updates.\n// source: ${sourceUrl}; docs: ${documentationUrl}; hash: ${hash};\n\n${code}` + ); + this.fileManager.write(filePath, codeToStore); + this.showLoadingIndicator(); + this.updateScriptsData().then(() => { + this.render(); + }); + } + + async getInstallationUI() { + let install = new Alert(); + install.title = "Install"; + install.message = "ScriptDude makes downloading and updating Scriptable scripts easy."; + install.addTextField("Script Name"); + install.addTextField("Source URL"); + install.addTextField("Documentation URL"); + install.addAction("Install"); + install.addCancelAction("Cancel"); + let result = await install.presentAlert(); + if (0 == result) { + let name = install.textFieldValue(0); + let source = install.textFieldValue(1); + let documentation = install.textFieldValue(2); + await this.installScript(name, source, documentation); + } + } + + async run() { + if (config.runsInWidget) { + await this.updateScriptsData(); + Script.setWidget(this.getWidget()); + } else { + this.ensureCorrectScriptNaming(); + this.showLoadingIndicator(); + await this.updateScriptsData(); + this.render(); + this.table.present(true); + this.checkForInstallationRequestFromWeb(); + } + } + + getWidget() { + let list = new ListWidget(); + let header = list.addText("🧑‍🚀 ScriptDude".toUpperCase()); + header.font = Font.mediumSystemFont(13); + list.addSpacer(); + let number = list.addText(this.updateableScripts.length + ""); + number.font = Font.largeTitle(); + number.rightAlignText(); + let title = list.addText("Updates".toUpperCase()); + title.font = Font.mediumSystemFont(13); + title.rightAlignText(); + list.refreshAfterDate = new Date(Date.now() + 60 * 60 * 1000); + return list; + } + + checkForInstallationRequestFromWeb() { + try { + let data = args.queryParameters; + if (data.name && data.source && data.docs) { + this.installScript(data.name, data.source, data.docs, data.icon, data.color, true); + } + } catch (e) { + // Input malformed + } + } + + showLoadingIndicator() { + let row = new UITableRow(); + row.addText("Loading", "Please be patient, ScriptDude is collecting information about your scripts and checks for available updates."); + row.height = 100; + this.table.removeAllRows(); + this.table.addRow(row); + this.table.reload(); + } + + updateScript(script) { + let [scriptableHeader, code] = this.mergeHeaders(script.content, script.updatePayload.code); + let codeToStore = Data.fromString( + `${scriptableHeader}// This script was downloaded using ScriptDude.\n// Do not remove these lines, if you want to benefit from automatic updates.\n// source: ${script.source}; docs: ${script.docs}; hash: ${script.updatePayload.hash};\n\n${code}` + ); + this.fileManager.write(script.path, codeToStore); + this.showLoadingIndicator(); + this.updateScriptsData().then(() => { + this.render(); + }); + } + + async updateScriptsData() { + let files = this.fileManager.listContents(this.documentsDirectory); + let managedScripts = files + // Convert to full paths + .map((fileName) => this.fileManager.joinPath(this.documentsDirectory, fileName)) + // Remove directories + .filter((filePath) => !this.fileManager.isDirectory(filePath)) + // Add file name and content metadata + .map((filePath) => { + return { + path: filePath, + name: this.fileManager.fileName(filePath), + content: this.fileManager.read(filePath).toRawString(), + }; + }) + // Filter for scripts that show up in Scriptable + .filter((file) => file.content && file.content.trimLeft().startsWith("// Variables used by Scriptable.")) + // Add source and origin metadata + .map((file) => { + let potentialScriptData = file.content + .split("\n", 50) // Scan first max. 50 lines + .filter((line) => line.length != 0) // Skip empty lines + .filter((line) => line.indexOf("//") != -1) // Take only comments + .map((line) => line.substr(2).trim()) // Remove comment slashes + .filter((line) => line.indexOf("source:") != -1 && line.indexOf("hash:") != -1 && line.indexOf("docs:") != -1); + if (!!potentialScriptData && potentialScriptData.length > 0) { + let customMetadata = potentialScriptData[0] + .split(";") + .map((keyValue) => keyValue.split(":").map((text) => text.trim())) + .filter((keyValue) => keyValue[0].length) + .reduce((dict, addable) => { + dict[addable.shift()] = addable.join(":"); + return dict; + }, {}); + if (!!customMetadata["source"] && !!customMetadata["hash"]) { + file.source = this.makeUrlUpdateable(customMetadata["source"]); + file.hash = customMetadata["hash"]; + file.docs = customMetadata["docs"] || ""; + } + } + return file; + }) + // Filter for scripts managed by Scriptstore + .filter((file) => !!file.source && !!file.hash); + + managedScripts = await Promise.all( + managedScripts.map(async (script) => { + let req = new Request(script.source); + let code = await req.loadString(); + let hash = this.hashCode(code); + script.updateAvailable = hash != script.hash; + script.updatePayload = { + hash: hash, + code: code, + }; + return script; + }) + ); + managedScripts = managedScripts.sort((a, b) => (a.name < b.name ? -1 : 1)); + this.updateableScripts = managedScripts.filter((script) => script.updateAvailable); + this.uptodateScripts = managedScripts.filter((script) => !script.updateAvailable); + } +} + +await new ScriptDude().run(); +Script.complete(); From 8579007154b1c19814e397b8dde6167ffa5a6d10 Mon Sep 17 00:00:00 2001 From: Elliott-Liu <16389800+Elliott-Liu@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:58:03 +0000 Subject: [PATCH 7/7] Install Prettier --- package.json | 3 +++ pnpm-lock.yaml | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 pnpm-lock.yaml diff --git a/package.json b/package.json index 41827cb..cae6e2e 100644 --- a/package.json +++ b/package.json @@ -20,5 +20,8 @@ "repository": { "type": "git", "url": "git+https://github.com/kevinkub/scriptdu.de.git" + }, + "devDependencies": { + "prettier": "^3.2.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..938a8c3 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,18 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +devDependencies: + prettier: + specifier: ^3.2.5 + version: 3.2.5 + +packages: + + /prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + engines: {node: '>=14'} + hasBin: true + dev: true