Skip to content

Commit 268d5f0

Browse files
committed
feat: create editoria11y web component package
1 parent c663a36 commit 268d5f0

19 files changed

Lines changed: 6147 additions & 1 deletion
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@frameless/editoria11y": major
3+
---
4+
5+
Nieuwe tool toegevoegd die automatisch webpagina’s scant op toegankelijkheidsproblemen, met Nederlandse taalondersteuning en integratie in het CMS.

.eslintignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,7 @@ gql
2929
**/yarn-error.log
3030
**/.strapi-updater.json
3131
**/generated
32-
apps/overige-objecten-api/src/types
32+
packages/editoria11y/lib/**
33+
packages/editoria11y/loader/**
34+
packages/editoria11y/www/**
35+
apps/overige-objecten-api/src/types

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ module.exports = {
7171
'./packages/catalogi-data/tsconfig.json',
7272
'./packages/content-compliance-checker/tsconfig.json',
7373
'./packages/content-compliance-checker/tsconfig.server.json',
74+
'./packages/editoria11y/tsconfig.json',
7475
'./packages/provider-upload-vercel/tsconfig.json',
7576
'./packages/samenwerkende-catalogi/tsconfig.json',
7677
'./packages/strapi-plugin-env-label/tsconfig.json',

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,9 @@ dist/
135135
# overige-objecten-api
136136
############################
137137
apps/overige-objecten-api/src/types
138+
139+
############################
140+
# Editoria11y
141+
############################
142+
/packages/editoria11y/src/components.d.ts
143+
/packages/editoria11y/src/components/frameless-editoria11y/readme.md

.markdownlintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ ENVIRONMENT_VARIABLES.md
1111
./apps/strapi.frameless.io/docs/developers/ENVIRONMENT_VARIABLES.md
1212
# Ignore partials
1313
_*.md
14+
packages/editoria11y/src/components/frameless-editoria11y/readme.md

.prettierignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ yarn.lock
2020
generated
2121
gql
2222
apps/overige-objecten-api/src/types
23+
packages/editoria11y/lib/**
24+
packages/editoria11y/loader/**
25+
packages/editoria11y/www/**

packages/editoria11y/.gitignore

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
dist/
2+
www/
3+
loader/
4+
5+
*~
6+
*.sw[mnpcod]
7+
*.log
8+
*.tmp
9+
*.tmp.*
10+
log.txt
11+
*.sublime-project
12+
*.sublime-workspace
13+
.vscode/
14+
npm-debug.log*
15+
16+
.idea/
17+
.sass-cache/
18+
.tmp/
19+
.versions/
20+
coverage/
21+
.nyc_output/
22+
23+
# Stencil
24+
.stencil/
25+
src/components.d.ts
26+
src/components/*/readme.md
27+
28+
# Node
29+
node_modules/
30+
npm-debug.log*
31+
yarn-debug.log*
32+
yarn-error.log*

packages/editoria11y/README.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# @frameless/editoria11y
2+
3+
A StencilJS web component wrapper for [Editoria11y](https://github.com/itmaybejj/editoria11y) accessibility checker.
4+
5+
## Usage
6+
7+
### HTML
8+
9+
```html
10+
<!doctype html>
11+
<html dir="ltr" lang="en">
12+
<head>
13+
<meta charset="utf-8" />
14+
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0" />
15+
<title>Editoria11y Wrapper</title>
16+
17+
<script type="module" src="../dist/editoria11y/editoria11y.esm.js"></script>
18+
</head>
19+
<body>
20+
<frameless-editoria11y lang="nl" theme="darkTheme"></frameless-editoria11y>
21+
22+
<!-- Your content here -->
23+
<main>
24+
<h1>Your Page Content</h1>
25+
</main>
26+
</body>
27+
</html>
28+
```
29+
30+
### Next.js
31+
32+
1. **Install the packages:**
33+
34+
```bash
35+
yarn add @frameless/editoria11y @frameless/ui
36+
```
37+
38+
2. **Use the reusable component:**
39+
40+
```tsx
41+
import { Editoria11yWrapper } from "@frameless/ui";
42+
43+
const Layout = ({ children }) => {
44+
const { isEnabled } = draftMode(); // or your preview mode check
45+
46+
return (
47+
<html>
48+
<body>
49+
{isEnabled && <Editoria11yWrapper theme="darkTheme" language="nl" withNonce={true} />}
50+
{children}
51+
</body>
52+
</html>
53+
);
54+
};
55+
```
56+
57+
## Properties
58+
59+
### Editoria11yWrapper Component Props
60+
61+
| Prop | Type | Default | Description |
62+
| ----------------- | --------- | ------- | --------------------------------------- |
63+
| `language` | `string` | - | Language code (e.g., 'nl', 'en') |
64+
| `theme` | `string` | - | Theme name (e.g., 'darkTheme') |
65+
| `alertMode` | `string` | - | Alert display mode |
66+
| `checkRoots` | `string` | - | CSS selector for content roots to check |
67+
| `ignoreElements` | `string` | - | CSS selector for elements to ignore |
68+
| `allowHide` | `boolean` | - | Allow hiding alerts |
69+
| `allowOK` | `boolean` | - | Allow marking alerts as OK |
70+
| `inlineAlerts` | `boolean` | - | Show alerts inline |
71+
| `watchForChanges` | `boolean` | - | Watch for DOM changes |
72+
| `withNonce` | `boolean` | `false` | Enable CSP nonce support |
73+
74+
For detailed properties and configuration options, see [src/components/frameless-editoria11y/readme.md](src/components/frameless-editoria11y/readme.md)
75+
76+
## Implementation Steps
77+
78+
This package was created using the original Editoria11y source with automated modifications:
79+
80+
1. **Clone the original repository:**
81+
82+
```bash
83+
git clone https://github.com/itmaybejj/editoria11y.git
84+
cd editoria11y
85+
npm install
86+
```
87+
88+
2. **Add ESM build support:**
89+
90+
- Copy `frameless/packages/lib/build-esm-script.mjs` to `scripts/build-esm-script.mjs`
91+
- Add to package.json: `"build:esm": "node scripts/build-esm-script.mjs"`
92+
- The script automatically handles:
93+
- Dutch language support
94+
- CSP nonce support
95+
- Global scope exposure
96+
- Formatting fixes
97+
- **Original files stay untouched** - all modifications applied only to generated bundle
98+
99+
3. **Build and copy files:**
100+
101+
```bash
102+
npm run build:esm
103+
# Copy /dist/editoria11y.esm.js to /frameless/packages/editoria11y/lib
104+
```
105+
106+
4. **Build the wrapper component:**
107+
108+
```bash
109+
yarn build
110+
```
111+
112+
## Why This Approach?
113+
114+
The reason for using this approach was due to several issues:
115+
116+
1. The original editoria11y repo didn't update the version in package.json after each release, causing issues when installing from the repo since they don't publish to npm
117+
2. The issues that needed fixing in the original repo would require `patch-package` which is very hard to maintain
118+
119+
We can make it better by implementing these improvements directly to the original editoria11y repo, but this takes some time.
120+
121+
## Testing
122+
123+
To test the build you can check the `src/index.html` example
124+
125+
## Development
126+
127+
```bash
128+
# Build the component
129+
yarn build
130+
131+
# Start development server
132+
yarn start
133+
134+
# Run tests
135+
yarn test
136+
```
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import { promises as fs } from 'node:fs';
2+
import { fileURLToPath } from 'node:url';
3+
import { dirname, join } from 'node:path';
4+
5+
const __filename = fileURLToPath(import.meta.url);
6+
const __dirname = dirname(__filename);
7+
const distDir = join(__dirname, '../dist');
8+
9+
import * as acorn from "acorn";
10+
11+
/**
12+
* Fix common invalid syntax issues that creep into bundled JS.
13+
* - Converts stray HTML-style comments into valid JS comments
14+
* - Handles spacing and multiline safely
15+
* - Optionally decodes &lt; and &gt; entities
16+
*/
17+
const fixJavaScriptSyntax = (content, { decodeEntities = false } = {}) => {
18+
let fixed = content;
19+
20+
// 1. Fix malformed HTML comments like < !-- ... -->
21+
fixed = fixed.replace(/<\s*!--([\s\S]*?)-->/g, (_, inner) => {
22+
return `/* ${inner.trim()} */`;
23+
});
24+
25+
// 2. Convert proper <!-- ... --> comments
26+
// Use block comments instead of // to safely handle multi-line
27+
fixed = fixed.replace(/<!--([\s\S]*?)-->/g, (_, inner) => {
28+
return `/* ${inner.trim()} */`;
29+
});
30+
31+
// 3. Decode HTML entities only if requested
32+
if (decodeEntities) {
33+
fixed = fixed.replace(/&lt;/g, "<").replace(/&gt;/g, ">");
34+
}
35+
36+
// 4. Validate with Acorn (throws if still invalid)
37+
try {
38+
acorn.parse(fixed, { ecmaVersion: "latest", sourceType: "module" });
39+
} catch (err) {
40+
console.error("❌ Still invalid after fix:", err.message);
41+
throw err;
42+
}
43+
44+
return fixed;
45+
};
46+
47+
48+
const removeFile = async () => {
49+
try {
50+
const filePath = join(distDir, 'editoria11y.esm.js');
51+
await fs.unlink(filePath);
52+
console.log('🗑️ Removed old bundle:', filePath);
53+
} catch (err) {
54+
if (err.code === 'ENOENT') {
55+
console.log('ℹ️ File does not exist, nothing to remove.');
56+
} else {
57+
console.error('❌ Error removing file:', err);
58+
}
59+
}
60+
};
61+
62+
/**
63+
* Replace a pattern in the input string, or throw if not found.
64+
*/
65+
const requestReplace = (input, pattern, replacement, description = 'pattern') => {
66+
const regex = typeof pattern === 'string' ? new RegExp(pattern, 'g') : pattern;
67+
const result = input.replace(regex, replacement);
68+
69+
if (result === input) {
70+
throw new Error(`❌ Could not find ${description}`);
71+
}
72+
73+
return result;
74+
}
75+
// Add nonce modifications
76+
const addNonceSupport = (content) => {
77+
// Flexible patterns to handle spaces/newlines/minified code
78+
content = requestReplace(
79+
content,
80+
/customTests\s*:\s*0\s*,[\s\S]*?};/,
81+
`customTests: 0,
82+
83+
nonce: false,
84+
85+
};`,
86+
'customTests block'
87+
);
88+
89+
content = requestReplace(
90+
content,
91+
/Ed11y\.attachCSS\s*=\s*function\s*\(appendTo\)\s*{[\s\S]*?};/,
92+
`Ed11y.attachCSS = function(appendTo) {
93+
const bundle = cssBundle.cloneNode(true);
94+
if (Ed11y.options.nonce) {
95+
bundle.querySelectorAll('link').forEach(link => {
96+
link.setAttribute('nonce', Ed11y.options.nonce);
97+
});
98+
}
99+
appendTo.appendChild(bundle);
100+
};`,
101+
'attachCSS function'
102+
);
103+
104+
return content;
105+
}
106+
107+
const buildBundle = async (files) => {
108+
return files.reduce(async (bundlePromise, file) => {
109+
const bundle = await bundlePromise; // wait for previous iteration
110+
const filePath = join(__dirname, '../js', file);
111+
112+
try {
113+
await fs.access(filePath);
114+
let content = await fs.readFile(filePath, 'utf8');
115+
// Apply fixes to all files
116+
content = fixJavaScriptSyntax(content);
117+
if (file === 'ed11y.js') {
118+
content = addNonceSupport(content);
119+
}
120+
121+
return bundle + content + '\n';
122+
} catch (err) {
123+
if (err.code === 'ENOENT') {
124+
console.log(`⚠️ Skipping missing file: ${filePath}`);
125+
return bundle;
126+
} else {
127+
console.error(`❌ Error processing ${filePath}:`, err);
128+
return bundle;
129+
}
130+
}
131+
}, Promise.resolve('')); // start with an empty bundle
132+
};
133+
134+
const writeFiles = async (esmBundle) => {
135+
try {
136+
const esmPath = join(distDir, 'editoria11y.esm.js');
137+
await fs.writeFile(esmPath, esmBundle, 'utf8');
138+
console.log('✅ Wrote JS bundle:', esmPath);
139+
140+
const cssSrcPath = join(distDir, 'editoria11y.min.css');
141+
const cssDestPath = join(distDir, 'editoria11y.css');
142+
await fs.copyFile(cssSrcPath, cssDestPath);
143+
console.log('✅ Copied CSS to:', cssDestPath);
144+
} catch (err) {
145+
console.error('❌ Error writing files:', err);
146+
}
147+
};
148+
149+
const buildESM = async () => {
150+
await removeFile();
151+
152+
const files = [
153+
'ed11y-localization.js',
154+
'ed11y-test-embeds.js',
155+
'ed11y-test-headings.js',
156+
'ed11y-test-images.js',
157+
'ed11y-test-links.js',
158+
'ed11y-test-text.js',
159+
'ed11y.js',
160+
'ed11y-element-alt.js',
161+
'ed11y-element-panel.js',
162+
'ed11y-element-result.js',
163+
'ed11y-element-tip.js',
164+
];
165+
166+
const jsBundle = await buildBundle(files);
167+
168+
const esmBundle = `${jsBundle}
169+
170+
export default Ed11y;
171+
export { Ed11y };
172+
`;
173+
await writeFiles(esmBundle);
174+
175+
console.log('🎉 Build complete with nonce support and syntax fixes');
176+
console.log(' - Original files unchanged');
177+
console.log(' - Generated: dist/editoria11y.esm.js');
178+
console.log(' - Generated: dist/editoria11y.css');
179+
};
180+
181+
await buildESM();

0 commit comments

Comments
 (0)