adapt-authoring-ui 2.0.6 → 3.0.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.
@@ -1,32 +1,17 @@
1
1
  name: Release
2
+
2
3
  on:
3
4
  push:
4
5
  branches:
5
6
  - master
6
7
 
8
+ permissions:
9
+ contents: write
10
+ issues: write
11
+ pull-requests: write
12
+ id-token: write
13
+ packages: write
14
+
7
15
  jobs:
8
16
  release:
9
- name: Release
10
- runs-on: ubuntu-latest
11
- permissions:
12
- contents: write # to be able to publish a GitHub release
13
- issues: write # to be able to comment on released issues
14
- pull-requests: write # to be able to comment on released pull requests
15
- id-token: write # to enable use of OIDC for trusted publishing and npm provenance
16
- steps:
17
- - name: Checkout
18
- uses: actions/checkout@v3
19
- with:
20
- fetch-depth: 0
21
- - name: Setup Node.js
22
- uses: actions/setup-node@v3
23
- with:
24
- node-version: 'lts/*'
25
- - name: Update npm
26
- run: npm install -g npm@latest
27
- - name: Install dependencies
28
- run: npm install
29
- - name: Release
30
- env:
31
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32
- run: npx semantic-release
17
+ uses: adaptlearning/semantic-release-config/.github/workflows/release.yml@master
@@ -10,6 +10,7 @@ define(function(require) {
10
10
  return;
11
11
  }
12
12
  var model = Origin.editor.data.content.findWhere({ _id: data.id });
13
+ await model.fetch();
13
14
  var form = await Origin.scaffold.buildForm({ model });
14
15
  Helpers.setPageTitle(model);
15
16
  Origin.sidebar.addView(new EditorArticleEditSidebarView({ model, form }).$el);
@@ -10,6 +10,7 @@ define(function(require) {
10
10
  return;
11
11
  }
12
12
  var model = Origin.editor.data.content.findWhere({ _id: data.id });
13
+ await model.fetch();
13
14
  var form = await Origin.scaffold.buildForm({ model });
14
15
  Helpers.setPageTitle(model);
15
16
  Origin.sidebar.addView(new EditorBlockEditSidebarView({ model, form }).$el);
@@ -14,6 +14,7 @@ define(function(require) {
14
14
  });
15
15
  }
16
16
  const model = _id === 'new' ? Origin.editor.data.newcomponent : Origin.editor.data.content.findWhere({ _id });
17
+ if (_id !== 'new') await model.fetch();
17
18
  const form = await Origin.scaffold.buildForm({ model });
18
19
  Helpers.setPageTitle(model);
19
20
  Origin.sidebar.addView(new EditorComponentEditSidebarView({ model, form }).$el);
@@ -7,6 +7,7 @@ define(function(require) {
7
7
 
8
8
  Origin.on('editor:config', async function(data) {
9
9
  var model = Origin.editor.data.config;
10
+ await model.fetch();
10
11
  var form = await Origin.scaffold.buildForm({ model });
11
12
  Helpers.setPageTitle(model);
12
13
  Origin.sidebar.addView(new EditorConfigEditSidebarView({ form }).$el);
@@ -30,6 +30,7 @@ define(function(require) {
30
30
  });
31
31
 
32
32
  async function renderContentObjectEdit(data) {
33
+ await data.model.fetch();
33
34
  Helpers.setPageTitle(data.model);
34
35
  var form = await Origin.scaffold.buildForm({ model: data.model });
35
36
  Origin.sidebar.addView(new EditorPageEditSidebarView({ form: form }).$el);
@@ -199,8 +199,19 @@ define(function(require){
199
199
  this.contentobjects.add(newModel);
200
200
  },
201
201
 
202
- onItemDeleted: async function(oldModel) {
203
- await this.updateContentObjects(true);
202
+ onItemDeleted: function(oldModel) {
203
+ var content = Origin.editor.data.content;
204
+ var removeDescendants = function(parentId) {
205
+ content.where({ _parentId: parentId }).forEach(function(child) {
206
+ removeDescendants(child.get('_id'));
207
+ content.remove(child);
208
+ });
209
+ };
210
+ removeDescendants(oldModel.get('_id'));
211
+ content.remove(oldModel);
212
+ this.contentobjects = new Backbone.Collection(content.filter(function(c) {
213
+ return c.get('_type') === 'menu' || c.get('_type') === 'page';
214
+ }));
204
215
  Origin.trigger('editorView:menuView:updateSelectedItem', this.contentobjects.findWhere({ _id: oldModel.get('_parentId') }));
205
216
  }
206
217
  }, {
@@ -10,6 +10,7 @@ define(function(require) {
10
10
  Origin.on('editor:course', renderCourseEdit);
11
11
 
12
12
  async function renderCourseEdit() {
13
+ await Origin.editor.data.course.fetch();
13
14
  EditorHelpers.setPageTitle(Origin.editor.data.course);
14
15
  var form = await Origin.scaffold.buildForm({ model: Origin.editor.data.course });
15
16
  Origin.contentPane.setView(EditorCourseEditView, { model: Origin.editor.data.course, form });
@@ -1,11 +1,13 @@
1
1
  // LICENCE https://github.com/adaptlearning/adapt_authoring/blob/master/LICENSE
2
2
  define(function(require) {
3
3
  var _ = require('underscore');
4
- var ContentCollection = require('core/collections/contentCollection');
4
+ var Backbone = require('backbone');
5
+ var ContentModel = require('core/models/contentModel');
5
6
  var ContentPluginCollection = require('core/collections/contentPluginCollection');
6
7
  var Origin = require('core/origin');
7
8
 
8
9
  var isLoaded;
10
+ var lastModified;
9
11
 
10
12
  var Preloader = {
11
13
  /**
@@ -21,35 +23,19 @@ define(function(require) {
21
23
 
22
24
  isLoaded = false;
23
25
 
24
- if(await isOutdated()) {
25
- try {
26
+ try {
27
+ var treeData = await isOutdated();
28
+ if(treeData) {
26
29
  await Promise.all([
27
- new Promise (async (resolve) => {
28
- const content = new ContentCollection(undefined, { _courseId: Origin.location.route1 });
29
- content.queryOptions = { limit: 0 };
30
- await content.fetch();
31
- Origin.editor.data.content = content;
32
- Origin.editor.data.course = content.findWhere({ _type: 'course' });
33
- Origin.editor.data.config = content.findWhere({ _type: 'config' });
34
- if(!Origin.editor.data.course || !Origin.editor.data.config) {
35
- return handleError();
36
- }
37
- resolve()
38
- }),
39
- new Promise (async (resolve) => {
40
- const componentTypes = new ContentPluginCollection(undefined, { type: 'component' });
41
- await componentTypes.fetch();
42
- Origin.editor.data.componentTypes = componentTypes;
43
- resolve()
44
- })
30
+ loadTree(treeData),
31
+ loadComponentTypes()
45
32
  ]);
46
- } catch(e) {
47
- return handleError();
48
33
  }
34
+ } catch(e) {
35
+ return handleError();
49
36
  }
50
37
  isLoaded = true;
51
38
 
52
- Origin.editor.data.lastFetch = new Date().toISOString();
53
39
  if(_.isFunction(callback)) {
54
40
  callback();
55
41
  }
@@ -60,6 +46,7 @@ define(function(require) {
60
46
  Origin.editor.data.course = undefined;
61
47
  Origin.editor.data.config = undefined;
62
48
  }
49
+ lastModified = undefined;
63
50
  },
64
51
  /**
65
52
  * Makes sure all data has been loaded and calls callback
@@ -69,18 +56,74 @@ define(function(require) {
69
56
  }
70
57
  };
71
58
 
59
+ async function loadTree(treeData) {
60
+ var items, resLastModified;
61
+ if(treeData && treeData.items) {
62
+ items = treeData.items;
63
+ resLastModified = treeData.lastModified;
64
+ } else {
65
+ var courseId = Origin.location.route1;
66
+ await new Promise(function(resolve, reject) {
67
+ $.ajax({
68
+ url: '/api/content/tree/' + courseId,
69
+ success: function(data, textStatus, jqXHR) {
70
+ items = data;
71
+ resLastModified = jqXHR.getResponseHeader('Last-Modified');
72
+ resolve();
73
+ },
74
+ error: function() { reject(new Error('Failed to fetch content tree')); }
75
+ });
76
+ });
77
+ }
78
+ lastModified = resLastModified;
79
+ var content = new Backbone.Collection(items.map(function(item) {
80
+ return new ContentModel(item);
81
+ }), {
82
+ model: ContentModel,
83
+ comparator: '_sortOrder'
84
+ });
85
+ Origin.editor.data.content = content;
86
+ Origin.editor.data.course = content.findWhere({ _type: 'course' });
87
+ Origin.editor.data.config = content.findWhere({ _type: 'config' });
88
+ if(!Origin.editor.data.course || !Origin.editor.data.config) {
89
+ return handleError();
90
+ }
91
+ }
92
+
93
+ async function loadComponentTypes() {
94
+ var componentTypes = new ContentPluginCollection(undefined, { type: 'component' });
95
+ await componentTypes.fetch();
96
+ Origin.editor.data.componentTypes = componentTypes;
97
+ }
98
+
72
99
  async function isOutdated() {
73
100
  try {
74
101
  if(Origin.editor.data.course.get('_id') !== Origin.location.route1) {
75
- Origin.editor.data.lastFetch = 0
102
+ lastModified = undefined;
76
103
  return true;
77
104
  }
78
105
  } catch(e) {
79
- Origin.editor.data.lastFetch = 0
106
+ lastModified = undefined;
80
107
  return true;
81
108
  }
82
- const [latestDoc] = await $.post('/api/content/query?sort={%22updatedAt%22:-1}&limit=1', {_courseId:Origin.editor.data.course.get('_id')});
83
- return !latestDoc || new Date(Origin.editor.data.lastFetch) < new Date(latestDoc.updatedAt);
109
+ if(!lastModified) return true;
110
+ var courseId = Origin.editor.data.course.get('_id');
111
+ return new Promise(function(resolve, reject) {
112
+ $.ajax({
113
+ url: '/api/content/tree/' + courseId,
114
+ headers: { 'If-Modified-Since': lastModified },
115
+ complete: function(jqXHR) {
116
+ if(jqXHR.status === 304) return resolve(false);
117
+ if(jqXHR.status === 200) {
118
+ return resolve({
119
+ items: jqXHR.responseJSON,
120
+ lastModified: jqXHR.getResponseHeader('Last-Modified')
121
+ });
122
+ }
123
+ reject(new Error('Failed to check content staleness'));
124
+ }
125
+ });
126
+ });
84
127
  }
85
128
 
86
129
  function handleError() {
@@ -195,19 +195,12 @@ define(function(require) {
195
195
  },
196
196
 
197
197
  copyIdToClipboard: function(model) {
198
- var id = model.get('_id');
199
-
200
- if (helpers.copyStringToClipboard(id)) {
201
- Origin.Notify.alert({
202
- type: 'info',
203
- text: Origin.l10n.t('app.copyidtoclipboardsuccess', { id: id })
204
- });
205
- } else {
206
- Origin.Notify.alert({
207
- type: 'warning',
208
- text: Origin.l10n.t('app.copyidtoclipboarderror', { id: id })
209
- });
210
- }
198
+ const id = model.get('_friendlyId') || model.get('_id');
199
+ const isSuccess = helpers.copyStringToClipboard(id)
200
+ Origin.Notify.alert({
201
+ type: isSuccess ? 'info' : 'warning',
202
+ text: Origin.l10n.t(isSuccess ? 'app.copyidtoclipboardsuccess' : 'app.copyidtoclipboarderror', { id })
203
+ });
211
204
  },
212
205
 
213
206
  pasteFromClipboard: function(_parentId, _sortOrder, _layout) {
@@ -8,7 +8,6 @@ define(function(require) {
8
8
  Origin.on('editor:menusettings', function(data) {
9
9
  var route1 = Origin.location.route1;
10
10
  var model = Origin.editor.data.config;
11
-
12
11
  Helpers.setPageTitle(model);
13
12
 
14
13
  var backButtonRoute = `#/editor/${route1}/menu`;
@@ -8,7 +8,11 @@ define(function(require) {
8
8
  Origin.router.navigate(`#/editor/${Origin.editor.data.course.get('_id')}/${ROUTE}`, { trigger: true });
9
9
  });
10
10
 
11
- Origin.on('editor:selecttheme', () => {
11
+ Origin.on('editor:selecttheme', async () => {
12
+ await Promise.all([
13
+ Origin.editor.data.course.fetch(),
14
+ Origin.editor.data.config.fetch()
15
+ ]);
12
16
  Origin.sidebar.addView(new EditorThemingSidebarView().$el);
13
17
  Origin.contentPane.setView(EditorThemingView);
14
18
  });
@@ -76,7 +76,7 @@ define(function(require){
76
76
  this.$('#tags').val(this.model.get('tags').map(t => t.title));
77
77
  }
78
78
  const data = await Helpers.submitForm(this.$('form.frameworkImport'), { data: { dryRun } })
79
- return Object.assign(data, { canImport: data.statusReport.error === undefined });
79
+ return Object.assign(data, { canImport: !data.statusReport.error?.length });
80
80
  },
81
81
 
82
82
  onAddTag: function (tag) {
package/lib/UiModule.js CHANGED
@@ -1,6 +1,18 @@
1
1
  import { AbstractModule, Hook } from 'adapt-authoring-core'
2
2
  import path from 'path'
3
3
  import UiBuild from './UiBuild.js'
4
+
5
+ const CSP_POLICY = [
6
+ "default-src 'self'",
7
+ "frame-ancestors 'self'",
8
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdnjs.cloudflare.com https://cdn.ckeditor.com",
9
+ "style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://cdn.ckeditor.com",
10
+ "font-src 'self' data: https://cdnjs.cloudflare.com",
11
+ "img-src 'self' data: blob:",
12
+ "worker-src 'self' blob:",
13
+ "connect-src 'self' https://cdnjs.cloudflare.com https://cdn.ckeditor.com",
14
+ 'upgrade-insecure-requests'
15
+ ].join('; ')
4
16
  /**
5
17
  * The main entry-point for the Adapt authoring tool web-app/front-end
6
18
  * @memberof ui
@@ -96,6 +108,7 @@ class UiModule extends AbstractModule {
96
108
  servePage (pageName) {
97
109
  return async (req, res, next) => {
98
110
  const framework = await this.app.waitForModule('adaptframework')
111
+ res.setHeader('Content-Security-Policy', CSP_POLICY)
99
112
  res.render(path.resolve(this.buildDir, pageName), {
100
113
  buildType: this.isProduction ? 'production' : 'development',
101
114
  versions: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-ui",
3
- "version": "2.0.6",
3
+ "version": "3.0.0",
4
4
  "description": "Front-end application for the Adapt authoring tool",
5
5
  "homepage": "https://github.com/adapt-security/adapt-authoring-ui",
6
6
  "license": "GPL-3.0",
@@ -20,7 +20,7 @@
20
20
  "@rollup/plugin-babel": "^6.0.3",
21
21
  "@rollup/plugin-commonjs": "^29.0.0",
22
22
  "@rollup/plugin-node-resolve": "^16.0.3",
23
- "@rollup/plugin-terser": "^0.4.0",
23
+ "@rollup/plugin-terser": "^1.0.0",
24
24
  "adapt-authoring-core": "^2.0.0",
25
25
  "babel-plugin-transform-amd-to-es6": "^1.0.2",
26
26
  "cpy": "^13.0.0",
@@ -38,40 +38,16 @@
38
38
  },
39
39
  "peerDependencies": {
40
40
  "adapt-authoring-adaptframework": "^2.0.0",
41
+ "adapt-authoring-authored": "^1.4.0",
42
+ "adapt-authoring-content": "^3.0.0",
41
43
  "adapt-authoring-server": "^2.0.0"
42
44
  },
43
45
  "devDependencies": {
44
- "@semantic-release/git": "^10.0.1",
45
- "conventional-changelog-eslint": "^6.0.0",
46
- "semantic-release": "^25.0.2",
46
+ "@adaptlearning/semantic-release-config": "^1.0.0",
47
47
  "standard": "^17.1.0"
48
48
  },
49
49
  "release": {
50
- "plugins": [
51
- [
52
- "@semantic-release/commit-analyzer",
53
- {
54
- "preset": "eslint"
55
- }
56
- ],
57
- [
58
- "@semantic-release/release-notes-generator",
59
- {
60
- "preset": "eslint"
61
- }
62
- ],
63
- "@semantic-release/npm",
64
- "@semantic-release/github",
65
- [
66
- "@semantic-release/git",
67
- {
68
- "assets": [
69
- "package.json"
70
- ],
71
- "message": "Chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
72
- }
73
- ]
74
- ]
50
+ "extends": "@adaptlearning/semantic-release-config"
75
51
  },
76
52
  "standard": {
77
53
  "ignore": [