adapt-authoring-core 1.3.2 → 1.3.4

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.
@@ -2,6 +2,7 @@
2
2
  "module": false,
3
3
  "documentation": {
4
4
  "enable": true,
5
+ "manualCover": "docs/cover-manual.md",
5
6
  "manualIndex": "docs/index-manual.md",
6
7
  "sourceIndex": "docs/index-backend.md",
7
8
  "manualPages": {
@@ -22,6 +23,7 @@
22
23
  "manualPlugins": [
23
24
  "docs/plugins/binscripts.js",
24
25
  "docs/plugins/coremodules.js",
26
+ "docs/plugins/index-manual.js",
25
27
  "docs/plugins/licensing.js"
26
28
  ]
27
29
  }
@@ -0,0 +1,263 @@
1
+ import https from 'https'
2
+
3
+ /**
4
+ * Documentation plugin that generates a list of contributors
5
+ * from all adapt-authoring-* repositories in the adapt-security organisation.
6
+ */
7
+ export default class Contributors {
8
+ ICON_SIZE = 55
9
+ BORDER_WIDTH = 3
10
+ MIN_CONTRIBUTIONS = 25
11
+
12
+ constructor (app, config, outputDir) {
13
+ this.org = 'adapt-security'
14
+ this.repoPrefix = 'adapt-authoring'
15
+ this.perPage = 100
16
+ this.excludeUsers = ['dependabot[bot]', 'dependabot-preview[bot]', 'greenkeeper[bot]', 'semantic-release-bot', 'snyk-bot']
17
+ // Contribution tiers
18
+ this.tiers = [
19
+ { name: 'gold', count: 3, border: '#FFD700' },
20
+ { name: 'silver', count: 6, border: '#C0C0C0' },
21
+ { name: 'bronze', count: 10, border: '#CD7F32' },
22
+ { name: 'contributor' }
23
+ ]
24
+ }
25
+
26
+ async run () {
27
+ this.manualFile = 'index-manual.md'
28
+ this.replace = { CONTRIBUTORS: await this.generateContributorsList() }
29
+ }
30
+
31
+ /**
32
+ * Makes an HTTPS GET request to the GitHub API
33
+ */
34
+ async githubRequest (path) {
35
+ return new Promise((resolve, reject) => {
36
+ const options = {
37
+ hostname: 'api.github.com',
38
+ path,
39
+ method: 'GET',
40
+ headers: {
41
+ 'User-Agent': 'adapt-authoring-docs',
42
+ Accept: 'application/vnd.github.v3+json'
43
+ }
44
+ }
45
+
46
+ // Add auth token if available (increases rate limit)
47
+ if (process.env.GITHUB_TOKEN) {
48
+ options.headers.Authorization = `token ${process.env.GITHUB_TOKEN}`
49
+ }
50
+
51
+ const req = https.request(options, res => {
52
+ let data = ''
53
+ res.on('data', chunk => { data += chunk })
54
+ res.on('end', () => {
55
+ try {
56
+ resolve(JSON.parse(data))
57
+ } catch (e) {
58
+ reject(new Error(`Failed to parse GitHub response: ${e.message}`))
59
+ }
60
+ })
61
+ })
62
+
63
+ req.on('error', reject)
64
+ req.end()
65
+ })
66
+ }
67
+
68
+ /**
69
+ * Fetches all repositories matching the prefix from the organisation
70
+ */
71
+ async fetchRepos () {
72
+ const repos = []
73
+ let page = 1
74
+
75
+ while (true) {
76
+ const response = await this.githubRequest(
77
+ `/orgs/${this.org}/repos?per_page=${this.perPage}&page=${page}`
78
+ )
79
+
80
+ if (!Array.isArray(response) || response.length === 0) break
81
+
82
+ const matchingRepos = response
83
+ .filter(repo => repo.name.startsWith(this.repoPrefix))
84
+ .map(repo => repo.name)
85
+
86
+ repos.push(...matchingRepos)
87
+
88
+ if (response.length < this.perPage) break
89
+ page++
90
+ }
91
+
92
+ return repos
93
+ }
94
+
95
+ /**
96
+ * Fetches contributors for a single repository
97
+ */
98
+ async fetchRepoContributors (repoName) {
99
+ const contributors = []
100
+ let page = 1
101
+
102
+ while (true) {
103
+ const response = await this.githubRequest(
104
+ `/repos/${this.org}/${repoName}/contributors?per_page=${this.perPage}&page=${page}`
105
+ )
106
+
107
+ if (!Array.isArray(response) || response.length === 0) break
108
+
109
+ contributors.push(...response)
110
+
111
+ if (response.length < this.perPage) break
112
+ page++
113
+ }
114
+
115
+ return contributors
116
+ }
117
+
118
+ /**
119
+ * Aggregates contributors across all repositories
120
+ */
121
+ async aggregateContributors () {
122
+ const repos = await this.fetchRepos()
123
+ const contributorMap = new Map()
124
+
125
+ for (const repo of repos) {
126
+ try {
127
+ const contributors = await this.fetchRepoContributors(repo)
128
+
129
+ for (const contributor of contributors) {
130
+ if (this.excludeUsers.includes(contributor.login)) continue
131
+ if (contributor.type !== 'User') continue
132
+
133
+ if (contributorMap.has(contributor.login)) {
134
+ const existing = contributorMap.get(contributor.login)
135
+ existing.contributions += contributor.contributions
136
+ } else {
137
+ contributorMap.set(contributor.login, {
138
+ login: contributor.login,
139
+ avatarUrl: contributor.avatar_url,
140
+ profileUrl: contributor.html_url,
141
+ contributions: contributor.contributions
142
+ })
143
+ }
144
+ }
145
+ } catch (e) {
146
+ console.warn(`Failed to fetch contributors for ${repo}: ${e.message}`)
147
+ }
148
+ }
149
+
150
+ // Sort by contributions (descending) and return as array
151
+ return Array.from(contributorMap.values())
152
+ .sort((a, b) => b.contributions - a.contributions)
153
+ }
154
+
155
+ /**
156
+ * Determines the tier for a contributor based on rank and contribution count
157
+ */
158
+ getTier (contributor, rank) {
159
+ let position = 0
160
+ for (const tier of this.tiers) {
161
+ const tierMax = position + (tier.count || Infinity)
162
+ const rankMatch = !tier.count || (rank > position && rank <= tierMax)
163
+ const minContributions = tier.count ? this.MIN_CONTRIBUTIONS : 0
164
+ const countMatch = contributor.contributions >= minContributions
165
+
166
+ if (rankMatch && countMatch) {
167
+ return tier
168
+ }
169
+ position = tierMax
170
+ }
171
+ return this.tiers[this.tiers.length - 1]
172
+ }
173
+
174
+ /**
175
+ * Generates the HTML for a single contributor avatar
176
+ */
177
+ contributorToHtml (contributor, tier) {
178
+ const hexClipPath = 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)'
179
+ const isHexagon = tier.name !== 'contributor'
180
+
181
+ const wrapperStyle = [
182
+ `width: ${this.ICON_SIZE}px`,
183
+ `height: ${this.ICON_SIZE}px`,
184
+ isHexagon ? `clip-path: ${hexClipPath}` : 'border-radius: 50%',
185
+ tier.border ? `background: ${tier.border}` : '',
186
+ 'display: inline-block',
187
+ 'transition: transform 0.2s'
188
+ ].filter(Boolean).join('; ')
189
+
190
+ const imgSize = this.ICON_SIZE - (this.BORDER_WIDTH * 2)
191
+
192
+ const imgStyle = [
193
+ `width: ${imgSize}px`,
194
+ `height: ${imgSize}px`,
195
+ isHexagon ? `clip-path: ${hexClipPath}` : 'border-radius: 50%',
196
+ 'display: block',
197
+ `margin: ${this.BORDER_WIDTH}px`
198
+ ].join('; ')
199
+
200
+ return `<a href="${contributor.profileUrl}" title="${contributor.login} (${contributor.contributions} contributions)" target="_blank" rel="noopener" style="${wrapperStyle}"><img src="${contributor.avatarUrl}" alt="${contributor.login}" class="contributor-avatar contributor-${tier.name}" style="${imgStyle}" /></a>`
201
+ }
202
+
203
+ /**
204
+ * Generates the full contributors list HTML
205
+ */
206
+ async generateContributorsList () {
207
+ try {
208
+ const contributors = await this.aggregateContributors()
209
+
210
+ if (contributors.length === 0) {
211
+ return '<p>No contributors found.</p>'
212
+ }
213
+
214
+ // Group contributors by tier
215
+ const tierGroups = new Map()
216
+ contributors.forEach((contributor, index) => {
217
+ const tier = this.getTier(contributor, index + 1)
218
+ if (!tierGroups.has(tier.name)) {
219
+ tierGroups.set(tier.name, [])
220
+ }
221
+ tierGroups.get(tier.name).push(this.contributorToHtml(contributor, tier))
222
+ })
223
+
224
+ // Generate HTML for each tier row
225
+ const rows = Array.from(tierGroups.entries())
226
+ .map(([tierName, avatars]) =>
227
+ `<div class="contributors-row contributors-${tierName}">\n${avatars.join('\n')}\n</div>`
228
+ )
229
+ .join('\n')
230
+
231
+ return `<div class="contributors-grid">\n${rows}\n</div>
232
+ <style>
233
+ .contributors-grid {
234
+ margin: 0 auto;
235
+ max-width: 600px;
236
+ padding: 20px 0;
237
+ }
238
+
239
+ .contributors-row {
240
+ display: flex;
241
+ flex-wrap: wrap;
242
+ align-items: center;
243
+ justify-content: center;
244
+ gap: 4px;
245
+ margin-bottom: 12px;
246
+ text-align: center;
247
+ }
248
+
249
+ .contributors-grid a:hover {
250
+ transform: scale(1.1);
251
+ }
252
+
253
+ .contributor-avatar {
254
+ object-fit: cover;
255
+ vertical-align: middle;
256
+ }
257
+ </style>`
258
+ } catch (e) {
259
+ console.error('Failed to generate contributors list:', e)
260
+ return '<p>Unable to load contributors list.</p>'
261
+ }
262
+ }
263
+ }
@@ -0,0 +1,47 @@
1
+ # Adapt Authoring Tool Developer guides
2
+
3
+ Welcome to the technical documentation for the Adapt authoring tool — a web-based application for creating responsive, multi-device e-learning content built on the [Adapt Framework](https://github.com/adaptlearning/adapt_framework).
4
+
5
+ The authoring tool provides a user-friendly interface for building courses without needing to write code, while its modular architecture gives developers the flexibility to extend and customise virtually every aspect of the system. Whether you're looking to build custom plugins, integrate with external services, or contribute to the core codebase, you're in the right place.
6
+
7
+ ## What makes it tick
8
+
9
+ The authoring tool is built on a few key principles:
10
+
11
+ - **Modular by design** — The entire application is composed of discrete modules that can be swapped, extended, or replaced. Need custom authentication? Write an auth plugin. Want to store data differently? Create a new database adapter.
12
+
13
+ - **Schema-driven** — Content types, validation rules, and UI forms are all defined using JSON schemas. This means you can add new content types or modify existing ones without touching application code.
14
+
15
+ - **RESTful API** — Every feature is accessible via a comprehensive REST API, making it straightforward to integrate with other systems or build custom tooling.
16
+
17
+ - **Built for collaboration** — Multi-user support with role-based permissions lets teams work together on courses with appropriate access controls.
18
+
19
+ ## About this documentation
20
+
21
+ This documentation covers the technical side of the authoring tool — how it works under the hood and how to extend it. You'll find guides on writing custom modules, working with the database, creating schemas, and contributing to the project.
22
+
23
+ If you're looking for help using the authoring tool to create courses, check out the user guides on the [Adapt Learning community site](https://www.adaptlearning.org/).
24
+
25
+ ## Where to start
26
+
27
+ New to the codebase? Here are some good starting points:
28
+
29
+ - **[Folder Structure](folder-structure)** — Get familiar with how the application is organised
30
+ - **[Writing a Module](writing-a-module)** — Learn the basics of creating your own module
31
+ - **[Schemas Introduction](schemas-introduction)** — Understand how schemas drive the application
32
+ - **[Hooks](hooks)** — See how to tap into the application lifecycle
33
+
34
+ ## Get involved
35
+
36
+ The Adapt authoring tool is open source and we welcome contributions. You can find the source code and report issues on GitHub:
37
+
38
+ - [adapt-security](https://github.com/adapt-security) — Authoring tool repositories
39
+ - [adaptlearning](https://github.com/adaptlearning) — Adapt Framework and community plugins
40
+
41
+ ## Contributors
42
+
43
+ A huge thank you to everyone who has contributed to the Adapt authoring tool. This project wouldn't be possible without the time and effort of our community.
44
+
45
+ {{{CONTRIBUTORS}}}
46
+
47
+ <div class="big-text">Happy coding!</div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-core",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "A bundle of reusable 'core' functionality",
5
5
  "homepage": "https://github.com/adapt-security/adapt-authoring-core",
6
6
  "license": "GPL-3.0",
File without changes