Skip to content

Commit

Permalink
Merge pull request #48 from shgysk8zer0/patch/misc
Browse files Browse the repository at this point in the history
patch/misc
  • Loading branch information
shgysk8zer0 authored Nov 17, 2023
2 parents 034f34b + 825c324 commit 0be1670
Show file tree
Hide file tree
Showing 19 changed files with 285 additions and 18 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [v0.2.6] - 2023-11-17

### Added
- Add `qr.js` module using `https://api.qrserver.com/`
- Add `iCal.js` module to create iCalendar files & QR codes
- Add `timeZoneInfo.js` module for getting time zone info/offsets

### Changed
- Update to use `@shgysk8zer0/consts`

### Deprecated
- Mark own version of constants (`namespaces.js`, `states.js`, & `types.js`) as deprecated

## [v0.2.5] - 2023-10-31

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion custom-elements.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @copyright 2023 Chris Zuber <[email protected]>
*/
import { HTML } from './types.js';
import { HTML } from '@shgysk8zer0/consts/mimes.js';

export const supported = globalThis.customElements instanceof Object;

Expand Down
2 changes: 1 addition & 1 deletion disqus.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import { createScript } from './elements.js';
import { getDeferred } from './promises.js';
import { JS } from './types.js';
import { JS } from '@shgysk8zer0/consts/mimes.js';
import { getDisqusPolicy } from './trust-policies.js';

const policyName = 'disqus#script-url';
Expand Down
2 changes: 1 addition & 1 deletion dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { signalAborted } from './abort.js';
import { addListener, listen } from './events.js';
import { getDeferred, isAsync } from './promises.js';
import { isHTML, isTrustPolicy } from './trust.js';
import { HTML } from './types.js';
import { HTML } from '@shgysk8zer0/consts/mimes.js';
import { errorToEvent, callOnce, isIterable } from './utility.js';
import { data as setData, css as setCss, attr as setAttr, aria as setAria } from './attrs.js';

Expand Down
37 changes: 35 additions & 2 deletions elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import { listen } from './events.js';
import { getDeferred } from './promises.js';
import { REFERRER_POLICY } from './defaults.js';
import { isObject, isNullish, isIterable } from './utility.js';
import { JS } from './types.js';
import { JS } from '@shgysk8zer0/consts/mimes.js';
import { isScriptURL, isHTML, setProp } from './trust.js';
import { getJSONScriptPolicy } from './trust-policies.js';
import { resolveModule, isBare } from './module.js';
import { STATES } from './states.js';
import { STATES } from '@shgysk8zer0/consts/states.js';
import { TIMEZONES } from '@shgysk8zer0/consts/timezones.js';

export function copyAs(target, tag, {
includeAttributes = true,
Expand Down Expand Up @@ -833,6 +834,38 @@ export function createStateSelect(name, {
});
}

export function createTimezoneSelect(name, {
required = false,
disabled = false,
multiple = false,
selected,
id,
classList,
dataset,
styles,
slot,
part,
animation,
events: { capture, passive, once, signal, ...events } = {},
...attrs
} = {}) {
const timezones = Object.groupBy(TIMEZONES, tz => tz === 'UTC' ? 'UTC' : tz.substring(0, tz.indexOf('/')));

const select = createSelect(name, Object.entries(timezones).map(([label, options]) => ({ label, options })), {
required, disabled, multiple, id, classList, dataset, styles,
slot, part, animation, events: { capture, passive, once, signal, ...events },
...attrs,
});

if (typeof selected === 'string' && TIMEZONES.includes(selected)) {
select.value = selected;
}

select.prepend(createOption({ label: 'Select timezone', value: '' }));

return select;
}

export async function showDialog({ text, html, children = [], classList = [], animation, signal, ...rest }) {
const { resolve, reject, promise } = getDeferred();

Expand Down
2 changes: 1 addition & 1 deletion http.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { signalAborted } from './abort.js';
import { setURLParams, setUTMParams, isObject, isNullish, callOnce } from './utility.js';
import { isTrustPolicy } from './trust.js';
import { HTTPException } from './HTTPException.js';
import * as TYPES from './types.js';
import * as TYPES from '@shgysk8zer0/consts/mimes.js';

function filename(src) {
if (typeof src === 'string') {
Expand Down
142 changes: 142 additions & 0 deletions iCal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { ICAL as ICAL_MIME } from '@shgysk8zer0/consts/mimes.js';
import { ICAL as ICAL_EXT } from '@shgysk8zer0/consts/exts.js';
import { createQRCode } from './qr.js';

const CRLF = '\r\n';
const BEGIN_EVENT = 'BEGIN:VEVENT';
const END_EVENT = 'END:VEVENT';
const ESCAPE_CHARS = {
'\\': '\\\\',
'\n': '\\n',
'\r': '\\r',
';': '\\;',
'"': '\\"',
',':'\\,',
};

const escape = str => str.replaceAll(new RegExp(`[${Object.keys(ESCAPE_CHARS).join('')}]`, 'g'), char => ESCAPE_CHARS[char]);

function formatUTCDate(date) {
return [
date.getUTCFullYear().toString().padStart(4, '0'),
(date.getUTCMonth() + 1).toString().padStart(2, '0'),
date.getUTCDate().toString().padStart(2, '0'),
'T',
date.getUTCHours().toString().padStart(2, '0'),
date.getUTCMinutes().toString().padStart(2, '0'),
date.getUTCSeconds().toString().padStart(2, '0'),
'Z',
].join('');
}

function formatDate(date) {
return [
date.getFullYear().toString().padStart(4, '0'),
(date.getMonth() + 1).toString().padStart(2, '0'),
date.getDate().toString().padStart(2, '0'),
'T',
date.getHours().toString().padStart(2, '0'),
date.getMinutes().toString().padStart(2, '0'),
date.getSeconds().toString().padStart(2, '0'),
].join('');
}

export function createICalEvent({
name,
startDate,
endDate,
description,
method = 'REQUEST',
location,
url,
modified = new Date(),
uid = crypto.randomUUID(),
}) {
if (typeof name !== 'string' || name.length === 0) {
throw new TypeError('Event name must be a non-empty string.');
} else if (typeof startDate === 'string' || typeof startDate === 'number') {
return createICalEvent({ name, startDate: new Date(startDate), endDate, description, location, modified, uid });
} else if (! (startDate instanceof Date)) {
throw new TypeError('startDate must be a Date object');
} else if (typeof endDate === 'string' || typeof endDate === 'number') {
return createICalEvent({ name, startDate, endDate: new Date(endDate), description, location, modified, uid });
} else if (typeof modified === 'string' || typeof modified === 'number') {
return createICalEvent({ name, startDate, endDate, description, location, modified: new Date(modified), uid });
} else if (! (modified instanceof Date)){
throw new TypeError('Modified must be a date.');
} else {
const lines = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
`METHOD:${method}`,
'PRODID:-//github.com/shgysk8zer0/kazoo v1.0//EN',
BEGIN_EVENT,
`UID:${escape(uid)}`,
`SUMMARY:${escape(name.trim())}`,
`LOCATION:${escape(location.trim())}`,
`DTSTAMP:${formatUTCDate(modified)}`,
`DTSTART:${formatUTCDate(startDate)}`,
];

if (endDate instanceof Date) {
lines.push(`DTEND:${formatUTCDate(endDate)}`);
}

if (typeof description === 'string' && description.length !== 0) {
lines.push(`DESCRIPTION:${escape(description.trim())}`);
}

if ((typeof url === 'string' && url.length !== 0) || url instanceof URL) {
lines.push(`URL:${escape(url)}`);
}

lines.push(END_EVENT, 'END:VCALENDAR');

return lines.join(CRLF);
}
}

export function createICalEventQR({
name,
startDate,
endDate,
description,
location,
size,
margin,
format,
color,
bgColor,
ecc,
}) {
const str = createICalEvent({ name, startDate, endDate, description, location });

// QR Events only get the event info, not whole calendar
const data = str.substring(str.indexOf(BEGIN_EVENT), str.indexOf(END_EVENT) + END_EVENT.length)
// Fix Google Calendar not respecting UTC via "Z"
.replace(/^DTSTART:\d+T\d+Z$/m, 'DTSTART:' + formatDate(new Date(startDate)))
.replace(/^DTEND:\d+T\d+Z$/m, 'DTEND:' + formatDate(new Date(endDate)))
// Fix escaping backslashes showing in locations/descriptions
.replaceAll('\\,', ',');

return createQRCode(data, { size, margin, format, color, bgColor, ecc });
}

export function createICalEventFile({
name,
startDate,
endDate,
description,
method = 'REQUEST',
location,
url,
modified = new Date(),
uid = crypto.randomUUID(),
}) {
if (typeof startDate === 'string' || typeof startDate === 'number') {
return createICalEventFile({ name, startDate: new Date(startDate), endDate, description, method, location, url, modified, uid });
} else {
const iCal = createICalEvent({ name, startDate, endDate, description, method, location, url, modified, uid });
return new File([iCal], `${startDate.toISOString()} ${name.replaceAll(/[^A-Za-z0-9]+/g, '-')}${ICAL_EXT}`, { type: ICAL_MIME });
}
}
2 changes: 1 addition & 1 deletion img-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import { getDeferred } from './promises.js';
import { createElement } from './elements.js';
import { JPEG, PNG, GIF, WEBP, SVG } from './types.js';
import { JPEG, PNG, GIF, WEBP, SVG } from '@shgysk8zer0/consts/mimes.js';
import { getType } from './utility.js';

export const EXTENSIONS = {
Expand Down
2 changes: 1 addition & 1 deletion loader.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @copyright 2023 Chris Zuber <[email protected]>
*/
import { JS } from './types.js';
import { JS } from '@shgysk8zer0/consts/mimes.js';
import { createScript, createImage, createLink } from './elements.js';
import { getDeferred } from './promises.js';
import { getHTML } from './http.js';
Expand Down
2 changes: 1 addition & 1 deletion markdown.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { use, parse } from 'marked';
import { markedHighlight } from 'marked-highlight';
import hljs from 'highlight.js';
import { MARKDOWN, TEXT } from './types.js';
import { MARKDOWN, TEXT } from '@shgysk8zer0/consts/mimes.js';

export const STYLESHEETS = {
github: {
Expand Down
1 change: 1 addition & 0 deletions namespaces.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* @copyright 2023 Chris Zuber <[email protected]>
*/
console.warn('namespaces.js module is deprecated. Please use `@shgysk8zer0/consts/namespaces.js` instead.');
export const HTML = 'http://www.w3.org/1999/xhtml';
export const SVG = 'http://www.w3.org/2000/svg';
export const XLINK = 'http://www.w3.org/1999/xlink';
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shgysk8zer0/kazoo",
"version": "0.2.5",
"version": "0.2.6",
"private": false,
"type": "module",
"description": "A JavaScript monorepo for all the things!",
Expand Down
62 changes: 62 additions & 0 deletions qr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { createImage } from './elements.js';
import { setUTMParams } from './utility.js';
import { clamp } from './math.js';

export const QRSERVER = 'https://api.qrserver.com/';

export function createQRCode(data, {
size,
margin,
format,
color,
bgColor,
ecc,
...rest
} = {}) {
const url = new URL('/v1/create-qr-code/', QRSERVER);
url.searchParams.set('data', data);

if (typeof size === 'number' && ! Number.isNaN(size)) {
url.searchParams.set('size', `${clamp(10, size, 1000)}x${clamp(10, size, 1000)}`);
}

if (typeof margin === 'number' && ! Number.isNaN(margin)) {
url.searchParams.set('margin', clamp(0, margin, 50));
}

if (typeof color === 'string') {
url.searchParams.set('color', color.replace('#', ''));
}

if (typeof bgColor === 'string') {
url.searchParams.set('bgcolor', bgColor.replace('#', ''));
}

if (typeof format === 'string') {
url.searchParams.set('format', format.toLowerCase());
}

if (typeof ecc === 'string') {
url.searchParams.set('ecc', ecc);
}

return createImage(url, {
width: size || 250,
height: size || 250,
crossOrigin: 'anonymous',
referrerPolicy: 'no-referrer',
...rest
});
}

export function createURLQRCode(url, {
source,
medium = 'qr',
content,
campaign,
term,
...rest
} = {}) {
const utmURL = setUTMParams(url, { source, medium, content, campaign, term });
return createQRCode(utmURL, rest);
}
2 changes: 2 additions & 0 deletions states.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/**
* @copyright 2023 Chris Zuber <[email protected]>
*/
console.warn('state.js module is deprecated. Please use `@shgysk8zer0/consts/states.js` instead.');

export const STATES = [{
'abbr': 'AL',
'name': 'Alabama',
Expand Down
4 changes: 2 additions & 2 deletions svg.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**
* @copyright 2022-2023 Chris Zuber <[email protected]>
*/
import { SVG, XLINK } from './namespaces.js';
import { SVG, XLINK } from '@shgysk8zer0/consts/namespaces.js';
import { isObject } from './utility.js';
import { css, data, attr } from './attrs.js';
import { SVG as TYPE } from './types.js';
import { SVG as TYPE } from '@shgysk8zer0/consts/mimes.js';

export { rotate, scale, translate } from './animate.js';

Expand Down
Loading

0 comments on commit 0be1670

Please sign in to comment.