gbu-accessibility-package 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/QUICK_START.md +23 -23
- package/README.md +201 -214
- package/cli.js +44 -2
- package/demo/duplicate-roles.html +45 -0
- package/demo/duplicate-roles.html.backup +45 -0
- package/demo/sample.html +13 -13
- package/demo/sample.html.backup +13 -13
- package/lib/fixer.js +200 -24
- package/package.json +3 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 GBU Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/QUICK_START.md
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
# 🚀 Quick Start Guide
|
|
2
2
|
|
|
3
|
-
Hướng dẫn nhanh để sử dụng Accessibility
|
|
3
|
+
Hướng dẫn nhanh để sử dụng GBU Accessibility Package trong 5 phút.
|
|
4
4
|
|
|
5
5
|
## ⚡ Cài đặt nhanh
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
# 1.
|
|
9
|
-
|
|
8
|
+
# 1. Cài đặt global (khuyến nghị)
|
|
9
|
+
npm install -g gbu-accessibility-package
|
|
10
10
|
|
|
11
|
-
# 2.
|
|
12
|
-
|
|
13
|
-
npm install
|
|
11
|
+
# 2. Hoặc cài đặt local
|
|
12
|
+
npm install gbu-accessibility-package
|
|
14
13
|
|
|
15
14
|
# 3. Chạy ngay!
|
|
16
|
-
|
|
15
|
+
gbu-a11y
|
|
17
16
|
```
|
|
18
17
|
|
|
19
18
|
## 🎯 Sử dụng cơ bản
|
|
@@ -22,16 +21,19 @@ node cli.js
|
|
|
22
21
|
|
|
23
22
|
```bash
|
|
24
23
|
# Fix toàn bộ dự án (current directory)
|
|
25
|
-
|
|
24
|
+
gbu-a11y
|
|
26
25
|
|
|
27
26
|
# Fix thư mục cụ thể
|
|
28
|
-
|
|
27
|
+
gbu-a11y ./src
|
|
29
28
|
|
|
30
29
|
# Preview trước khi fix
|
|
31
|
-
|
|
30
|
+
gbu-a11y --dry-run
|
|
32
31
|
|
|
33
32
|
# Fix với ngôn ngữ khác
|
|
34
|
-
|
|
33
|
+
gbu-a11y -l en ./dist
|
|
34
|
+
|
|
35
|
+
# Fix comprehensive (khuyến nghị)
|
|
36
|
+
gbu-a11y --comprehensive
|
|
35
37
|
```
|
|
36
38
|
|
|
37
39
|
### Cách 2: Node.js Script
|
|
@@ -39,7 +41,7 @@ node accessibility-package/cli.js -l en ./dist
|
|
|
39
41
|
Tạo file `fix.js`:
|
|
40
42
|
|
|
41
43
|
```javascript
|
|
42
|
-
const AccessibilityFixer = require('
|
|
44
|
+
const AccessibilityFixer = require('gbu-accessibility-package');
|
|
43
45
|
|
|
44
46
|
const fixer = new AccessibilityFixer({
|
|
45
47
|
language: 'ja', // Thay đổi theo dự án
|
|
@@ -48,9 +50,8 @@ const fixer = new AccessibilityFixer({
|
|
|
48
50
|
});
|
|
49
51
|
|
|
50
52
|
async function fix() {
|
|
51
|
-
|
|
52
|
-
await fixer.
|
|
53
|
-
await fixer.fixRoleAttributes('.');
|
|
53
|
+
// Fix tất cả issues
|
|
54
|
+
await fixer.fixAllAccessibilityIssues('.');
|
|
54
55
|
console.log('✅ Done!');
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -61,11 +62,10 @@ Chạy: `node fix.js`
|
|
|
61
62
|
|
|
62
63
|
## 📋 Checklist nhanh
|
|
63
64
|
|
|
64
|
-
- [ ]
|
|
65
|
-
- [ ] `npm install` trong thư mục package
|
|
65
|
+
- [ ] `npm install -g gbu-accessibility-package`
|
|
66
66
|
- [ ] Backup code (git commit)
|
|
67
|
-
- [ ] Chạy
|
|
68
|
-
- [ ] Chạy
|
|
67
|
+
- [ ] Chạy `gbu-a11y --dry-run` để preview
|
|
68
|
+
- [ ] Chạy `gbu-a11y --comprehensive` để fix
|
|
69
69
|
- [ ] Kiểm tra kết quả
|
|
70
70
|
- [ ] Commit changes
|
|
71
71
|
|
|
@@ -103,19 +103,19 @@ language: 'en' // 'ja', 'vi', 'zh', etc.
|
|
|
103
103
|
|
|
104
104
|
### Không tạo backup
|
|
105
105
|
```bash
|
|
106
|
-
|
|
106
|
+
gbu-a11y --no-backup
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
### Chỉ preview
|
|
110
110
|
```bash
|
|
111
|
-
|
|
111
|
+
gbu-a11y --dry-run
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
## ❓ Troubleshooting
|
|
115
115
|
|
|
116
116
|
**Lỗi "Cannot find module"**
|
|
117
117
|
```bash
|
|
118
|
-
|
|
118
|
+
npm install -g gbu-accessibility-package
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
**Duplicate attributes**
|
|
@@ -130,7 +130,7 @@ cd accessibility-package && npm install
|
|
|
130
130
|
|
|
131
131
|
1. Đọc [README.md](./README.md) đầy đủ
|
|
132
132
|
2. Xem [example.js](./example.js)
|
|
133
|
-
3. Chạy `
|
|
133
|
+
3. Chạy `gbu-a11y --help`
|
|
134
134
|
|
|
135
135
|
---
|
|
136
136
|
|
package/README.md
CHANGED
|
@@ -1,305 +1,292 @@
|
|
|
1
1
|
# GBU Accessibility Package
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
3
|
+
🚀 **Automated accessibility fixes for HTML files** - Smart, context-aware accessibility improvements with zero configuration.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/gbu-accessibility-package)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://nodejs.org/)
|
|
7
8
|
|
|
8
|
-
##
|
|
9
|
+
## ✨ Features
|
|
9
10
|
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
- ✅ **Backup tự động**: Tạo backup files trước khi sửa
|
|
19
|
-
- ✅ **Dry run mode**: Xem preview trước khi apply changes
|
|
11
|
+
- 🖼️ **Smart Alt Text Generation** - Context-aware alt attributes for images
|
|
12
|
+
- 🌐 **HTML Lang Attributes** - Automatic language attribute fixes
|
|
13
|
+
- 🎭 **Role Attributes** - WCAG-compliant role attribute management
|
|
14
|
+
- 🧹 **Duplicate Cleanup** - Remove duplicate role attributes
|
|
15
|
+
- 📁 **Batch Processing** - Process entire directories recursively
|
|
16
|
+
- 💾 **Automatic Backups** - Safe modifications with backup files
|
|
17
|
+
- 🔍 **Dry Run Mode** - Preview changes before applying
|
|
18
|
+
- 📊 **Detailed Reports** - Comprehensive fix summaries
|
|
20
19
|
|
|
21
|
-
##
|
|
20
|
+
## 🚀 Quick Start
|
|
22
21
|
|
|
23
|
-
###
|
|
22
|
+
### Installation
|
|
24
23
|
|
|
25
24
|
```bash
|
|
26
|
-
#
|
|
25
|
+
# Global installation (recommended)
|
|
27
26
|
npm install -g gbu-accessibility-package
|
|
28
27
|
|
|
29
|
-
#
|
|
30
|
-
npm install gbu-accessibility-package
|
|
28
|
+
# Local installation
|
|
29
|
+
npm install gbu-accessibility-package
|
|
31
30
|
```
|
|
32
31
|
|
|
33
|
-
###
|
|
32
|
+
### Basic Usage
|
|
34
33
|
|
|
35
34
|
```bash
|
|
36
|
-
#
|
|
37
|
-
|
|
35
|
+
# Fix current directory
|
|
36
|
+
gbu-a11y
|
|
38
37
|
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
```
|
|
38
|
+
# Preview changes (dry run)
|
|
39
|
+
gbu-a11y --dry-run
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
# Fix specific directory
|
|
42
|
+
gbu-a11y ./src
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
# Sau khi cài đặt global
|
|
49
|
-
gbu-a11y [options] [directory]
|
|
44
|
+
# Comprehensive fixes (recommended)
|
|
45
|
+
gbu-a11y --comprehensive
|
|
50
46
|
|
|
51
|
-
#
|
|
52
|
-
|
|
47
|
+
# Fix specific file
|
|
48
|
+
gbu-a11y index.html
|
|
53
49
|
```
|
|
54
50
|
|
|
55
|
-
|
|
51
|
+
## 📖 Detailed Usage
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
# Chạy từ node_modules
|
|
59
|
-
npx gbu-a11y [options] [directory]
|
|
53
|
+
### Command Line Options
|
|
60
54
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
55
|
+
```bash
|
|
56
|
+
gbu-a11y [options] [directory/file]
|
|
57
|
+
|
|
58
|
+
Options:
|
|
59
|
+
-d, --directory <path> Target directory (default: current directory)
|
|
60
|
+
-l, --language <lang> Language for lang attribute (default: ja)
|
|
61
|
+
--no-backup Don't create backup files
|
|
62
|
+
--dry-run Preview changes without applying
|
|
63
|
+
--cleanup-only Only cleanup duplicate role attributes
|
|
64
|
+
--comprehensive, --all Run all fixes including cleanup (recommended)
|
|
65
|
+
-h, --help Show help message
|
|
68
66
|
```
|
|
69
67
|
|
|
70
|
-
###
|
|
68
|
+
### Examples
|
|
71
69
|
|
|
72
|
-
```
|
|
73
|
-
|
|
70
|
+
```bash
|
|
71
|
+
# Basic fixes for current directory
|
|
72
|
+
gbu-a11y
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
language: 'ja', // Ngôn ngữ cho lang attribute
|
|
78
|
-
backupFiles: true, // Tạo backup files
|
|
79
|
-
dryRun: false // false = apply changes, true = preview only
|
|
80
|
-
});
|
|
74
|
+
# Preview all changes
|
|
75
|
+
gbu-a11y --dry-run --comprehensive
|
|
81
76
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// Fix lang attributes
|
|
85
|
-
const langResults = await fixer.fixHtmlLang('.');
|
|
86
|
-
|
|
87
|
-
// Fix alt attributes
|
|
88
|
-
const altResults = await fixer.fixEmptyAltAttributes('.');
|
|
89
|
-
|
|
90
|
-
// Fix role attributes
|
|
91
|
-
const roleResults = await fixer.fixRoleAttributes('.');
|
|
92
|
-
|
|
93
|
-
console.log('Hoàn thành!');
|
|
94
|
-
}
|
|
77
|
+
# Fix with English language
|
|
78
|
+
gbu-a11y -l en ./public
|
|
95
79
|
|
|
96
|
-
|
|
97
|
-
|
|
80
|
+
# Only cleanup duplicates
|
|
81
|
+
gbu-a11y --cleanup-only
|
|
98
82
|
|
|
99
|
-
|
|
83
|
+
# Fix without creating backups
|
|
84
|
+
gbu-a11y --no-backup ./dist
|
|
85
|
+
```
|
|
100
86
|
|
|
101
|
-
|
|
87
|
+
## 🔧 Programmatic Usage
|
|
102
88
|
|
|
103
89
|
```javascript
|
|
104
90
|
const AccessibilityFixer = require('gbu-accessibility-package');
|
|
105
91
|
|
|
106
92
|
const fixer = new AccessibilityFixer({
|
|
107
|
-
language: '
|
|
93
|
+
language: 'en',
|
|
108
94
|
backupFiles: true,
|
|
109
95
|
dryRun: false
|
|
110
96
|
});
|
|
111
97
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
console.log('✅ Hoàn thành!');
|
|
98
|
+
// Fix all accessibility issues
|
|
99
|
+
async function fixAccessibility() {
|
|
100
|
+
try {
|
|
101
|
+
const results = await fixer.fixAllAccessibilityIssues('./src');
|
|
102
|
+
console.log('Fixed files:', results);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('Error:', error);
|
|
105
|
+
}
|
|
121
106
|
}
|
|
122
107
|
|
|
123
|
-
|
|
108
|
+
fixAccessibility();
|
|
124
109
|
```
|
|
125
110
|
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
node fix-accessibility.js
|
|
129
|
-
```
|
|
111
|
+
## 🎯 What Gets Fixed
|
|
130
112
|
|
|
131
|
-
|
|
113
|
+
### 1. Alt Attributes
|
|
114
|
+
- **Missing alt attributes** → Adds contextual alt text
|
|
115
|
+
- **Empty alt attributes** → Generates meaningful descriptions
|
|
116
|
+
- **Context-aware generation** → Uses surrounding text, headings, captions
|
|
132
117
|
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
dryRun: false // true = chỉ preview, false = apply changes
|
|
138
|
-
};
|
|
118
|
+
```html
|
|
119
|
+
<!-- Before -->
|
|
120
|
+
<img src="logo.png">
|
|
121
|
+
<img src="chart.jpg" alt="">
|
|
139
122
|
|
|
140
|
-
|
|
123
|
+
<!-- After -->
|
|
124
|
+
<img src="logo.png" alt="ロゴ">
|
|
125
|
+
<img src="chart.jpg" alt="グラフ">
|
|
141
126
|
```
|
|
142
127
|
|
|
143
|
-
|
|
128
|
+
### 2. HTML Lang Attributes
|
|
129
|
+
- **Missing lang attributes** → Adds specified language
|
|
130
|
+
- **Empty lang attributes** → Sets proper language code
|
|
144
131
|
|
|
145
|
-
|
|
146
|
-
|
|
132
|
+
```html
|
|
133
|
+
<!-- Before -->
|
|
134
|
+
<html>
|
|
135
|
+
<html lang="">
|
|
147
136
|
|
|
148
|
-
|
|
149
|
-
|
|
137
|
+
<!-- After -->
|
|
138
|
+
<html lang="ja">
|
|
139
|
+
<html lang="ja">
|
|
150
140
|
```
|
|
151
141
|
|
|
152
|
-
###
|
|
153
|
-
|
|
142
|
+
### 3. Role Attributes
|
|
143
|
+
- **Images** → `role="img"`
|
|
144
|
+
- **Links** → `role="link"`
|
|
145
|
+
- **Clickable elements** → `role="button"`
|
|
146
|
+
- **Navigation lists** → `role="menubar"`
|
|
147
|
+
- **Menu items** → `role="menuitem"`
|
|
154
148
|
|
|
155
|
-
```
|
|
156
|
-
|
|
149
|
+
```html
|
|
150
|
+
<!-- Before -->
|
|
151
|
+
<img src="icon.png" alt="Icon">
|
|
152
|
+
<a href="/home">Home</a>
|
|
153
|
+
<div class="btn-click">Click me</div>
|
|
154
|
+
|
|
155
|
+
<!-- After -->
|
|
156
|
+
<img src="icon.png" alt="Icon" role="img">
|
|
157
|
+
<a href="/home" role="link">Home</a>
|
|
158
|
+
<div class="btn-click" role="button">Click me</div>
|
|
157
159
|
```
|
|
158
160
|
|
|
159
|
-
###
|
|
160
|
-
|
|
161
|
+
### 4. Duplicate Cleanup
|
|
162
|
+
- **Removes duplicate role attributes**
|
|
163
|
+
- **Preserves first occurrence**
|
|
164
|
+
- **Handles mixed quote styles**
|
|
161
165
|
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
### 4. addMainLandmarks(directory)
|
|
167
|
-
Phát hiện và suggest main landmarks
|
|
166
|
+
```html
|
|
167
|
+
<!-- Before -->
|
|
168
|
+
<img src="test.jpg" role="img" role="img" alt="Test">
|
|
168
169
|
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
<!-- After -->
|
|
171
|
+
<img src="test.jpg" role="img" alt="Test">
|
|
171
172
|
```
|
|
172
173
|
|
|
173
|
-
##
|
|
174
|
+
## 🌟 Smart Alt Text Generation
|
|
174
175
|
|
|
175
|
-
|
|
176
|
-
```html
|
|
177
|
-
<!-- Trước -->
|
|
178
|
-
<img src="logo.png">
|
|
179
|
-
<img src="icon.png" alt="">
|
|
176
|
+
The package uses intelligent context analysis to generate meaningful alt text:
|
|
180
177
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
178
|
+
### Context Sources
|
|
179
|
+
1. **Title attributes**
|
|
180
|
+
2. **Aria-label attributes**
|
|
181
|
+
3. **Definition terms (dt elements)**
|
|
182
|
+
4. **Parent link text**
|
|
183
|
+
5. **Nearby headings**
|
|
184
|
+
6. **Figure captions**
|
|
185
|
+
7. **Surrounding text content**
|
|
185
186
|
|
|
186
|
-
###
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
<!-- Sau -->
|
|
194
|
-
<img src="photo.jpg" alt="Beautiful sunset" role="img">
|
|
195
|
-
<a href="/about" role="link">About Us</a>
|
|
196
|
-
<button onclick="submit()" role="button">Submit</button>
|
|
197
|
-
```
|
|
187
|
+
### Fallback Patterns
|
|
188
|
+
- `logo.png` → "ロゴ" (Logo)
|
|
189
|
+
- `icon.svg` → "アイコン" (Icon)
|
|
190
|
+
- `banner.jpg` → "バナー" (Banner)
|
|
191
|
+
- `chart.png` → "グラフ" (Chart)
|
|
192
|
+
- Generic images → "画像" (Image)
|
|
198
193
|
|
|
199
|
-
|
|
200
|
-
```html
|
|
201
|
-
<!-- Trước -->
|
|
202
|
-
<html>
|
|
194
|
+
## 📊 Output Examples
|
|
203
195
|
|
|
204
|
-
|
|
205
|
-
<html lang="ja">
|
|
196
|
+
### Standard Mode
|
|
206
197
|
```
|
|
198
|
+
🚀 Starting Accessibility Fixer...
|
|
199
|
+
📝 Step 1: Fixing HTML lang attributes...
|
|
200
|
+
✅ Fixed lang attributes in 5 files
|
|
207
201
|
|
|
208
|
-
|
|
202
|
+
🖼️ Step 2: Fixing alt attributes...
|
|
203
|
+
✅ Fixed alt attributes in 12 files (34 issues)
|
|
209
204
|
|
|
210
|
-
|
|
205
|
+
🎭 Step 3: Fixing role attributes...
|
|
206
|
+
✅ Fixed role attributes in 8 files (67 issues)
|
|
211
207
|
|
|
212
|
-
|
|
208
|
+
📊 Summary:
|
|
209
|
+
Total files scanned: 25
|
|
210
|
+
Files fixed: 15
|
|
211
|
+
Total issues resolved: 106
|
|
213
212
|
|
|
214
|
-
|
|
215
|
-
generateAltText(imgTag, htmlContent = '', imgIndex = 0) {
|
|
216
|
-
// Thay đổi các text này theo ngôn ngữ dự án
|
|
217
|
-
if (srcValue.includes('logo')) {
|
|
218
|
-
return 'Logo'; // Thay vì 'ロゴ'
|
|
219
|
-
} else if (srcValue.includes('icon')) {
|
|
220
|
-
return 'Icon'; // Thay vì 'アイコン'
|
|
221
|
-
}
|
|
222
|
-
// ... thêm các cases khác
|
|
223
|
-
}
|
|
213
|
+
🎉 All accessibility fixes completed successfully!
|
|
224
214
|
```
|
|
225
215
|
|
|
226
|
-
###
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
216
|
+
### Comprehensive Mode
|
|
217
|
+
```
|
|
218
|
+
🎯 Running comprehensive accessibility fixes...
|
|
219
|
+
📝 Step 1: HTML lang attributes...
|
|
220
|
+
🖼️ Step 2: Alt attributes...
|
|
221
|
+
🎭 Step 3: Role attributes...
|
|
222
|
+
🧹 Step 4: Cleanup duplicate roles...
|
|
223
|
+
|
|
224
|
+
🎉 All accessibility fixes completed!
|
|
225
|
+
📊 Final Summary:
|
|
226
|
+
Total files scanned: 25
|
|
227
|
+
Files fixed: 15
|
|
228
|
+
Total issues resolved: 106
|
|
236
229
|
```
|
|
237
230
|
|
|
238
|
-
##
|
|
239
|
-
|
|
240
|
-
1. **Backup**: Luôn tạo backup trước khi chạy tool
|
|
241
|
-
2. **Test**: Chạy với `dryRun: true` trước để xem preview
|
|
242
|
-
3. **Review**: Kiểm tra kết quả sau khi chạy
|
|
243
|
-
4. **Git**: Commit code trước khi chạy tool
|
|
231
|
+
## 🔒 Safety Features
|
|
244
232
|
|
|
245
|
-
|
|
233
|
+
- **Automatic backups** with `.backup` extension
|
|
234
|
+
- **Dry run mode** for safe previewing
|
|
235
|
+
- **Non-destructive** - only adds missing attributes
|
|
236
|
+
- **Duplicate prevention** - won't add existing attributes
|
|
237
|
+
- **Error handling** - continues processing on individual file errors
|
|
246
238
|
|
|
247
|
-
|
|
248
|
-
- Số files được xử lý
|
|
249
|
-
- Số issues được tìm thấy và sửa
|
|
250
|
-
- Chi tiết từng file
|
|
251
|
-
- Thống kê tổng quan
|
|
239
|
+
## 🛠️ Configuration
|
|
252
240
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
241
|
+
### Package.json Scripts
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"scripts": {
|
|
245
|
+
"a11y:fix": "gbu-a11y",
|
|
246
|
+
"a11y:check": "gbu-a11y --dry-run",
|
|
247
|
+
"a11y:comprehensive": "gbu-a11y --comprehensive",
|
|
248
|
+
"a11y:cleanup": "gbu-a11y --cleanup-only"
|
|
249
|
+
}
|
|
250
|
+
}
|
|
262
251
|
```
|
|
263
252
|
|
|
264
|
-
|
|
253
|
+
### CI/CD Integration
|
|
254
|
+
```yaml
|
|
255
|
+
# GitHub Actions example
|
|
256
|
+
- name: Check Accessibility
|
|
257
|
+
run: npx gbu-accessibility-package --dry-run
|
|
265
258
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
cd accessibility-package
|
|
269
|
-
npm install
|
|
259
|
+
- name: Fix Accessibility Issues
|
|
260
|
+
run: npx gbu-accessibility-package --comprehensive
|
|
270
261
|
```
|
|
271
262
|
|
|
272
|
-
|
|
273
|
-
Chạy cleanup script:
|
|
274
|
-
```javascript
|
|
275
|
-
// Tạo file cleanup.js
|
|
276
|
-
const fs = require('fs');
|
|
277
|
-
const path = require('path');
|
|
278
|
-
|
|
279
|
-
async function cleanup() {
|
|
280
|
-
// Code cleanup duplicate roles
|
|
281
|
-
const content = await fs.promises.readFile('file.html', 'utf8');
|
|
282
|
-
const fixed = content.replace(/role="([^"]+)"(\s+role="\1")+/g, 'role="$1"');
|
|
283
|
-
await fs.promises.writeFile('file.html', fixed);
|
|
284
|
-
}
|
|
285
|
-
```
|
|
263
|
+
## 🤝 Contributing
|
|
286
264
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
265
|
+
1. Fork the repository
|
|
266
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
267
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
268
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
269
|
+
5. Open a Pull Request
|
|
291
270
|
|
|
292
271
|
## 📝 License
|
|
293
272
|
|
|
294
|
-
MIT License -
|
|
273
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
295
274
|
|
|
296
|
-
##
|
|
275
|
+
## 🆘 Support
|
|
276
|
+
|
|
277
|
+
- 📧 **Issues**: [GitHub Issues](https://github.com/your-org/gbu-accessibility-package/issues)
|
|
278
|
+
- 📖 **Documentation**: [GitHub Wiki](https://github.com/your-org/gbu-accessibility-package/wiki)
|
|
279
|
+
- 💬 **Discussions**: [GitHub Discussions](https://github.com/your-org/gbu-accessibility-package/discussions)
|
|
280
|
+
|
|
281
|
+
## 🏆 Why Choose GBU Accessibility Package?
|
|
297
282
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
283
|
+
- ✅ **Zero Configuration** - Works out of the box
|
|
284
|
+
- ✅ **Smart & Context-Aware** - Not just generic fixes
|
|
285
|
+
- ✅ **Safe & Reliable** - Automatic backups and dry run
|
|
286
|
+
- ✅ **Comprehensive** - Covers all major accessibility issues
|
|
287
|
+
- ✅ **Fast & Efficient** - Batch processing with detailed reports
|
|
288
|
+
- ✅ **WCAG Compliant** - Follows accessibility standards
|
|
302
289
|
|
|
303
290
|
---
|
|
304
291
|
|
|
305
|
-
|
|
292
|
+
Made with ❤️ by the GBU Team
|
package/cli.js
CHANGED
|
@@ -16,7 +16,9 @@ const options = {
|
|
|
16
16
|
language: 'ja',
|
|
17
17
|
backupFiles: true,
|
|
18
18
|
dryRun: false,
|
|
19
|
-
help: false
|
|
19
|
+
help: false,
|
|
20
|
+
cleanupOnly: false,
|
|
21
|
+
comprehensive: false
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
// Parse arguments
|
|
@@ -42,6 +44,13 @@ for (let i = 0; i < args.length; i++) {
|
|
|
42
44
|
case '--dry-run':
|
|
43
45
|
options.dryRun = true;
|
|
44
46
|
break;
|
|
47
|
+
case '--cleanup-only':
|
|
48
|
+
options.cleanupOnly = true;
|
|
49
|
+
break;
|
|
50
|
+
case '--comprehensive':
|
|
51
|
+
case '--all':
|
|
52
|
+
options.comprehensive = true;
|
|
53
|
+
break;
|
|
45
54
|
default:
|
|
46
55
|
if (!arg.startsWith('-')) {
|
|
47
56
|
options.directory = arg;
|
|
@@ -61,10 +70,14 @@ Options:
|
|
|
61
70
|
-l, --language <lang> Language for lang attribute (default: ja)
|
|
62
71
|
--no-backup Don't create backup files
|
|
63
72
|
--dry-run Preview changes without applying
|
|
73
|
+
--cleanup-only Only cleanup duplicate role attributes
|
|
74
|
+
--comprehensive, --all Run all fixes including cleanup (recommended)
|
|
64
75
|
-h, --help Show this help message
|
|
65
76
|
|
|
66
77
|
Examples:
|
|
67
|
-
node cli.js # Fix current directory
|
|
78
|
+
node cli.js # Fix current directory (standard fixes)
|
|
79
|
+
node cli.js --comprehensive # Run all fixes including cleanup
|
|
80
|
+
node cli.js --cleanup-only # Only cleanup duplicate roles
|
|
68
81
|
node cli.js ./src # Fix src directory
|
|
69
82
|
node cli.js -l en --dry-run ./dist # Preview fixes for dist directory in English
|
|
70
83
|
node cli.js --no-backup ./public # Fix without creating backups
|
|
@@ -95,6 +108,32 @@ async function main() {
|
|
|
95
108
|
});
|
|
96
109
|
|
|
97
110
|
try {
|
|
111
|
+
// Handle different modes
|
|
112
|
+
if (options.comprehensive) {
|
|
113
|
+
// Run comprehensive fix (all fixes including cleanup)
|
|
114
|
+
console.log(chalk.blue('🎯 Running comprehensive accessibility fixes...'));
|
|
115
|
+
const results = await fixer.fixAllAccessibilityIssues(options.directory);
|
|
116
|
+
|
|
117
|
+
// Results already logged in the method
|
|
118
|
+
return;
|
|
119
|
+
|
|
120
|
+
} else if (options.cleanupOnly) {
|
|
121
|
+
// Only cleanup duplicate roles
|
|
122
|
+
console.log(chalk.blue('🧹 Running cleanup for duplicate role attributes...'));
|
|
123
|
+
const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
|
|
124
|
+
const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
|
|
125
|
+
|
|
126
|
+
console.log(chalk.green(`\n✅ Cleaned duplicate roles in ${cleanupFixed} files`));
|
|
127
|
+
|
|
128
|
+
if (options.dryRun) {
|
|
129
|
+
console.log(chalk.cyan('\n💡 This was a dry run. Use without --dry-run to apply changes.'));
|
|
130
|
+
} else {
|
|
131
|
+
console.log(chalk.green('\n🎉 Cleanup completed successfully!'));
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Standard mode - run individual fixes
|
|
98
137
|
// Fix HTML lang attributes
|
|
99
138
|
console.log(chalk.yellow('📝 Step 1: Fixing HTML lang attributes...'));
|
|
100
139
|
const langResults = await fixer.fixHtmlLang(options.directory);
|
|
@@ -146,6 +185,9 @@ async function main() {
|
|
|
146
185
|
console.log(chalk.gray(' Backup files created with .backup extension'));
|
|
147
186
|
}
|
|
148
187
|
}
|
|
188
|
+
|
|
189
|
+
// Suggest cleanup if not comprehensive mode
|
|
190
|
+
console.log(chalk.blue('\n💡 Pro tip: Use --comprehensive to include duplicate role cleanup!'));
|
|
149
191
|
|
|
150
192
|
} catch (error) {
|
|
151
193
|
console.error(chalk.red('❌ Error occurred:'), error.message);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ja">
|
|
3
|
+
<head>
|
|
4
|
+
<title>Test Duplicate Roles</title>
|
|
5
|
+
</head>
|
|
6
|
+
<body>
|
|
7
|
+
<h1>Test Duplicate Role Attributes</h1>
|
|
8
|
+
|
|
9
|
+
<!-- Images with duplicate roles -->
|
|
10
|
+
<img src="logo.png" alt="Logo" role="img" role="img" role="img" role="img" role="img">
|
|
11
|
+
<img src="banner.jpg" alt="Banner" role="img" role="img" role="img" role="img" role="img" role="img">
|
|
12
|
+
|
|
13
|
+
<!-- Links with duplicate roles -->
|
|
14
|
+
<a href="/home" role="link" role="link" role="link" role="link" role="link">Home</a>
|
|
15
|
+
<a href="/about" role="link" role="link" role="link" role="link" role="link" role="link">About</a>
|
|
16
|
+
|
|
17
|
+
<!-- Buttons with duplicate roles -->
|
|
18
|
+
<button onclick="submit()" role="button" role="button" role="button" role="button" role="button">Submit</button>
|
|
19
|
+
<button type="button" role="button" role="button" role="button">Click Me</button>
|
|
20
|
+
|
|
21
|
+
<!-- Mixed quotes -->
|
|
22
|
+
<div onclick="toggle()" role="button" role='button' role="button" role="button" role="button">Toggle</div>
|
|
23
|
+
<span onclick="show()" role='button' role="button" role="button" role="button" role="button">Show</span>
|
|
24
|
+
|
|
25
|
+
<!-- Navigation with duplicates -->
|
|
26
|
+
<nav>
|
|
27
|
+
<ul class="nav-menu" role="menubar" role="menubar" role="menubar" role="menubar" role="menubar">
|
|
28
|
+
<li class="nav-item" role="menuitem" role="menuitem" role="menuitem" role="menuitem" role="menuitem">
|
|
29
|
+
<a href="/products" role="link" role="link" role="link" role="link" role="link">Products</a>
|
|
30
|
+
</li>
|
|
31
|
+
<li class="nav-item" role="menuitem" role="menuitem" role="menuitem" role="menuitem" role="menuitem" role="menuitem">
|
|
32
|
+
<a href="/services" role="link" role="link" role="link" role="link" role="link" role="link">Services</a>
|
|
33
|
+
</li>
|
|
34
|
+
</ul>
|
|
35
|
+
</nav>
|
|
36
|
+
|
|
37
|
+
<!-- Complex duplicates -->
|
|
38
|
+
<article>
|
|
39
|
+
<h2>Article with Issues</h2>
|
|
40
|
+
<img src="article1.jpg" alt="Article Image" role="img" role="img" role="img" role="img" role="img">
|
|
41
|
+
<p>Some content...</p>
|
|
42
|
+
<a href="/read-more" role="link" role="link" role="link" role="link" role="link" role="link" role="link">Read More</a>
|
|
43
|
+
</article>
|
|
44
|
+
</body>
|
|
45
|
+
</html>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ja">
|
|
3
|
+
<head>
|
|
4
|
+
<title>Test Duplicate Roles</title>
|
|
5
|
+
</head>
|
|
6
|
+
<body>
|
|
7
|
+
<h1>Test Duplicate Role Attributes</h1>
|
|
8
|
+
|
|
9
|
+
<!-- Images with duplicate roles -->
|
|
10
|
+
<img src="logo.png" alt="Logo" role="img" role="img" role="img" role="img">
|
|
11
|
+
<img src="banner.jpg" alt="Banner" role="img" role="img" role="img" role="img" role="img">
|
|
12
|
+
|
|
13
|
+
<!-- Links with duplicate roles -->
|
|
14
|
+
<a href="/home" role="link" role="link" role="link" role="link">Home</a>
|
|
15
|
+
<a href="/about" role="link" role="link" role="link" role="link" role="link">About</a>
|
|
16
|
+
|
|
17
|
+
<!-- Buttons with duplicate roles -->
|
|
18
|
+
<button onclick="submit()" role="button" role="button" role="button" role="button">Submit</button>
|
|
19
|
+
<button type="button" role="button" role="button" role="button">Click Me</button>
|
|
20
|
+
|
|
21
|
+
<!-- Mixed quotes -->
|
|
22
|
+
<div onclick="toggle()" role="button" role='button' role="button" role="button">Toggle</div>
|
|
23
|
+
<span onclick="show()" role='button' role="button" role="button" role="button">Show</span>
|
|
24
|
+
|
|
25
|
+
<!-- Navigation with duplicates -->
|
|
26
|
+
<nav>
|
|
27
|
+
<ul class="nav-menu" role="menubar" role="menubar" role="menubar" role="menubar">
|
|
28
|
+
<li class="nav-item" role="menuitem" role="menuitem" role="menuitem" role="menuitem">
|
|
29
|
+
<a href="/products" role="link" role="link" role="link" role="link">Products</a>
|
|
30
|
+
</li>
|
|
31
|
+
<li class="nav-item" role="menuitem" role="menuitem" role="menuitem" role="menuitem" role="menuitem">
|
|
32
|
+
<a href="/services" role="link" role="link" role="link" role="link" role="link">Services</a>
|
|
33
|
+
</li>
|
|
34
|
+
</ul>
|
|
35
|
+
</nav>
|
|
36
|
+
|
|
37
|
+
<!-- Complex duplicates -->
|
|
38
|
+
<article>
|
|
39
|
+
<h2>Article with Issues</h2>
|
|
40
|
+
<img src="article1.jpg" alt="Article Image" role="img" role="img" role="img" role="img">
|
|
41
|
+
<p>Some content...</p>
|
|
42
|
+
<a href="/read-more" role="link" role="link" role="link" role="link" role="link" role="link">Read More</a>
|
|
43
|
+
</article>
|
|
44
|
+
</body>
|
|
45
|
+
</html>
|
package/demo/sample.html
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
2
|
+
<html lang="ja">
|
|
3
3
|
<head>
|
|
4
4
|
<title>Demo HTML for Accessibility Testing</title>
|
|
5
5
|
</head>
|
|
@@ -7,27 +7,27 @@
|
|
|
7
7
|
<h1>Sample Website</h1>
|
|
8
8
|
|
|
9
9
|
<!-- Images without alt or role -->
|
|
10
|
-
<img src="logo.png">
|
|
11
|
-
<img src="banner.jpg" alt="">
|
|
12
|
-
<img src="icon.svg" alt="Home Icon">
|
|
10
|
+
<img src="logo.png" alt="Sample Website" role="img" role="img" role="img">
|
|
11
|
+
<img src="banner.jpg" alt="Sample Website" role="img" role="img" role="img">
|
|
12
|
+
<img src="icon.svg" alt="Home Icon" role="img" role="img" role="img">
|
|
13
13
|
|
|
14
14
|
<!-- Links without role -->
|
|
15
|
-
<a href="/home">Home</a>
|
|
16
|
-
<a href="/about">About Us</a>
|
|
15
|
+
<a href="/home" role="link" role="link" role="link">Home</a>
|
|
16
|
+
<a href="/about" role="link" role="link" role="link">About Us</a>
|
|
17
17
|
|
|
18
18
|
<!-- Buttons without role -->
|
|
19
|
-
<button onclick="submit()">Submit Form</button>
|
|
19
|
+
<button onclick="submit()" role="button" role="button" role="button">Submit Form</button>
|
|
20
20
|
<button type="button">Regular Button</button>
|
|
21
21
|
|
|
22
22
|
<!-- Clickable elements -->
|
|
23
|
-
<div onclick="navigate()" class="btn">Click Me</div>
|
|
24
|
-
<span onclick="toggle()">Toggle</span>
|
|
23
|
+
<div onclick="navigate()" class="btn" role="button" role="button" role="button" role="button" role="button" role="button">Click Me</div>
|
|
24
|
+
<span onclick="toggle()" role="button" role="button" role="button">Toggle</span>
|
|
25
25
|
|
|
26
26
|
<!-- Navigation -->
|
|
27
27
|
<nav>
|
|
28
|
-
<ul class="nav-menu">
|
|
29
|
-
<li class="nav-item"><a href="/products">Products</a></li>
|
|
30
|
-
<li class="nav-item"><a href="/services">Services</a></li>
|
|
28
|
+
<ul class="nav-menu" role="menubar" role="menubar" role="menubar">
|
|
29
|
+
<li class="nav-item" role="menuitem"><a href="/products" role="link" role="link" role="link">Products</a></li>
|
|
30
|
+
<li class="nav-item" role="menuitem"><a href="/services" role="link" role="link" role="link">Services</a></li>
|
|
31
31
|
</ul>
|
|
32
32
|
</nav>
|
|
33
33
|
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<article>
|
|
37
37
|
<h2>Article Title</h2>
|
|
38
38
|
<p>Some content here...</p>
|
|
39
|
-
<img src="article-image.jpg" alt="">
|
|
39
|
+
<img src="article-image.jpg" alt="Article Title" role="img" role="img" role="img">
|
|
40
40
|
</article>
|
|
41
41
|
</main>
|
|
42
42
|
|
package/demo/sample.html.backup
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
2
|
+
<html lang="ja">
|
|
3
3
|
<head>
|
|
4
4
|
<title>Demo HTML for Accessibility Testing</title>
|
|
5
5
|
</head>
|
|
@@ -7,27 +7,27 @@
|
|
|
7
7
|
<h1>Sample Website</h1>
|
|
8
8
|
|
|
9
9
|
<!-- Images without alt or role -->
|
|
10
|
-
<img src="logo.png">
|
|
11
|
-
<img src="banner.jpg" alt="">
|
|
12
|
-
<img src="icon.svg" alt="Home Icon">
|
|
10
|
+
<img src="logo.png" alt="Sample Website" role="img" role="img" role="img">
|
|
11
|
+
<img src="banner.jpg" alt="Sample Website" role="img" role="img" role="img">
|
|
12
|
+
<img src="icon.svg" alt="Home Icon" role="img" role="img" role="img">
|
|
13
13
|
|
|
14
14
|
<!-- Links without role -->
|
|
15
|
-
<a href="/home">Home</a>
|
|
16
|
-
<a href="/about">About Us</a>
|
|
15
|
+
<a href="/home" role="link" role="link" role="link">Home</a>
|
|
16
|
+
<a href="/about" role="link" role="link" role="link">About Us</a>
|
|
17
17
|
|
|
18
18
|
<!-- Buttons without role -->
|
|
19
|
-
<button onclick="submit()">Submit Form</button>
|
|
19
|
+
<button onclick="submit()" role="button" role="button" role="button">Submit Form</button>
|
|
20
20
|
<button type="button">Regular Button</button>
|
|
21
21
|
|
|
22
22
|
<!-- Clickable elements -->
|
|
23
|
-
<div onclick="navigate()" class="btn">Click Me</div>
|
|
24
|
-
<span onclick="toggle()">Toggle</span>
|
|
23
|
+
<div onclick="navigate()" class="btn" role="button" role="button" role="button" role="button" role="button" role="button">Click Me</div>
|
|
24
|
+
<span onclick="toggle()" role="button" role="button" role="button">Toggle</span>
|
|
25
25
|
|
|
26
26
|
<!-- Navigation -->
|
|
27
27
|
<nav>
|
|
28
|
-
<ul class="nav-menu">
|
|
29
|
-
<li class="nav-item"><a href="/products">Products</a></li>
|
|
30
|
-
<li class="nav-item"><a href="/services">Services</a></li>
|
|
28
|
+
<ul class="nav-menu" role="menubar" role="menubar" role="menubar">
|
|
29
|
+
<li class="nav-item"><a href="/products" role="link" role="link" role="link">Products</a></li>
|
|
30
|
+
<li class="nav-item"><a href="/services" role="link" role="link" role="link">Services</a></li>
|
|
31
31
|
</ul>
|
|
32
32
|
</nav>
|
|
33
33
|
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<article>
|
|
37
37
|
<h2>Article Title</h2>
|
|
38
38
|
<p>Some content here...</p>
|
|
39
|
-
<img src="article-image.jpg" alt="">
|
|
39
|
+
<img src="article-image.jpg" alt="Article Title" role="img" role="img" role="img">
|
|
40
40
|
</article>
|
|
41
41
|
</main>
|
|
42
42
|
|
package/lib/fixer.js
CHANGED
|
@@ -645,73 +645,105 @@ class AccessibilityFixer {
|
|
|
645
645
|
|
|
646
646
|
// Fix all images - add role="img" (only if no role exists)
|
|
647
647
|
fixed = fixed.replace(
|
|
648
|
-
/<img([^>]
|
|
649
|
-
(match,
|
|
648
|
+
/<img([^>]*>)/gi,
|
|
649
|
+
(match, fullTag) => {
|
|
650
|
+
// Check if role attribute already exists
|
|
651
|
+
if (/role\s*=/i.test(match)) {
|
|
652
|
+
return match; // Return unchanged if role already exists
|
|
653
|
+
}
|
|
650
654
|
console.log(chalk.yellow(` 🖼️ Added role="img" to image element`));
|
|
651
|
-
return
|
|
655
|
+
return match.replace(/(<img[^>]*?)(\s*>)/i, '$1 role="img"$2');
|
|
652
656
|
}
|
|
653
657
|
);
|
|
654
658
|
|
|
655
659
|
// Fix button elements with onclick - add role="button"
|
|
656
660
|
fixed = fixed.replace(
|
|
657
|
-
/<button([^>]*onclick[^>]
|
|
658
|
-
(match
|
|
661
|
+
/<button([^>]*onclick[^>]*>)/gi,
|
|
662
|
+
(match) => {
|
|
663
|
+
// Check if role attribute already exists
|
|
664
|
+
if (/role\s*=/i.test(match)) {
|
|
665
|
+
return match; // Return unchanged if role already exists
|
|
666
|
+
}
|
|
659
667
|
console.log(chalk.yellow(` 🔘 Added role="button" to button with onclick`));
|
|
660
|
-
return
|
|
668
|
+
return match.replace(/(<button[^>]*?)(\s*>)/i, '$1 role="button"$2');
|
|
661
669
|
}
|
|
662
670
|
);
|
|
663
671
|
|
|
664
672
|
// Fix anchor elements - add role="link"
|
|
665
673
|
fixed = fixed.replace(
|
|
666
|
-
/<a([^>]*href[^>]
|
|
667
|
-
(match
|
|
674
|
+
/<a([^>]*href[^>]*>)/gi,
|
|
675
|
+
(match) => {
|
|
676
|
+
// Check if role attribute already exists
|
|
677
|
+
if (/role\s*=/i.test(match)) {
|
|
678
|
+
return match; // Return unchanged if role already exists
|
|
679
|
+
}
|
|
668
680
|
console.log(chalk.yellow(` 🔗 Added role="link" to anchor element`));
|
|
669
|
-
return
|
|
681
|
+
return match.replace(/(<a[^>]*?)(\s*>)/i, '$1 role="link"$2');
|
|
670
682
|
}
|
|
671
683
|
);
|
|
672
684
|
|
|
673
685
|
// Fix any element with onclick (except a and button) - add role="button"
|
|
674
686
|
fixed = fixed.replace(
|
|
675
|
-
/<((?!a|button)[a-zA-Z][a-zA-Z0-9]*)([^>]*onclick[^>]
|
|
676
|
-
(match, tag
|
|
687
|
+
/<((?!a|button)[a-zA-Z][a-zA-Z0-9]*)([^>]*onclick[^>]*>)/gi,
|
|
688
|
+
(match, tag) => {
|
|
689
|
+
// Check if role attribute already exists
|
|
690
|
+
if (/role\s*=/i.test(match)) {
|
|
691
|
+
return match; // Return unchanged if role already exists
|
|
692
|
+
}
|
|
677
693
|
console.log(chalk.yellow(` 🔘 Added role="button" to ${tag} with onclick`));
|
|
678
|
-
return
|
|
694
|
+
return match.replace(/(<[^>]*?)(\s*>)/i, '$1 role="button"$2');
|
|
679
695
|
}
|
|
680
696
|
);
|
|
681
697
|
|
|
682
698
|
// Fix clickable divs - add role="button"
|
|
683
699
|
fixed = fixed.replace(
|
|
684
|
-
/<div([^>]*class="[^"]*(?:btn|button|click)[^"]*"[^>]
|
|
685
|
-
(match
|
|
700
|
+
/<div([^>]*class="[^"]*(?:btn|button|click)[^"]*"[^>]*>)/gi,
|
|
701
|
+
(match) => {
|
|
702
|
+
// Check if role attribute already exists
|
|
703
|
+
if (/role\s*=/i.test(match)) {
|
|
704
|
+
return match; // Return unchanged if role already exists
|
|
705
|
+
}
|
|
686
706
|
console.log(chalk.yellow(` 🔘 Added role="button" to clickable div`));
|
|
687
|
-
return
|
|
707
|
+
return match.replace(/(<div[^>]*?)(\s*>)/i, '$1 role="button"$2');
|
|
688
708
|
}
|
|
689
709
|
);
|
|
690
710
|
|
|
691
711
|
// Fix focusable elements with tabindex
|
|
692
712
|
fixed = fixed.replace(
|
|
693
|
-
/<(div|span)([^>]*tabindex\s*=\s*[""']?[0-9-]+[""']?[^>]
|
|
694
|
-
(match, tag
|
|
713
|
+
/<(div|span)([^>]*tabindex\s*=\s*[""']?[0-9-]+[""']?[^>]*>)/gi,
|
|
714
|
+
(match, tag) => {
|
|
715
|
+
// Check if role attribute already exists
|
|
716
|
+
if (/role\s*=/i.test(match)) {
|
|
717
|
+
return match; // Return unchanged if role already exists
|
|
718
|
+
}
|
|
695
719
|
console.log(chalk.yellow(` ⌨️ Added role="button" to focusable ${tag}`));
|
|
696
|
-
return
|
|
720
|
+
return match.replace(/(<[^>]*?)(\s*>)/i, '$1 role="button"$2');
|
|
697
721
|
}
|
|
698
722
|
);
|
|
699
723
|
|
|
700
724
|
// Fix navigation lists that should be menus
|
|
701
725
|
fixed = fixed.replace(
|
|
702
|
-
/<ul([^>]*class="[^"]*(?:nav|menu)[^"]*"[^>]
|
|
703
|
-
(match
|
|
726
|
+
/<ul([^>]*class="[^"]*(?:nav|menu)[^"]*"[^>]*>)/gi,
|
|
727
|
+
(match) => {
|
|
728
|
+
// Check if role attribute already exists
|
|
729
|
+
if (/role\s*=/i.test(match)) {
|
|
730
|
+
return match; // Return unchanged if role already exists
|
|
731
|
+
}
|
|
704
732
|
console.log(chalk.yellow(` 📋 Added role="menubar" to navigation list`));
|
|
705
|
-
return
|
|
733
|
+
return match.replace(/(<ul[^>]*?)(\s*>)/i, '$1 role="menubar"$2');
|
|
706
734
|
}
|
|
707
735
|
);
|
|
708
736
|
|
|
709
737
|
// Fix list items in navigation menus
|
|
710
738
|
fixed = fixed.replace(
|
|
711
|
-
/<li([^>]*class="[^"]*(?:nav|menu)[^"]*"[^>]
|
|
712
|
-
(match
|
|
739
|
+
/<li([^>]*class="[^"]*(?:nav|menu)[^"]*"[^>]*>)/gi,
|
|
740
|
+
(match) => {
|
|
741
|
+
// Check if role attribute already exists
|
|
742
|
+
if (/role\s*=/i.test(match)) {
|
|
743
|
+
return match; // Return unchanged if role already exists
|
|
744
|
+
}
|
|
713
745
|
console.log(chalk.yellow(` 📋 Added role="menuitem" to navigation list item`));
|
|
714
|
-
return
|
|
746
|
+
return match.replace(/(<li[^>]*?)(\s*>)/i, '$1 role="menuitem"$2');
|
|
715
747
|
}
|
|
716
748
|
);
|
|
717
749
|
|
|
@@ -739,9 +771,153 @@ class AccessibilityFixer {
|
|
|
739
771
|
return candidates;
|
|
740
772
|
}
|
|
741
773
|
|
|
774
|
+
async cleanupDuplicateRoles(directory = '.') {
|
|
775
|
+
console.log(chalk.blue('🧹 Cleaning up duplicate role attributes...'));
|
|
776
|
+
|
|
777
|
+
const htmlFiles = await this.findHtmlFiles(directory);
|
|
778
|
+
const results = [];
|
|
779
|
+
let totalFixedFiles = 0;
|
|
780
|
+
|
|
781
|
+
for (const file of htmlFiles) {
|
|
782
|
+
try {
|
|
783
|
+
const content = await fs.readFile(file, 'utf8');
|
|
784
|
+
const fixed = this.cleanupDuplicateRolesInContent(content);
|
|
785
|
+
|
|
786
|
+
if (fixed !== content) {
|
|
787
|
+
if (this.config.backupFiles) {
|
|
788
|
+
await fs.writeFile(`${file}.backup`, content);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (!this.config.dryRun) {
|
|
792
|
+
await fs.writeFile(file, fixed);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
console.log(chalk.green(`✅ Cleaned duplicate roles in: ${file}`));
|
|
796
|
+
results.push({ file, status: 'fixed' });
|
|
797
|
+
totalFixedFiles++;
|
|
798
|
+
} else {
|
|
799
|
+
results.push({ file, status: 'no-change' });
|
|
800
|
+
}
|
|
801
|
+
} catch (error) {
|
|
802
|
+
console.error(chalk.red(`❌ Error processing ${file}: ${error.message}`));
|
|
803
|
+
results.push({ file, status: 'error', error: error.message });
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
console.log(chalk.blue(`\n📊 Summary: Cleaned duplicate roles in ${totalFixedFiles} files`));
|
|
808
|
+
return results;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
cleanupDuplicateRolesInContent(content) {
|
|
812
|
+
let fixed = content;
|
|
813
|
+
let changesMade = false;
|
|
814
|
+
|
|
815
|
+
// Pattern to match duplicate role attributes
|
|
816
|
+
// Matches: role="value" role="value" or role="value" role="value" role="value" etc.
|
|
817
|
+
const duplicateRolePattern = /(\s+role\s*=\s*["']([^"']+)["'])(\s+role\s*=\s*["']\2["'])+/gi;
|
|
818
|
+
|
|
819
|
+
fixed = fixed.replace(duplicateRolePattern, (match, firstRole, roleValue) => {
|
|
820
|
+
changesMade = true;
|
|
821
|
+
console.log(chalk.yellow(` 🧹 Removed duplicate role="${roleValue}" attributes`));
|
|
822
|
+
return firstRole; // Keep only the first occurrence
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
// Also handle cases where roles have different quotes but same value
|
|
826
|
+
// e.g., role="button" role='button'
|
|
827
|
+
const mixedQuotePattern = /(\s+role\s*=\s*["']([^"']+)["'])(\s+role\s*=\s*['"]?\2['"]?)+/gi;
|
|
828
|
+
|
|
829
|
+
fixed = fixed.replace(mixedQuotePattern, (match, firstRole, roleValue) => {
|
|
830
|
+
if (!changesMade) { // Only log if not already logged above
|
|
831
|
+
changesMade = true;
|
|
832
|
+
console.log(chalk.yellow(` 🧹 Removed duplicate role="${roleValue}" attributes (mixed quotes)`));
|
|
833
|
+
}
|
|
834
|
+
return firstRole;
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
return fixed;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
async fixAllAccessibilityIssues(directory = '.') {
|
|
841
|
+
console.log(chalk.blue('🚀 Starting comprehensive accessibility fixes...'));
|
|
842
|
+
|
|
843
|
+
const results = {
|
|
844
|
+
lang: [],
|
|
845
|
+
alt: [],
|
|
846
|
+
roles: [],
|
|
847
|
+
cleanup: []
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
try {
|
|
851
|
+
// Step 1: Fix lang attributes
|
|
852
|
+
console.log(chalk.yellow('\n📝 Step 1: HTML lang attributes...'));
|
|
853
|
+
results.lang = await this.fixHtmlLang(directory);
|
|
854
|
+
|
|
855
|
+
// Step 2: Fix alt attributes
|
|
856
|
+
console.log(chalk.yellow('\n🖼️ Step 2: Alt attributes...'));
|
|
857
|
+
results.alt = await this.fixEmptyAltAttributes(directory);
|
|
858
|
+
|
|
859
|
+
// Step 3: Fix role attributes
|
|
860
|
+
console.log(chalk.yellow('\n🎭 Step 3: Role attributes...'));
|
|
861
|
+
results.roles = await this.fixRoleAttributes(directory);
|
|
862
|
+
|
|
863
|
+
// Step 4: Cleanup duplicate roles
|
|
864
|
+
console.log(chalk.yellow('\n🧹 Step 4: Cleanup duplicate roles...'));
|
|
865
|
+
results.cleanup = await this.cleanupDuplicateRoles(directory);
|
|
866
|
+
|
|
867
|
+
// Summary
|
|
868
|
+
const totalFiles = new Set([
|
|
869
|
+
...results.lang.map(r => r.file),
|
|
870
|
+
...results.alt.map(r => r.file),
|
|
871
|
+
...results.roles.map(r => r.file),
|
|
872
|
+
...results.cleanup.map(r => r.file)
|
|
873
|
+
]).size;
|
|
874
|
+
|
|
875
|
+
const totalFixed = new Set([
|
|
876
|
+
...results.lang.filter(r => r.status === 'fixed').map(r => r.file),
|
|
877
|
+
...results.alt.filter(r => r.status === 'fixed').map(r => r.file),
|
|
878
|
+
...results.roles.filter(r => r.status === 'fixed').map(r => r.file),
|
|
879
|
+
...results.cleanup.filter(r => r.status === 'fixed').map(r => r.file)
|
|
880
|
+
]).size;
|
|
881
|
+
|
|
882
|
+
const totalIssues =
|
|
883
|
+
results.lang.filter(r => r.status === 'fixed').length +
|
|
884
|
+
results.alt.reduce((sum, r) => sum + (r.issues || 0), 0) +
|
|
885
|
+
results.roles.reduce((sum, r) => sum + (r.issues || 0), 0) +
|
|
886
|
+
results.cleanup.filter(r => r.status === 'fixed').length;
|
|
887
|
+
|
|
888
|
+
console.log(chalk.green('\n🎉 All accessibility fixes completed!'));
|
|
889
|
+
console.log(chalk.blue('📊 Final Summary:'));
|
|
890
|
+
console.log(chalk.white(` Total files scanned: ${totalFiles}`));
|
|
891
|
+
console.log(chalk.green(` Files fixed: ${totalFixed}`));
|
|
892
|
+
console.log(chalk.yellow(` Total issues resolved: ${totalIssues}`));
|
|
893
|
+
|
|
894
|
+
if (this.config.dryRun) {
|
|
895
|
+
console.log(chalk.cyan('\n💡 This was a dry run. Use without --dry-run to apply changes.'));
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return results;
|
|
899
|
+
|
|
900
|
+
} catch (error) {
|
|
901
|
+
console.error(chalk.red('❌ Error during comprehensive fix:'), error.message);
|
|
902
|
+
throw error;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
742
906
|
async findHtmlFiles(directory) {
|
|
743
907
|
const files = [];
|
|
744
908
|
|
|
909
|
+
// Check if the path is a file or directory
|
|
910
|
+
const stat = await fs.stat(directory);
|
|
911
|
+
|
|
912
|
+
if (stat.isFile()) {
|
|
913
|
+
// If it's a file, check if it's HTML
|
|
914
|
+
if (directory.endsWith('.html')) {
|
|
915
|
+
files.push(directory);
|
|
916
|
+
}
|
|
917
|
+
return files;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// If it's a directory, scan recursively
|
|
745
921
|
async function scan(dir) {
|
|
746
922
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
747
923
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gbu-accessibility-package",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Automated accessibility fixes for HTML files - Alt attributes, Lang attributes, Role attributes. Smart context-aware alt text generation and comprehensive role attribute management.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"start": "node cli.js",
|
|
12
12
|
"fix": "node cli.js",
|
|
13
13
|
"preview": "node cli.js --dry-run",
|
|
14
|
-
"test": "node
|
|
14
|
+
"test": "node test-package.js",
|
|
15
15
|
"demo": "node cli.js --dry-run demo",
|
|
16
|
-
"prepublishOnly": "npm run
|
|
16
|
+
"prepublishOnly": "npm run test"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"accessibility",
|