Skip to content

Commit 6e35ec0

Browse files
authored
Merge pull request #2 from AegisJSProject/patch/initial-release
Initial release of @aegisjsproject/qr-encoder
2 parents 3271cc1 + edc80c8 commit 6e35ec0

File tree

12 files changed

+206
-157
lines changed

12 files changed

+206
-157
lines changed

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ importmap.yaml
1818
*.bak
1919
*.env
2020
!*.map
21+
qr.js

CHANGELOG.md

Lines changed: 1 addition & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -7,93 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
### Added
11-
- Add `@aegisjsproject/dev-server`
12-
13-
### Changed
14-
- Update CSP, template, etc
15-
16-
## [v1.1.3] - 2025-11-21
17-
18-
### Added
19-
- Add `npm start` script
20-
21-
### Changed
22-
- Update npm publishing
23-
24-
## [v1.1.2] - 2025-05-01
25-
26-
### Changed
27-
- Use `eslint` & `rollup` directly instead of by other packages
28-
- Update node version via `.npmrc`
29-
- Update Node CI workflow
30-
- Install & use `@shgysk8zer0/eslint-config`
31-
- Add support for `node --test`, including ignoring tests for publishing
32-
- Update ESLint & super-linter
33-
- Switch to more basic Rollup config
34-
- Update `exports` and `main` accordingly
35-
36-
### Fixed
37-
- Fix missed renaming in README
38-
39-
## Removed
40-
- Remove old ESLint config files
41-
42-
## [v1.1.1] - 2023-09-24
43-
44-
### Added
45-
- Add `unpkg` to `package.json`
46-
- Add badges in README
47-
48-
### Changed
49-
- Update `exports` to `package.json` to handle wider variety
50-
51-
### Fixed
52-
- Fix typo in `fix:js` script
53-
54-
### [v1.1.0] - 2023-07-03
55-
56-
### Changed
57-
- Update to node 20
58-
- Update npm publishing GH Action
59-
60-
## [v1.0.5] - 2023-07-02
61-
62-
### Added
63-
- Add `funding`
64-
65-
### Changed
66-
- Updated GitHub Actions workflows
67-
- Update versioning & lock-file scripts
68-
- Update `.npmignore` & `.gitignore`
69-
70-
## [v1.0.4] - 2023-06-08
71-
72-
### Added
73-
- Install `@shgysk8zer0/npm-utils`
74-
- Add `exports` to package config
75-
76-
### Removed
77-
- Uninstall `rollup`, `eslint`
78-
79-
### Changed
80-
- Use `getConfig()` from `@shgysk8zer0/js-utils/rollup` for rollup config
81-
82-
## [v1.0.3] - 2023-06-01
83-
84-
### Fixed
85-
- Revert to old Release Action, now with permissions & link to changelog
86-
87-
## [v1.0.2] - 2023-06-01
88-
89-
### Fixed
90-
- Fix `changelog-entry` to match `[$version]` instead of `$version`
91-
92-
## [v1.0.1] - 2023-05-31
93-
94-
### Fixed
95-
- Update GitHub Release workflow to use [Auto Release](https://github.com/marketplace/actions/auto-release)
96-
97-
## [v1.0.0] - 2023-05-31
10+
## [v1.0.0] - 2025-12-23
9811

9912
Initial Release

README.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
# npm-template
1+
# `@aegisjsproject/qr-encoder`
22

3-
A template repo for npm packages
3+
A QR encoder for node and browsers
44

5-
[![CodeQL](https://github.com/shgysk8zer0/npm-template/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/shgysk8zer0/npm-template/actions/workflows/codeql-analysis.yml)
6-
![Node CI](https://github.com/shgysk8zer0/npm-template/workflows/Node%20CI/badge.svg)
7-
![Lint Code Base](https://github.com/shgysk8zer0/npm-template/workflows/Lint%20Code%20Base/badge.svg)
5+
[![CodeQL](https://github.com/AegisJSProject/qr-encoder/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/AegisJSProject/qr-encoder/actions/workflows/codeql-analysis.yml)
6+
![Node CI](https://github.com/AegisJSProject/qr-encoder/workflows/Node%20CI/badge.svg)
7+
![Lint Code Base](https://github.com/AegisJSProject/qr-encoder/workflows/Lint%20Code%20Base/badge.svg)
88

9-
[![GitHub license](https://img.shields.io/github/license/shgysk8zer0/npm-template.svg)](https://github.com/shgysk8zer0/npm-template/blob/master/LICENSE)
10-
[![GitHub last commit](https://img.shields.io/github/last-commit/shgysk8zer0/npm-template.svg)](https://github.com/shgysk8zer0/npm-template/commits/master)
11-
[![GitHub release](https://img.shields.io/github/release/shgysk8zer0/npm-template?logo=github)](https://github.com/shgysk8zer0/npm-template/releases)
9+
[![GitHub license](https://img.shields.io/github/license/AegisJSProject/qr-encoder.svg)](https://github.com/AegisJSProject/qr-encoder/blob/master/LICENSE)
10+
[![GitHub last commit](https://img.shields.io/github/last-commit/AegisJSProject/qr-encoder.svg)](https://github.com/AegisJSProject/qr-encoder/commits/master)
11+
[![GitHub release](https://img.shields.io/github/release/AegisJSProject/qr-encoder?logo=github)](https://github.com/AegisJSProject/qr-encoder/releases)
1212
[![GitHub Sponsors](https://img.shields.io/github/sponsors/shgysk8zer0?logo=github)](https://github.com/sponsors/shgysk8zer0)
1313

14-
[![npm](https://img.shields.io/npm/v/@shgysk8zer0/npm-template)](https://www.npmjs.com/package/@shgysk8zer0/npm-template)
15-
![node-current](https://img.shields.io/node/v/@shgysk8zer0/npm-template)
16-
![npm bundle size gzipped](https://img.shields.io/bundlephobia/minzip/@shgysk8zer0/npm-template)
17-
[![npm](https://img.shields.io/npm/dw/@shgysk8zer0/npm-template?logo=npm)](https://www.npmjs.com/package/@shgysk8zer0/npm-template)
14+
[![npm](https://img.shields.io/npm/v/@aegisjsproject/qr-encoder)](https://www.npmjs.com/package/@aegisjsproject/qr-encoder)
15+
![node-current](https://img.shields.io/node/v/@aegisjsproject/qr-encoder)
16+
![npm bundle size gzipped](https://img.shields.io/bundlephobia/minzip/@aegisjsproject/qr-encoder)
17+
[![npm](https://img.shields.io/npm/dw/@aegisjsproject/qr-encoder?logo=npm)](https://www.npmjs.com/package/@aegisjsproject/qr-encoder)
1818

1919
[![GitHub followers](https://img.shields.io/github/followers/shgysk8zer0.svg?style=social)](https://github.com/shgysk8zer0)
20-
![GitHub forks](https://img.shields.io/github/forks/shgysk8zer0/npm-template.svg?style=social)
21-
![GitHub stars](https://img.shields.io/github/stars/shgysk8zer0/npm-template.svg?style=social)
20+
![GitHub forks](https://img.shields.io/github/forks/AegisJSProject/qr-encoder.svg?style=social)
21+
![GitHub stars](https://img.shields.io/github/stars/AegisJSProject/qr-encoder.svg?style=social)
2222
[![Twitter Follow](https://img.shields.io/twitter/follow/shgysk8zer0.svg?style=social)](https://twitter.com/shgysk8zer0)
2323

2424
[![Donate using Liberapay](https://img.shields.io/liberapay/receives/shgysk8zer0.svg?logo=liberapay)](https://liberapay.com/shgysk8zer0/donate "Donate using Liberapay")

consts.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

http.config.js

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import { imports } from '@shgysk8zer0/importmap';
2-
import { checkCacheItem, setCacheItem } from '@aegisjsproject/http-utils/cache.js';
3-
import { addTrustedTypePolicy, addScriptSrc, useDefaultCSP } from '@aegisjsproject/http-utils/csp.js';
2+
import { addScriptSrc, addTrustedTypePolicy, useDefaultCSP } from '@aegisjsproject/http-utils/csp.js';
43

54
addScriptSrc(
6-
'https://unpkg.com/@aegisjsproject/',
7-
'https://unpkg.com/@shgysk8zer0/',
5+
imports['@shgysk8zer0/polyfills'],
6+
imports.qr,
87
);
9-
108
addTrustedTypePolicy('aegis-sanitizer#html');
119

1210
export default {
@@ -15,19 +13,5 @@ export default {
1513
'/favicon.svg': '@aegisjsproject/dev-server/favicon',
1614
},
1715
open: true,
18-
requestPreprocessors: [
19-
'@aegisjsproject/http-utils/request-id.js',
20-
checkCacheItem,
21-
],
22-
responsePostprocessors: [
23-
'@aegisjsproject/http-utils/compression.js',
24-
'@aegisjsproject/http-utils/cors.js',
25-
useDefaultCSP(),
26-
(response, { request }) => {
27-
if (request.destination === 'document') {
28-
response.headers.append('Link', `<${imports['@shgysk8zer0/polyfills']}>; rel="preload"; as="script"; fetchpriority="high"; crossorigin="anonymous"; referrerpolicy="no-referrer"`);
29-
}
30-
},
31-
setCacheItem,
32-
],
16+
responsePostprocessors: [useDefaultCSP()],
3317
};

index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<link rel="icon" href="/favicon.svg" type="image/svg+xml" sizes="any" />
99
<script type="importmap" integrity="{{ INTEGRITY }}">{{ IMPORTMAP }}</script>
1010
<script src="{{ POLYFILLS }}" referrerpolicy="no-referrer" crossorigin="anonymous" defer=""></script>
11+
<script src="/qr.js" type="module"></script>
1112
</head>
1213
<body>
1314
<header id="header">

index.js

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,107 @@
1-
import { MESSAGE } from 'npm-template/consts.js';
2-
console.log(import.meta);
3-
console.info(MESSAGE);
1+
import encodeQR from 'qr';
2+
const namespace = 'http://www.w3.org/2000/svg';
3+
4+
const SANITIZER = {
5+
allowElements: [
6+
{ name: 'svg', namespace },
7+
{ name: 'path', namespace },
8+
{ name: 'rect', namespace }],
9+
allowAttributes: {
10+
'viewBox': ['svg'],
11+
'xmlns': ['svg'],
12+
'd': ['path'],
13+
'fill': ['svg', 'rect'],
14+
'x': ['rect'],
15+
'y': ['rect'],
16+
'width': ['*'],
17+
'height': ['*'],
18+
}
19+
};
20+
const ECC = 'medium';
21+
const BORDER = 4;
22+
const SCALE = 4;
23+
const GIF = 'gif';
24+
const SVG = 'svg';
25+
const FILL = '#000';
26+
const BACKGROUND = '#fff';
27+
const SIZE = 480;
28+
29+
const attr = str => {
30+
return String(str ?? '').replace(/[&<>"']/g, (char) => ({
31+
'&': '&amp;',
32+
'<': '&lt;',
33+
'>': '&gt;',
34+
'"': '&quot;',
35+
'\'': '&#39;'
36+
}[char]));
37+
};
38+
39+
export function createGIF(input, {
40+
ecc = ECC,
41+
border = BORDER,
42+
scale = SCALE,
43+
} = {}) {
44+
return encodeQR(input, GIF, { ecc, border, scale });
45+
}
46+
47+
export const createGIFBlob = (input, {
48+
ecc = ECC,
49+
border = BORDER,
50+
scale = SCALE,
51+
} = {}) => new Blob([createGIF(input, { ecc, border, scale })], { type: 'image/gif' });
52+
53+
export const createGIFFile = (input, name = 'qr.gif', {
54+
ecc = ECC,
55+
border = BORDER,
56+
scale = SCALE,
57+
} = {}) => new File([createGIF(input, { ecc, border, scale })], name, { type: 'image/gif' });
58+
59+
export function createSVGString(input, {
60+
ecc = ECC,
61+
border = BORDER,
62+
scale = SCALE,
63+
size = SIZE,
64+
fill = FILL,
65+
background = BACKGROUND
66+
} = {}) {
67+
const s = attr(size);
68+
69+
return encodeQR(input, SVG, { ecc, border, scale })
70+
.replace('<svg ', `<svg fill="${attr(fill)}" height="${s}" width="${s}" `)
71+
.replace(/(<svg[^>]*>)/i, `$1\n\t<rect x="0" y="0" fill="${attr(background)}" height="100%" width="100%" />\n`);
72+
}
73+
74+
export const createSVGBlob = (input, {
75+
ecc = ECC,
76+
border = BORDER,
77+
scale = SCALE,
78+
fill = FILL,
79+
background = BACKGROUND,
80+
size = SIZE,
81+
} = {}) => new Blob([createSVGString(input, { ecc, border, scale, fill, background, size })], { type: 'image/svg+xml' });
82+
83+
export const createSVGFile = (input, name = 'qr.svg', {
84+
ecc = ECC,
85+
border = BORDER,
86+
scale = SCALE,
87+
fill = FILL,
88+
background = BACKGROUND,
89+
size = SIZE,
90+
} = {}) => new File([createSVGString(input, { ecc, border, scale, fill, background, size })], name, { type: 'image/svg+xml' });
91+
92+
export function createSVGElement(input, {
93+
ecc = ECC,
94+
border = BORDER,
95+
scale = SCALE,
96+
fill = FILL,
97+
background = BACKGROUND,
98+
size = SIZE,
99+
} = {}) {
100+
// eslint-disable-next-line no-undef
101+
const el = document.createElement('div');
102+
el.setHTML(
103+
createSVGString(input, { ecc, border, scale, fill, background, size }),
104+
{ sanitizer: SANITIZER }
105+
);
106+
return el.firstElementChild;
107+
}

index.test.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,34 @@
11
import { test, describe } from 'node:test';
22
import assert from 'node:assert';
3-
import { MESSAGE } from './consts.js';
3+
import { createSVGBlob, createSVGFile, createGIFFile, createGIFBlob } from './index.js';
44
const signal = AbortSignal.timeout(300);
5+
const input = 'https://github.com/AegisJSProject/qr-encoder';
56

6-
describe('An example test', () => {
7-
test('Message is a string', { signal }, () => assert.equal(typeof MESSAGE, 'string', 'Message should be a string.'));
7+
describe('Test generating QR codes', () => {
8+
test('Test generating SVG Blobs', { signal }, () => {
9+
const result = createSVGBlob(input);
10+
assert.ok(result instanceof Blob, 'Should output a `Blob`.');
11+
assert.equal(result?.type, 'image/svg+xml', 'Should have an SVG type.');
12+
assert.notEqual(result?.size, 0, 'Should not be empty.');
13+
});
14+
test('Test generating SVG Files', { signal }, () => {
15+
const result = createSVGFile(input);
16+
assert.ok(result instanceof File, 'Should output a `Blob`.');
17+
assert.equal(result?.type, 'image/svg+xml', 'Should have an SVG type.');
18+
assert.notEqual(result?.size, 0, 'Should not be empty.');
19+
assert.ok(result?.name?.endsWith('.svg'), 'SVG files should have the `.svg` extension.');
20+
});
21+
test('Test generating GIF Blobs', { signal }, () => {
22+
const result = createGIFBlob(input);
23+
assert.ok(result instanceof Blob, 'Should output a `Blob`.');
24+
assert.equal(result?.type, 'image/gif', 'Should have an SVG type.');
25+
assert.notEqual(result?.size, 0, 'Should not be empty.');
26+
});
27+
test('Test generating GIF Files', { signal }, () => {
28+
const result = createGIFFile(input);
29+
assert.ok(result instanceof File, 'Should output a `Blob`.');
30+
assert.equal(result?.type, 'image/gif', 'Should have an GIF type.');
31+
assert.notEqual(result?.size, 0, 'Should not be empty.');
32+
assert.ok(result?.name?.endsWith('.gif'), 'SVG files should have the `.gif` extension.');
33+
});
834
});

0 commit comments

Comments
 (0)