├── .gitignore
├── README.md
├── scriptable-CaschysBlog.js
├── widget-config.jpeg
├── widget-examples.jpeg
├── widgetConfig.jpeg
└── widgetExamples.jpeg
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DIESES SKRIPT WIRD NICHT MEHR LÄNGER VON MIR UNTERSTÜTZT.
2 | # BITTE NUTZT DAS NEUE SKRIPT:
3 | # https://github.com/Saudumm/scriptable-News-Widget
4 |
5 | ## Alte Beschreibung:
6 |
7 | ## scriptable-CaschysBlog
8 | iOS Scriptable Widget für News von Caschys Blog (stadt-bremerhaven.de)
9 |
10 | Das Skript sollte auch für die meisten anderen WordPress Seiten funktionieren.
11 |
12 | ## Voraussetzung:
13 |
14 | Scriptable für iOS: [Link](https://apps.apple.com/de/app/scriptable/id1405459188)
15 |
16 | ## Changelog
17 |
18 | - v1.0 - Erster Upload
19 | - v1.1 - Fix für Anzeige von Datum und Uhrzeit je nach Systemsprache
20 | - v1.2 - Widget kann jetzt mit Parametern angepasst werden, sogar mit Hintergrundbildern! (Siehe Code Kommentare für weitere Infos)
21 | - v1.3 - Bugfix für URLs mit Sonderzeichen
22 | - v1.4 - Eigene Hintergrundbilder für alle Widget Größen
23 | - v1.5 - Code gewaltig aufgeräut, neue Beschreibungen für die Anpassung des Widgets
24 | - v1.5.1 - Fix für die Anzeige des Datums
25 | - v1.5.2 - Twitter Link hinzugefügt
26 | - v1.6.0 - Unschärfeeffekt für Hintergrundbilder (oder Artikelbilder bei kleinen Widgets)
27 | - v1.6.1 - Korrekte Anzeige für Datum/Uhrzeit
28 |
29 | ---
30 |
31 | _Falls mir jemand einen Kaffee ausgeben möchte 😊: https://ko-fi.com/saudumm_
32 |
33 | ---
34 |
35 | ## Konfiguration:
36 |
37 | 1. Skript zu Scriptable hinzufügen
38 | 2. Widget auf Homescreen erstellen und Script auswählen
39 | 3. Wer möchte, kann die Hintergrundfarbe (oder sogar einen Farbverlauf) direkt im Code anpassen. Der entprechende Teil wurde im Code kommentiert/beschrieben. Hintergrundbilder sind auch möglich.
40 |
41 | Man kann auch das Layout und den Hintergrund des Widgets anpassen. Einfach lange auf das Widget drücken und "Widget bearbeiten" auswählen.
42 |
43 | 
44 |
45 | ### Widget Parameters
46 | - Beispiel: small|https://www.stadt-bremerhaven.de|Caschys Blog|image-name.jpg
47 | - Parameter Reihenfolge: widget size, site url, site name, background image
48 | - Parameter müssen durch | getrennt sein
49 | - Man kann auch Parameter weglassen, zum Beispiel das Hintergrundbild: small|https://www.stadt-bremerhaven.de|Caschys Blog
50 | - Man kann auch nur "small", "medium" oder "large" als Parameter setzen
51 | - Nicht gesetzte Parameter werden im Code durch die Standard Config genutzt
52 |
53 | ## Beispiele:
54 |
55 | 
56 |
57 | - Oben Links: Kleines Widget mit Standard Parameter
58 | - Oben Mitte: Kleines Widget mit Standard Parameter und Hintergrundbild
59 | - Oben Rechts: Mittleres Widget mit Parameter "medium"
60 | - Mitte Links: Mittleres Widget mit Standard Parameter "small"
61 | - Unten Links: Mittleres Widget mit Parameter "medium" und Hintergrundbild
62 | - Unten Rechts: Großes Widget mit Parameter "large" und Hintergrundbild
63 |
--------------------------------------------------------------------------------
/scriptable-CaschysBlog.js:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: deep-blue; icon-glyph: city;
4 | // v1.6.1 coded by Saudumm (https://twitter.com/saudumm)
5 | // GitHub: https://github.com/Saudumm/scriptable-CaschysBlog
6 |
7 | /* WIDGET PARAMETERS: you can long press on the widget on your homescreen and edit parameters
8 | - example: small|https://www.stadt-bremerhaven.de|Caschys Blog|background.jpg|false|true
9 | - parameter order has to be: widget size, site url, site name, background image, blur background image, background image gradient
10 | - parameters have to be separated by |
11 | - You can omit parameters, for example background image: small|https://www.stadt-bremerhaven.de|Caschys Blog
12 | - you can just set "small", "medium" or "large" as a parameter
13 | - parameters that are not set will be set by the standard widget config
14 | */
15 |
16 | /* STANDARD WIDGET CONFIG: standard config below can be overwritten by widget parameters
17 | WIDGET_SIZE: small, medium, large
18 | SITE_URL: address (URL) of the website you want to fetch posts from
19 | SITE_NAME: name of the website to display in the widget
20 | BG_IMAGE_NAME: CASE SENSITIVE! filename of the custom background image, set to none if you don't want a custom image
21 | - Note: custom background image files have to be in the Scriptable iCloud Files directory (same as the script js file)
22 | BG_IMAGE_BLUR: true if you want background images to be blurred, false if not
23 | BG_IMAGE_GRADIENT: true = gradient over the background image, false = no gradient
24 | */
25 | var WIDGET_SIZE = "small";
26 | var SITE_URL = "https://www.stadt-bremerhaven.de";
27 | var SITE_NAME = "Caschys Blog";
28 | var BG_IMAGE_NAME = "none";
29 | var BG_IMAGE_BLUR = "true";
30 | var BG_IMAGE_GRADIENT = "true";
31 |
32 | /* COLOR CONFIG: You can edit almost all colors of your widget
33 | SHOW_POST_IMAGES: true = display images next to the post headlines; set to false if you don't want images next to posts
34 | - Note: combining SHOW_POST_IMAGES = true + WIDGET_SIZE = small will ignore BG_GRADIENT_COLOR values in small config widgets
35 | BG_GRADIENT: widget background; true = use gradient; false = single background color
36 | BG_COLOR: background color value if BG_GRADIENT = false
37 | BG_GRADIENT_COLOR_TOP: gradient color at the top
38 | BG_GRADIENT_COLOR_BTM: gradient color at the bottom
39 | BG_GRADIENT_OVERLAY_TOP: gradient background image overlay color top
40 | BG_GRADIENT_OVERLAY_BTM: gradient background image overlay color bottom
41 | FONT_COLOR_SITENAME: font color of the website name (SITE_NAME)
42 | FONT_COLOR_POST_DATE: font color of the date/time label
43 | FONT_COLOR_HEADLINE: font color of the post title
44 | */
45 | const SHOW_POST_IMAGES = true;
46 | var BG_GRADIENT = false;
47 | const BG_COLOR = new Color("#1c1c1e");
48 | var BG_GRADIENT_COLOR_TOP = new Color("#222222");
49 | var BG_GRADIENT_COLOR_BTM = new Color("#444444");
50 | const BG_GRADIENT_OVERLAY_TOP = new Color("#1c1c1e", 0.3);
51 | const BG_GRADIENT_OVERLAY_BTM = new Color("#1c1c1e", 1.0);
52 | const FONT_COLOR_SITENAME = Color.white();
53 | const FONT_COLOR_POST_DATE = Color.gray();
54 | const FONT_COLOR_HEADLINE = Color.white();
55 |
56 | // DO NOT CHANGE ANYTHING BELOW!
57 | // Unless you know what you're doing.
58 | // Unlike me, I don't know what I'm doing.
59 | if (args.widgetParameter) {
60 | let param = args.widgetParameter.split("|");
61 | if (param.length >= 1) {WIDGET_SIZE = param[0];}
62 | if (param.length >= 2) {SITE_URL = param[1];}
63 | if (param.length >= 3) {SITE_NAME = param[2];}
64 | if (param.length >= 4) {BG_IMAGE_NAME = param[3];}
65 | if (param.length >= 5) {BG_IMAGE_BLUR = param[4];}
66 | if (param.length >= 6) {BG_IMAGE_GRADIENT = param[5];}
67 | }
68 |
69 | // set the number of posts depending on WIDGET_SIZE
70 | var POST_COUNT = 1;
71 | switch (WIDGET_SIZE) {
72 | case "small":
73 | POST_COUNT = 1;
74 | break;
75 | case "medium":
76 | POST_COUNT = 2;
77 | break;
78 | case "large":
79 | POST_COUNT = 5;
80 | break;
81 | }
82 |
83 | // JSON URL of WP Posts (WordPress Standard)
84 | const JSON_API_URL = SITE_URL+"/wp-json/wp/v2/posts";
85 |
86 | // check directories
87 | checkFileDirs()
88 |
89 | // Create Widget
90 | let widget = await createWidget();
91 |
92 | if (!config.runsInWidget) {
93 | switch (WIDGET_SIZE) {
94 | case "small":
95 | await widget.presentSmall();
96 | break;
97 | case "medium":
98 | await widget.presentMedium();
99 | break;
100 | case "large":
101 | await widget.presentLarge();
102 | break;
103 | }
104 | }
105 |
106 | Script.setWidget(widget);
107 | Script.complete();
108 |
109 | // create the widget
110 | // parameter: none
111 | // result: an awesome widget
112 | async function createWidget() {
113 | const postData = await getData();
114 | const list = new ListWidget();
115 |
116 | // display name of the website
117 | const siteName = list.addText(SITE_NAME.toUpperCase());
118 | siteName.font = Font.heavySystemFont(13);
119 | siteName.textColor = FONT_COLOR_SITENAME;
120 |
121 | list.addSpacer();
122 |
123 | if (postData) {
124 | if (POST_COUNT == 1) {
125 | // load widget background image (if SHOW_POST_IMAGES = true or BG_IMAGE_NAME is set)
126 | if (SHOW_POST_IMAGES == true && BG_IMAGE_NAME == "none") {
127 | let postBGImage = await loadPostImage(postData.arrPostIMGPaths[0]);
128 | if (BG_IMAGE_BLUR == "true") {postBGImage = await blurImage(postBGImage, 2, 2);}
129 | list.backgroundImage = postBGImage;
130 |
131 | // draw gradient over background image for better readability
132 | BG_GRADIENT = true;
133 | BG_GRADIENT_COLOR_TOP = BG_GRADIENT_OVERLAY_TOP;
134 | BG_GRADIENT_COLOR_BTM = BG_GRADIENT_OVERLAY_BTM;
135 |
136 | // small shadow outline on SITE_NAME for better readability
137 | siteName.shadowRadius = 1;
138 | siteName.shadowColor = Color.black();
139 | }
140 |
141 | const postStack = list.addStack();
142 | postStack.layoutVertically();
143 |
144 | const labelDateTime = postStack.addText(convertJSONDateString(postData.arrPostDates[0]));
145 | labelDateTime.font = Font.heavySystemFont(12);
146 | labelDateTime.textColor = FONT_COLOR_POST_DATE;
147 | labelDateTime.lineLimit = 1;
148 | labelDateTime.minimumScaleFactor = 0.5;
149 |
150 | const labelHeadline = postStack.addText(postData.arrPostTitles[0]);
151 | labelHeadline.font = Font.heavySystemFont(12);
152 | labelHeadline.textColor = FONT_COLOR_HEADLINE;
153 | labelHeadline.lineLimit = 3;
154 |
155 | list.url = postData.arrPostURLs[0];
156 | } else {
157 | let arrStackRow = [];
158 | arrStackRow.length = POST_COUNT;
159 | let arrStackCol = [];
160 | arrStackCol.length = POST_COUNT;
161 | let arrLblPostDate = [];
162 | arrLblPostDate.length = POST_COUNT;
163 | let arrLblPostTitle = [];
164 | arrLblPostTitle.length = POST_COUNT;
165 | let arrLblPostIMG = [];
166 | arrLblPostIMG.length = POST_COUNT;
167 |
168 | let i;
169 | for (i = 0; i < POST_COUNT; i++) {
170 | arrStackRow[i] = list.addStack();
171 | arrStackRow[i].layoutHorizontally();
172 | arrStackRow[i].url = postData.arrPostURLs[i];
173 |
174 | arrStackCol[i] = arrStackRow[i].addStack();
175 | arrStackCol[i].layoutVertically();
176 |
177 | arrLblPostDate[i] = arrStackCol[i].addText(convertJSONDateString(postData.arrPostDates[i]));
178 | arrLblPostDate[i].font = Font.heavySystemFont(12);
179 | arrLblPostDate[i].textColor = FONT_COLOR_POST_DATE;
180 | arrLblPostDate[i].lineLimit = 1;
181 | arrLblPostDate[i].minimumScaleFactor = 0.5;
182 |
183 | arrLblPostTitle[i] = arrStackCol[i].addText(postData.arrPostTitles[i]);
184 | arrLblPostTitle[i].font = Font.heavySystemFont(12);
185 | arrLblPostTitle[i].textColor = FONT_COLOR_HEADLINE;
186 | arrLblPostTitle[i].lineLimit = 2;
187 |
188 | if (SHOW_POST_IMAGES == true) {
189 | arrStackRow[i].addSpacer();
190 | arrLblPostIMG[i] = await loadSmallPostImage(postData.arrPostIMGPaths[i]);
191 | arrLblPostIMG[i] = await cropImage(arrLblPostIMG[i]);
192 | arrLblPostIMG[i] = arrStackRow[i].addImage(arrLblPostIMG[i]);
193 | arrLblPostIMG[i].imageSize = new Size(45,45);
194 | arrLblPostIMG[i].cornerRadius = 8;
195 | arrLblPostIMG[i].rightAlignImage();
196 | }
197 |
198 | if (i < POST_COUNT-1) {list.addSpacer();}
199 | }
200 | }
201 | } else {
202 | const err_msg = list.addText("Couldn't load data");
203 | err_msg.font = Font.regularSystemFont(12);
204 | err_msg.textColor = FONT_COLOR_HEADLINE;
205 | }
206 |
207 | // widget background (single color or gradient)
208 | if (BG_IMAGE_NAME != "none") {
209 | let bgImagePath = await loadBGImage(BG_IMAGE_NAME);
210 | if (bgImagePath != "not found") {
211 | let customBGImage
212 | if (BG_IMAGE_BLUR == "true") {
213 | let fm = await newFileManager();
214 | customBGImage = await fm.readImage(bgImagePath);
215 | customBGImage = await blurImage(customBGImage, 2, 4);
216 | } else {
217 | customBGImage = await Image.fromFile(bgImagePath);
218 | }
219 | list.backgroundImage = customBGImage;
220 |
221 | if (BG_IMAGE_GRADIENT == "true") {
222 | // draw gradient over background image for better readability
223 | const gradient = new LinearGradient();
224 | gradient.locations = [0, 1];
225 | gradient.colors = [BG_GRADIENT_OVERLAY_TOP, BG_GRADIENT_OVERLAY_BTM];
226 | list.backgroundGradient = gradient;
227 | }
228 | } else {
229 | list.backgroundColor = BG_COLOR;
230 | }
231 | } else if (BG_GRADIENT == true) {
232 | const gradient = new LinearGradient();
233 | gradient.locations = [0, 1];
234 | gradient.colors = [BG_GRADIENT_COLOR_TOP, BG_GRADIENT_COLOR_BTM];
235 | list.backgroundGradient = gradient;
236 | } else {
237 | list.backgroundColor = BG_COLOR;
238 | }
239 |
240 | return list;
241 | }
242 |
243 | // get all the data for the widget - this is where the magic happens
244 | // parameter: nothing at all
245 | // result: a buttload of post data
246 | async function getData() {
247 | try {
248 | let loadedJSON = await new Request(JSON_API_URL).loadJSON();
249 |
250 | let POSTS_TO_LOAD = 5;
251 |
252 | let arrPostDates = [];
253 | arrPostDates.length = POSTS_TO_LOAD;
254 | let arrPostTitles = [];
255 | arrPostTitles.length = POSTS_TO_LOAD;
256 | let arrPostURLs = [];
257 | arrPostURLs.length = POSTS_TO_LOAD;
258 | let arrPostIMGURLs = [];
259 | arrPostIMGURLs.length = POSTS_TO_LOAD;
260 | let arrPostIMGPaths = [];
261 | arrPostIMGPaths.length = POSTS_TO_LOAD;
262 | let arrPostFileNames = [];
263 | arrPostFileNames.length = POSTS_TO_LOAD;
264 |
265 | let i;
266 | for (i = 0; i < POSTS_TO_LOAD; i++) {
267 | arrPostDates[i] = loadedJSON[i].date;
268 |
269 | arrPostTitles[i] = loadedJSON[i].title.rendered;
270 | arrPostTitles[i] = formatPostTitle(arrPostTitles[i]);
271 |
272 | arrPostURLs[i] = loadedJSON[i].guid.rendered;
273 |
274 | if (SHOW_POST_IMAGES == true) {
275 | arrPostIMGURLs[i] = await getMediaURL(loadedJSON[i].featured_media);
276 | arrPostIMGPaths[i] = await getImagePath(arrPostIMGURLs[i], loadedJSON[i].id);
277 | arrPostFileNames[i] = await getFileName(arrPostIMGURLs[i], loadedJSON[i].id);
278 | await downloadPostImage(arrPostIMGPaths[i], arrPostIMGURLs[i]);
279 | }
280 | }
281 |
282 | if (SHOW_POST_IMAGES == true) {await cleanUpImages(arrPostFileNames);}
283 |
284 | const result = {
285 | arrPostDates: arrPostDates,
286 | arrPostTitles: arrPostTitles,
287 | arrPostURLs: arrPostURLs,
288 | arrPostIMGPaths: arrPostIMGPaths
289 | };
290 |
291 | return result;
292 | } catch (e) {
293 | return null;
294 | }
295 | }
296 |
297 | // format the post title and replace all html entities with characters
298 | // parameter: string of the title
299 | // result: string with the title, readable by a human
300 | function formatPostTitle(strHeadline) {
301 | strHeadline = strHeadline.replaceAll(""", '"');
302 | strHeadline = strHeadline.replaceAll("&", "&");
303 | strHeadline = strHeadline.replaceAll("<", "<");
304 | strHeadline = strHeadline.replaceAll(">", ">");
305 | strHeadline = strHeadline.replaceAll(""", '"');
306 | strHeadline = strHeadline.replaceAll("&", "&");
307 | strHeadline = strHeadline.replaceAll("<", "<");
308 | strHeadline = strHeadline.replaceAll(">", ">");
309 | strHeadline = strHeadline.replaceAll("Œ", "Œ");
310 | strHeadline = strHeadline.replaceAll("œ", "œ");
311 | strHeadline = strHeadline.replaceAll("Š", "Š");
312 | strHeadline = strHeadline.replaceAll("š", "š");
313 | strHeadline = strHeadline.replaceAll("Ÿ", "Ÿ");
314 | strHeadline = strHeadline.replaceAll("ˆ", "ˆ");
315 | strHeadline = strHeadline.replaceAll("˜", "˜");
316 | strHeadline = strHeadline.replaceAll("–", "–");
317 | strHeadline = strHeadline.replaceAll("—", "—");
318 | strHeadline = strHeadline.replaceAll("‘", "‘");
319 | strHeadline = strHeadline.replaceAll("’", "’");
320 | strHeadline = strHeadline.replaceAll("‚", "‚");
321 | strHeadline = strHeadline.replaceAll("“", "“");
322 | strHeadline = strHeadline.replaceAll("”", "”");
323 | strHeadline = strHeadline.replaceAll("„", "„");
324 | strHeadline = strHeadline.replaceAll("†", "†");
325 | strHeadline = strHeadline.replaceAll("‡", "‡");
326 | strHeadline = strHeadline.replaceAll("…", "…");
327 | strHeadline = strHeadline.replaceAll("‰", "‰");
328 | strHeadline = strHeadline.replaceAll("‹", "‹");
329 | strHeadline = strHeadline.replaceAll("›", "›");
330 | strHeadline = strHeadline.replaceAll("€", "€");
331 |
332 | return strHeadline;
333 | }
334 |
335 | // convert the WordPress date and time to something a human can read
336 | // parameter: string with date and time (from WordPress)
337 | // result: a nicely formatted date and time
338 | function convertJSONDateString(strDate) {
339 | let date_conv = new Date(strDate);
340 | let dateTimeLocal = date_conv.toLocaleString([], {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit"})
341 | return dateTimeLocal
342 | }
343 |
344 | // get the featuredMedia image URL
345 | // parameter: featureMedia ID
346 | // result: encoded URL to the image file on the server
347 | async function getMediaURL(featuredMedia) {
348 | let featuredMediaJSONURL = SITE_URL+"/wp-json/wp/v2/media/"+featuredMedia;
349 | let loadedMediaJSON = await new Request(featuredMediaJSONURL).loadJSON();
350 | let mediaURL = loadedMediaJSON.source_url;
351 | mediaURL = await encodeURI(mediaURL);
352 | return mediaURL;
353 | }
354 |
355 | // set the filename of the post image (site name + image id + file extension)
356 | // parameter: url to the image, id of the image
357 | // result: filename of the image
358 | function getFileName(url, id) {
359 | let extension = /(?:\.([^.]+))?$/.exec(url)[0];
360 | let site_id = SITE_NAME.replace(/[^a-zA-Z]+/g, "").toLowerCase();
361 | return site_id+"-"+id+extension;
362 | }
363 |
364 | // set the complete file path for the image
365 | // parameter: url to the image, id of the image
366 | // result: local filepath of the image
367 | function getImagePath(url, id) {
368 | let fm = newFileManager();
369 |
370 | let docDir = fm.documentsDirectory();
371 | let postImageDir = docDir+"/wordpress-widget-data/post-images";
372 |
373 | // set fileName
374 | let fileName = getFileName(url, id);
375 | // join path
376 | let path = fm.joinPath(postImageDir, fileName);
377 |
378 | return path;
379 | }
380 |
381 | // download the post image (if it doesn't already exist)
382 | // parameter: path to the image, url to the image on the interwebs
383 | // result: a nice and warm feeling
384 | async function downloadPostImage(path, url) {
385 | let fm = newFileManager();
386 |
387 | let image;
388 |
389 | // check if file already exists
390 | if (fm.fileExists(path)) {
391 | await fm.downloadFileFromiCloud(path);
392 | } else {
393 | // download and store image
394 | let req = await new Request(url);
395 | let loadedImage = await req.load();
396 |
397 | await fm.write(path, loadedImage);
398 | }
399 |
400 | return;
401 | }
402 |
403 | // load post image from file path
404 | // parameter: path to the image
405 | // result: image
406 | function loadPostImage(path) {
407 | let fm = newFileManager();
408 | if (fm.fileExists(path)) {
409 | fm.downloadFileFromiCloud(path);
410 | let image = fm.readImage(path);
411 |
412 | return image;
413 | }
414 | }
415 |
416 | // load post image from file path and resize it
417 | // parameter: path to the image
418 | // result: image
419 | function loadSmallPostImage(path) {
420 | let fm = newFileManager();
421 | if (fm.fileExists(path)) {
422 | fm.downloadFileFromiCloud(path);
423 | let image = fm.readImage(path);
424 | image = resizeImage(image, 4);
425 |
426 | return image;
427 | }
428 | }
429 |
430 | // search for and load a local (or iCloud) background image
431 | // parameter: filename of the image (case sensitive!)
432 | // result: path to the image or "not found" if image doesn't exist
433 | async function loadBGImage(imageName) {
434 | let fm = newFileManager();
435 | let docDir = fm.documentsDirectory();
436 | let bgImageDir = docDir+"/wordpress-widget-data/background-images";
437 |
438 | let bgImagePathDocDir = fm.joinPath(docDir, imageName)
439 | let bgImagePathBGDir = fm.joinPath(bgImageDir, imageName)
440 |
441 | if (fm.fileExists(bgImagePathDocDir)) {
442 | return bgImagePathDocDir;
443 | } else if (fm.fileExists(bgImagePathBGDir)) {
444 | return bgImagePathBGDir;
445 | } else {
446 | return "not found";
447 | }
448 | }
449 |
450 | // create a new FileManager (iCloud or local)
451 | // parameter: nothing
452 | // result: FileManager
453 | function newFileManager() {
454 | let fm;
455 | try {
456 | fm = FileManager.iCloud();
457 | } catch(e) {
458 | fm = FileManager.local();
459 | }
460 | return fm;
461 | }
462 |
463 | // check if all folders are available and create them if needed
464 | // parameter: none
465 | // result: new directories if needed
466 | function checkFileDirs() {
467 | // Create new FileManager and set data dir
468 | let fm = newFileManager();
469 | let docDir = fm.documentsDirectory();
470 | let postImageDir = docDir+"/wordpress-widget-data/post-images";
471 | let bgImageDir = docDir+"/wordpress-widget-data/background-images";
472 |
473 | if (!fm.fileExists(postImageDir)) {fm.createDirectory(postImageDir, true);}
474 | if (!fm.fileExists(bgImageDir)) {fm.createDirectory(bgImageDir, true);}
475 | }
476 |
477 | // cleanup post image files
478 | // parameter: array with image filenames that are needed at the moment
479 | // result: a nice and clean data folder
480 | function cleanUpImages(arrFileNames) {
481 | let fm = newFileManager();
482 | let docDir = fm.documentsDirectory();
483 | let postImageDir = docDir+"/wordpress-widget-data/post-images";
484 |
485 | let arrFiles = fm.listContents(postImageDir);
486 |
487 | let site_id = SITE_NAME.replace(/[^a-zA-Z]+/g, "").toLowerCase();
488 |
489 | let arrFilesSite = [];
490 |
491 | for (i = 0; i < arrFiles.length; i++) {
492 | if (arrFiles[i].substring(0, site_id.length) === site_id) {arrFilesSite.push(arrFiles[i]);}
493 | }
494 |
495 | for (i = 0; i < arrFilesSite.length; i++) {
496 | if (!arrFileNames.includes(arrFilesSite[i])) {
497 | let path = fm.joinPath(postImageDir, arrFilesSite[i]);
498 | fm.remove(path);
499 | }
500 | }
501 | }
502 |
503 | // blur the background image
504 | // parameter: image, downscale factor, strength of blur effect
505 | // result: blurry image (well, it's better than nothing)
506 | async function blurImage(img, resFactor, blurStrength) {
507 | /*
508 | A big THANK YOU to Mario Klingemann for the Blur Code and Max Zeryck for the WebView Code
509 | code taken and modified from: https://github.com/mzeryck/Widget-Blur
510 | Follow @mzeryck on Twitter: https://twitter.com/mzeryck
511 | */
512 |
513 | const js = `
514 | /*
515 | StackBlur - a fast almost Gaussian Blur For Canvas
516 | Version: 0.5
517 | Author: Mario Klingemann
518 | Contact: mario@quasimondo.com
519 | Website: http://quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
520 | Twitter: @quasimondo
521 | In case you find this class useful - especially in commercial projects -
522 | I am not totally unhappy for a small donation to my PayPal account
523 | mario@quasimondo.de
524 | Or support me on flattr:
525 | https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript
526 | Copyright (c) 2010 Mario Klingemann
527 | Permission is hereby granted, free of charge, to any person
528 | obtaining a copy of this software and associated documentation
529 | files (the "Software"), to deal in the Software without
530 | restriction, including without limitation the rights to use,
531 | copy, modify, merge, publish, distribute, sublicense, and/or sell
532 | copies of the Software, and to permit persons to whom the
533 | Software is furnished to do so, subject to the following
534 | conditions:
535 | The above copyright notice and this permission notice shall be
536 | included in all copies or substantial portions of the Software.
537 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
538 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
539 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
540 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
541 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
542 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
543 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
544 | OTHER DEALINGS IN THE SOFTWARE.
545 | */
546 | var mul_table = [512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,
547 | 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,
548 | 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,
549 | 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,
550 | 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,
551 | 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,
552 | 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,
553 | 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,
554 | 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,
555 | 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,
556 | 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,
557 | 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,
558 | 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,
559 | 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,
560 | 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,
561 | 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259];
562 |
563 | var shg_table = [ 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
564 | 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
565 | 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
566 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
567 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
568 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
569 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
570 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
571 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
572 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
573 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
574 | 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
575 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
576 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
577 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
578 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 ];
579 |
580 | function stackBlurCanvasRGB(id, top_x, top_y, width, height, radius) {
581 | if (isNaN(radius) || radius < 1) {return;}
582 | radius |= 0;
583 |
584 | var canvas = document.getElementById(id);
585 | var context = canvas.getContext("2d");
586 | var imageData;
587 |
588 | try {
589 | imageData = context.getImageData(top_x, top_y, width, height);
590 | } catch(e) {
591 | alert("Cannot access image");
592 | throw new Error("unable to access image data: " + e);
593 | }
594 |
595 | var pixels = imageData.data;
596 |
597 | var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum,
598 | r_out_sum, g_out_sum, b_out_sum,
599 | r_in_sum, g_in_sum, b_in_sum,
600 | pr, pg, pb, rbs;
601 |
602 | var div = radius + radius + 1;
603 | var w4 = width << 2;
604 | var widthMinus1 = width - 1;
605 | var heightMinus1 = height - 1;
606 | var radiusPlus1 = radius + 1;
607 | var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;
608 |
609 | var stackStart = new BlurStack();
610 | var stack = stackStart;
611 | for (i = 1; i < div; i++) {
612 | stack = stack.next = new BlurStack();
613 | if (i == radiusPlus1) var stackEnd = stack;
614 | }
615 | stack.next = stackStart;
616 | var stackIn = null;
617 | var stackOut = null;
618 |
619 | yw = yi = 0;
620 |
621 | var mul_sum = mul_table[radius];
622 | var shg_sum = shg_table[radius];
623 |
624 | for (y = 0; y < height; y++) {
625 | r_in_sum = g_in_sum = b_in_sum = r_sum = g_sum = b_sum = 0;
626 |
627 | r_out_sum = radiusPlus1 * (pr = pixels[yi]);
628 | g_out_sum = radiusPlus1 * (pg = pixels[yi+1]);
629 | b_out_sum = radiusPlus1 * (pb = pixels[yi+2]);
630 |
631 | r_sum += sumFactor * pr;
632 | g_sum += sumFactor * pg;
633 | b_sum += sumFactor * pb;
634 |
635 | stack = stackStart;
636 |
637 | for (i = 0; i < radiusPlus1; i++) {
638 | stack.r = pr;
639 | stack.g = pg;
640 | stack.b = pb;
641 | stack = stack.next;
642 | }
643 |
644 | for (i = 1; i < radiusPlus1; i++) {
645 | p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);
646 | r_sum += (stack.r = (pr = pixels[p])) * (rbs = radiusPlus1 - i);
647 | g_sum += (stack.g = (pg = pixels[p+1])) * rbs;
648 | b_sum += (stack.b = (pb = pixels[p+2])) * rbs;
649 |
650 | r_in_sum += pr;
651 | g_in_sum += pg;
652 | b_in_sum += pb;
653 |
654 | stack = stack.next;
655 | }
656 |
657 |
658 | stackIn = stackStart;
659 | stackOut = stackEnd;
660 | for (x = 0; x < width; x++) {
661 | pixels[yi] = (r_sum * mul_sum) >> shg_sum;
662 | pixels[yi+1] = (g_sum * mul_sum) >> shg_sum;
663 | pixels[yi+2] = (b_sum * mul_sum) >> shg_sum;
664 |
665 | r_sum -= r_out_sum;
666 | g_sum -= g_out_sum;
667 | b_sum -= b_out_sum;
668 |
669 | r_out_sum -= stackIn.r;
670 | g_out_sum -= stackIn.g;
671 | b_out_sum -= stackIn.b;
672 |
673 | p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2;
674 |
675 | r_in_sum += (stackIn.r = pixels[p]);
676 | g_in_sum += (stackIn.g = pixels[p+1]);
677 | b_in_sum += (stackIn.b = pixels[p+2]);
678 |
679 | r_sum += r_in_sum;
680 | g_sum += g_in_sum;
681 | b_sum += b_in_sum;
682 |
683 | stackIn = stackIn.next;
684 |
685 | r_out_sum += (pr = stackOut.r);
686 | g_out_sum += (pg = stackOut.g);
687 | b_out_sum += (pb = stackOut.b);
688 |
689 | r_in_sum -= pr;
690 | g_in_sum -= pg;
691 | b_in_sum -= pb;
692 |
693 | stackOut = stackOut.next;
694 | yi += 4;
695 | }
696 | yw += width;
697 | }
698 |
699 | for (x = 0; x < width; x++) {
700 | g_in_sum = b_in_sum = r_in_sum = g_sum = b_sum = r_sum = 0;
701 |
702 | yi = x << 2;
703 | r_out_sum = radiusPlus1 * (pr = pixels[yi]);
704 | g_out_sum = radiusPlus1 * (pg = pixels[yi+1]);
705 | b_out_sum = radiusPlus1 * (pb = pixels[yi+2]);
706 |
707 | r_sum += sumFactor * pr;
708 | g_sum += sumFactor * pg;
709 | b_sum += sumFactor * pb;
710 |
711 | stack = stackStart;
712 |
713 | for (i = 0; i < radiusPlus1; i++) {
714 | stack.r = pr;
715 | stack.g = pg;
716 | stack.b = pb;
717 | stack = stack.next;
718 | }
719 |
720 | yp = width;
721 |
722 | for (i = 1; i <= radius; i++) {
723 | yi = (yp + x) << 2;
724 |
725 | r_sum += (stack.r = (pr = pixels[yi])) * (rbs = radiusPlus1 - i);
726 | g_sum += (stack.g = (pg = pixels[yi+1])) * rbs;
727 | b_sum += (stack.b = (pb = pixels[yi+2])) * rbs;
728 |
729 | r_in_sum += pr;
730 | g_in_sum += pg;
731 | b_in_sum += pb;
732 |
733 | stack = stack.next;
734 |
735 | if (i < heightMinus1) {yp += width;}
736 | }
737 |
738 | yi = x;
739 | stackIn = stackStart;
740 | stackOut = stackEnd;
741 | for (y = 0; y < height; y++) {
742 | p = yi << 2;
743 | pixels[p] = (r_sum * mul_sum) >> shg_sum;
744 | pixels[p+1] = (g_sum * mul_sum) >> shg_sum;
745 | pixels[p+2] = (b_sum * mul_sum) >> shg_sum;
746 |
747 | r_sum -= r_out_sum;
748 | g_sum -= g_out_sum;
749 | b_sum -= b_out_sum;
750 |
751 | r_out_sum -= stackIn.r;
752 | g_out_sum -= stackIn.g;
753 | b_out_sum -= stackIn.b;
754 |
755 | p = (x + (((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width)) << 2;
756 |
757 | r_sum += (r_in_sum += (stackIn.r = pixels[p]));
758 | g_sum += (g_in_sum += (stackIn.g = pixels[p+1]));
759 | b_sum += (b_in_sum += (stackIn.b = pixels[p+2]));
760 |
761 | stackIn = stackIn.next;
762 |
763 | r_out_sum += (pr = stackOut.r);
764 | g_out_sum += (pg = stackOut.g);
765 | b_out_sum += (pb = stackOut.b);
766 |
767 | r_in_sum -= pr;
768 | g_in_sum -= pg;
769 | b_in_sum -= pb;
770 |
771 | stackOut = stackOut.next;
772 |
773 | yi += width;
774 | }
775 | }
776 |
777 | context.putImageData(imageData, top_x, top_y);
778 | }
779 |
780 | function BlurStack() {
781 | this.r = 0;
782 | this.g = 0;
783 | this.b = 0;
784 | this.a = 0;
785 | this.next = null;
786 | }
787 |
788 | // Set up the canvas
789 | const img = document.getElementById("blurImg");
790 | const canvas = document.getElementById("mainCanvas");
791 | const w = img.naturalWidth;
792 | const h = img.naturalHeight;
793 | const maxW = w / ${resFactor};
794 | const maxH = h / ${resFactor};
795 | canvas.style.width = w + "px";
796 | canvas.style.height = h + "px";
797 | canvas.width = maxW;
798 | canvas.height = maxH;
799 | const context = canvas.getContext("2d");
800 | context.clearRect(0, 0, w, h);
801 | context.drawImage(img, 0, 0, maxW, maxH);
802 |
803 | // Get the image data from the context
804 | var imageData = context.getImageData(0,0,w,h);
805 | // Draw over the old image
806 | context.putImageData(imageData,0,0);
807 | // Blur the image
808 | stackBlurCanvasRGB("mainCanvas", 0, 0, w, h, ${blurStrength});
809 | // Return a base64 representation
810 | canvas.toDataURL();
811 | `;
812 |
813 | // Convert the images and create the HTML
814 | let blurImgData = Data.fromPNG(img).toBase64String();
815 | let html = `
`;
816 |
817 | // Make the web view and get its return value
818 | let view = new WebView();
819 | await view.loadHTML(html);
820 | let returnValue = await view.evaluateJavaScript(js);
821 |
822 | // Remove the data type from the string and convert to data
823 | let imageDataString = returnValue.slice(22);
824 | let imageData = Data.fromBase64String(imageDataString);
825 |
826 | // Convert to image before returning
827 | let imageFromData = Image.fromData(imageData);
828 | return imageFromData;
829 | }
830 |
831 | // resize the background image
832 | // parameter: image, downscale factor
833 | // result: resized image (duh)
834 | async function resizeImage(img, resFactor) {
835 | const js = `
836 | // Set up the canvas
837 | const img = document.getElementById("resImg");
838 | const canvas = document.getElementById("mainCanvas");
839 | const w = img.naturalWidth;
840 | const h = img.naturalHeight;
841 | const maxW = w / ${resFactor};
842 | const maxH = h / ${resFactor};
843 | canvas.style.width = w + "px";
844 | canvas.style.height = h + "px";
845 | canvas.width = maxW;
846 | canvas.height = maxH;
847 | const context = canvas.getContext("2d");
848 | context.clearRect(0, 0, w, h);
849 | context.drawImage(img, 0, 0, maxW, maxH);
850 |
851 | // Get the image data from the context
852 | var imageData = context.getImageData(0,0,w,h);
853 | // Draw over the old image
854 | context.putImageData(imageData,0,0);
855 | // Return a base64 representation
856 | canvas.toDataURL();
857 | `;
858 |
859 | // Convert the images and create the HTML
860 | let resImgData = Data.fromPNG(img).toBase64String();
861 | let html = `
`;
862 |
863 | // Make the web view and get its return value
864 | let view = new WebView();
865 | await view.loadHTML(html);
866 | let returnValue = await view.evaluateJavaScript(js);
867 |
868 | // Remove the data type from the string and convert to data
869 | let imageDataString = returnValue.slice(22);
870 | let imageData = Data.fromBase64String(imageDataString);
871 |
872 | // Convert to image before returning
873 | let imageFromData = Image.fromData(imageData);
874 |
875 | return imageFromData;
876 | }
877 |
878 | // crop the image to a rectangle (to be honest, it's a square)
879 | // parameter: image
880 | // result: square image
881 | function cropImage(img) {
882 | let height = img.size.height;
883 | let width = img.size.width;
884 |
885 | let imgShortSide = Math.min(height, width);
886 | let imgLongSide = Math.max(height, width);
887 |
888 | if (imgShortSide != imgLongSide) {
889 | let imgCropTotal = imgLongSide - imgShortSide;
890 | let imgCropSide = Math.floor(imgCropTotal / 2);
891 |
892 | let rect;
893 | switch (imgShortSide) {
894 | case height:
895 | rect = new Rect(imgCropSide, 0, imgShortSide, imgShortSide);
896 | break;
897 | case width:
898 | rect = new Rect(0, imgCropSide, imgShortSide, imgShortSide);
899 | break;
900 | }
901 |
902 | let draw = new DrawContext();
903 | draw.size = new Size(rect.width, rect.height);
904 |
905 | draw.drawImageAtPoint(img, new Point(-rect.x, -rect.y));
906 | img = draw.getImage();
907 | }
908 |
909 | return img;
910 | }
911 |
--------------------------------------------------------------------------------
/widget-config.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Saudumm/scriptable-CaschysBlog/e0bbcf82812cc00aeb1b2b1067ef9c4a41e9b82e/widget-config.jpeg
--------------------------------------------------------------------------------
/widget-examples.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Saudumm/scriptable-CaschysBlog/e0bbcf82812cc00aeb1b2b1067ef9c4a41e9b82e/widget-examples.jpeg
--------------------------------------------------------------------------------
/widgetConfig.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Saudumm/scriptable-CaschysBlog/e0bbcf82812cc00aeb1b2b1067ef9c4a41e9b82e/widgetConfig.jpeg
--------------------------------------------------------------------------------
/widgetExamples.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Saudumm/scriptable-CaschysBlog/e0bbcf82812cc00aeb1b2b1067ef9c4a41e9b82e/widgetExamples.jpeg
--------------------------------------------------------------------------------