studiokit-scaffolding-js 5.1.6 → 5.2.0-next.1.2
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/lib/components/Quill/Formats/Image.d.ts +1 -0
- package/lib/components/Quill/Formats/Image.js +8 -3
- package/lib/components/Quill/ImageDropModule.d.ts +32 -0
- package/lib/components/Quill/ImageDropModule.js +128 -0
- package/lib/components/Quill/ImageWarning.d.ts +7 -0
- package/lib/components/Quill/ImageWarning.js +37 -0
- package/lib/components/Quill/ImageWithAltTextModal.js +16 -4
- package/lib/components/Quill/index.js +2 -0
- package/lib/utils/quill.d.ts +8 -7
- package/lib/utils/quill.js +69 -8
- package/package.json +1 -1
- package/lib/components/Quill/ImageMissingAltTextWarning.d.ts +0 -5
- package/lib/components/Quill/ImageMissingAltTextWarning.js +0 -15
|
@@ -24,6 +24,7 @@ export interface ImageValue {
|
|
|
24
24
|
alt?: string;
|
|
25
25
|
width: string;
|
|
26
26
|
height: string;
|
|
27
|
+
nonPublic?: boolean;
|
|
27
28
|
}
|
|
28
29
|
declare type ImageBlotOnClick = (img: ImageValue, blot: Image) => void;
|
|
29
30
|
export declare function setImageClickHandlersEnabled(value: boolean): void;
|
|
@@ -68,6 +68,10 @@ var Image = /** @class */ (function (_super) {
|
|
|
68
68
|
else {
|
|
69
69
|
node.className = 'alt-text-missing';
|
|
70
70
|
}
|
|
71
|
+
// Note: an image that's not public error "level" supercedes an image missing alt text
|
|
72
|
+
if (value.nonPublic) {
|
|
73
|
+
node.className = 'non-public-image';
|
|
74
|
+
}
|
|
71
75
|
node.setAttribute('src', value.src);
|
|
72
76
|
// Setting up the height and width to be reactive when an image is getting resize
|
|
73
77
|
node.setAttribute('height', (_b = value.height) !== null && _b !== void 0 ? _b : 'auto');
|
|
@@ -77,10 +81,11 @@ var Image = /** @class */ (function (_super) {
|
|
|
77
81
|
/** Returns the value for a DOM Node */
|
|
78
82
|
Image.value = function (node) {
|
|
79
83
|
return {
|
|
80
|
-
width: node.getAttribute('width'),
|
|
81
|
-
height: node.getAttribute('height'),
|
|
84
|
+
width: node.getAttribute('width') || 'auto',
|
|
85
|
+
height: node.getAttribute('height') || 'auto',
|
|
82
86
|
alt: node.getAttribute('alt') || undefined,
|
|
83
|
-
src: node.getAttribute('src')
|
|
87
|
+
src: node.getAttribute('src'),
|
|
88
|
+
nonPublic: node.classList.contains('non-public-image')
|
|
84
89
|
};
|
|
85
90
|
};
|
|
86
91
|
Image.blotName = 'image';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Quill } from 'react-quill';
|
|
2
|
+
/**
|
|
3
|
+
* Custom module for quilljs to allow user to drag images from their file system into the editor
|
|
4
|
+
* and paste images from clipboard (Works on Chrome, Firefox, Edge, not on Safari)
|
|
5
|
+
* @see https://quilljs.com/blog/building-a-custom-module/
|
|
6
|
+
*
|
|
7
|
+
* Roughly forked (and TypeScripted) from https://github.com/kensnyder/quill-image-drop-module/blob/master/src/ImageDrop.js
|
|
8
|
+
*/
|
|
9
|
+
export declare class ImageDropModule {
|
|
10
|
+
quill: Quill;
|
|
11
|
+
options: any;
|
|
12
|
+
/**
|
|
13
|
+
* Instantiate the module given a quill instance and any options
|
|
14
|
+
*/
|
|
15
|
+
constructor(quill: Quill, options: any);
|
|
16
|
+
/**
|
|
17
|
+
* Handler for drop event to read dropped files from evt.dataTransfer
|
|
18
|
+
*/
|
|
19
|
+
handleDrop: (evt: DragEvent) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Handler for paste event to read pasted files from evt.clipboardData
|
|
22
|
+
*/
|
|
23
|
+
handlePaste: (evt: ClipboardEvent) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Insert the image into the document at the current cursor position
|
|
26
|
+
*/
|
|
27
|
+
insert: (base64DataUrl: string | ArrayBuffer | null | undefined, alt?: string) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Extract base64 image URIs from a list of files from evt.dataTransfer or evt.clipboardData
|
|
30
|
+
*/
|
|
31
|
+
readFiles: (files: DataTransferItemList | FileList, callback: (dataUrl: string | ArrayBuffer | null | undefined) => void) => void;
|
|
32
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImageDropModule = void 0;
|
|
4
|
+
var react_quill_1 = require("react-quill");
|
|
5
|
+
var logger_1 = require("../../utils/logger");
|
|
6
|
+
var Delta = react_quill_1.Quill.import('delta');
|
|
7
|
+
/**
|
|
8
|
+
* Custom module for quilljs to allow user to drag images from their file system into the editor
|
|
9
|
+
* and paste images from clipboard (Works on Chrome, Firefox, Edge, not on Safari)
|
|
10
|
+
* @see https://quilljs.com/blog/building-a-custom-module/
|
|
11
|
+
*
|
|
12
|
+
* Roughly forked (and TypeScripted) from https://github.com/kensnyder/quill-image-drop-module/blob/master/src/ImageDrop.js
|
|
13
|
+
*/
|
|
14
|
+
var ImageDropModule = /** @class */ (function () {
|
|
15
|
+
/**
|
|
16
|
+
* Instantiate the module given a quill instance and any options
|
|
17
|
+
*/
|
|
18
|
+
function ImageDropModule(quill, options) {
|
|
19
|
+
var _this = this;
|
|
20
|
+
/**
|
|
21
|
+
* Handler for drop event to read dropped files from evt.dataTransfer
|
|
22
|
+
*/
|
|
23
|
+
this.handleDrop = function (evt) {
|
|
24
|
+
evt.preventDefault();
|
|
25
|
+
if (evt.dataTransfer && evt.dataTransfer.files && evt.dataTransfer.files.length) {
|
|
26
|
+
var selection = document.getSelection();
|
|
27
|
+
var range = null;
|
|
28
|
+
if (document.caretRangeFromPoint) {
|
|
29
|
+
range = document.caretRangeFromPoint(evt.clientX, evt.clientY);
|
|
30
|
+
}
|
|
31
|
+
else if (document.caretPositionFromPoint) {
|
|
32
|
+
var pos = document.caretPositionFromPoint(evt.clientX, evt.clientY);
|
|
33
|
+
range = document.createRange();
|
|
34
|
+
range.setStart((pos === null || pos === void 0 ? void 0 : pos.offsetNode) || document.body, (pos === null || pos === void 0 ? void 0 : pos.offset) || 0);
|
|
35
|
+
range.collapse(true);
|
|
36
|
+
}
|
|
37
|
+
if (selection && range) {
|
|
38
|
+
selection.setBaseAndExtent(range.startContainer, range.startOffset, range.startContainer, range.startOffset);
|
|
39
|
+
}
|
|
40
|
+
_this.readFiles(evt.dataTransfer.files, _this.insert.bind(_this));
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Handler for paste event to read pasted files from evt.clipboardData
|
|
45
|
+
*/
|
|
46
|
+
this.handlePaste = function (evt) {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
// Get alt text from clipboard data and add it to the image we create with base64 data
|
|
49
|
+
var text = (_a = evt.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text/html');
|
|
50
|
+
var alt = '';
|
|
51
|
+
if (text) {
|
|
52
|
+
var regExp = /alt="(.*?)"/;
|
|
53
|
+
alt = ((_b = regExp.exec(text)) === null || _b === void 0 ? void 0 : _b[1]) || '';
|
|
54
|
+
}
|
|
55
|
+
if (evt.clipboardData && evt.clipboardData.items && evt.clipboardData.items.length) {
|
|
56
|
+
_this.readFiles(evt.clipboardData.items, function (base64DataUrl) {
|
|
57
|
+
// wait until after the default paste action finishes
|
|
58
|
+
setTimeout(function () { return _this.insert(base64DataUrl, alt); }, 50);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Insert the image into the document at the current cursor position
|
|
64
|
+
*/
|
|
65
|
+
this.insert = function (base64DataUrl, alt) {
|
|
66
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
67
|
+
if (alt === void 0) { alt = ''; }
|
|
68
|
+
var logger = logger_1.getLogger();
|
|
69
|
+
var index = (_this.quill.getSelection() || {}).index || _this.quill.getLength();
|
|
70
|
+
var diff = new Delta();
|
|
71
|
+
diff.ops = [];
|
|
72
|
+
if (index > 0) {
|
|
73
|
+
diff.ops.push({ retain: index });
|
|
74
|
+
}
|
|
75
|
+
var indexBefore = Math.max(0, index - 1);
|
|
76
|
+
var contentsBeforeIndex = _this.quill.getContents(indexBefore, 1);
|
|
77
|
+
logger.debug('insert - find contents before index', { index: indexBefore, contentsBeforeIndex: contentsBeforeIndex });
|
|
78
|
+
// when copy-pasting an image from the web/browser, the web url image is immediately inserted.
|
|
79
|
+
// check if a web image exists at the previous index, and replace it.
|
|
80
|
+
// when copy-pasting an image file from the computer, the `base64DataUrl` image is immediately inserted,but is missing some blot info.
|
|
81
|
+
// check if the same image data already exists at the previous index, and replace it.
|
|
82
|
+
if (((_e = (_d = (_c = (_b = (_a = contentsBeforeIndex === null || contentsBeforeIndex === void 0 ? void 0 : contentsBeforeIndex.ops) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.insert) === null || _c === void 0 ? void 0 : _c.image) === null || _d === void 0 ? void 0 : _d.src) === null || _e === void 0 ? void 0 : _e.startsWith('http')) ||
|
|
83
|
+
((_j = (_h = (_g = (_f = contentsBeforeIndex === null || contentsBeforeIndex === void 0 ? void 0 : contentsBeforeIndex.ops) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.insert) === null || _h === void 0 ? void 0 : _h.image) === null || _j === void 0 ? void 0 : _j.src) === base64DataUrl) {
|
|
84
|
+
logger.debug('insert - remove image before index');
|
|
85
|
+
diff.ops = [];
|
|
86
|
+
if (indexBefore > 0) {
|
|
87
|
+
diff.ops.push({ retain: indexBefore });
|
|
88
|
+
}
|
|
89
|
+
diff.ops.push({ delete: 1 });
|
|
90
|
+
}
|
|
91
|
+
// insert the image at the index
|
|
92
|
+
diff.ops.push({ insert: { image: { src: base64DataUrl, alt: alt } } });
|
|
93
|
+
if (diff.ops.length > 0) {
|
|
94
|
+
logger.debug('insert - quill.updateContents', { diff: diff });
|
|
95
|
+
_this.quill.updateContents(diff, 'user');
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Extract base64 image URIs from a list of files from evt.dataTransfer or evt.clipboardData
|
|
100
|
+
*/
|
|
101
|
+
this.readFiles = function (files, callback) {
|
|
102
|
+
var regExp = RegExp(/^image\/(gif|jpe?g|a?png|svg|webp|bmp|vnd\.microsoft\.icon)/i);
|
|
103
|
+
[].forEach.call(files, function (file) {
|
|
104
|
+
if (!regExp.test(file.type)) {
|
|
105
|
+
// file is not an image
|
|
106
|
+
// Note that some file formats such as psd start with image/* but are not readable
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
var reader = new FileReader();
|
|
110
|
+
reader.onload = function (evt) {
|
|
111
|
+
var _a;
|
|
112
|
+
callback((_a = evt.target) === null || _a === void 0 ? void 0 : _a.result);
|
|
113
|
+
};
|
|
114
|
+
var blob = file.getAsFile ? file.getAsFile() : file;
|
|
115
|
+
if (blob instanceof Blob) {
|
|
116
|
+
reader.readAsDataURL(blob);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
this.quill = quill;
|
|
121
|
+
this.handleDrop = this.handleDrop.bind(this);
|
|
122
|
+
this.handlePaste = this.handlePaste.bind(this);
|
|
123
|
+
this.quill.root.addEventListener('drop', this.handleDrop, false);
|
|
124
|
+
this.quill.root.addEventListener('paste', this.handlePaste, false);
|
|
125
|
+
}
|
|
126
|
+
return ImageDropModule;
|
|
127
|
+
}());
|
|
128
|
+
exports.ImageDropModule = ImageDropModule;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
+
}) : function(o, v) {
|
|
12
|
+
o["default"] = v;
|
|
13
|
+
});
|
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
+
if (mod && mod.__esModule) return mod;
|
|
16
|
+
var result = {};
|
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
+
__setModuleDefault(result, mod);
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.ImageWarning = void 0;
|
|
26
|
+
var Error_1 = __importDefault(require("@material-ui/icons/Error"));
|
|
27
|
+
var Warning_1 = __importDefault(require("@material-ui/icons/Warning"));
|
|
28
|
+
var react_1 = __importStar(require("react"));
|
|
29
|
+
var ImageWarning = function (_a) {
|
|
30
|
+
var text = _a.text, className = _a.className, isError = _a.isError;
|
|
31
|
+
return react_1.useMemo(function () {
|
|
32
|
+
return (react_1.default.createElement("div", { className: "small pv1 mv1" + (className ? " " + className : '') },
|
|
33
|
+
isError ? react_1.default.createElement(Error_1.default, { className: "fill-red nt1 mr1" }) : react_1.default.createElement(Warning_1.default, { className: "fill-orange nt1 mr1" }),
|
|
34
|
+
text));
|
|
35
|
+
}, [className, text, isError]);
|
|
36
|
+
};
|
|
37
|
+
exports.ImageWarning = ImageWarning;
|
|
@@ -32,6 +32,7 @@ var react_bootstrap_1 = require("react-bootstrap");
|
|
|
32
32
|
var react_quill_1 = require("react-quill");
|
|
33
33
|
var ConnectedModal_1 = __importDefault(require("../ConnectedModal"));
|
|
34
34
|
var ErrorMessage_1 = require("../ErrorMessage");
|
|
35
|
+
var ImageWarning_1 = require("./ImageWarning");
|
|
35
36
|
var Delta = react_quill_1.Quill.import('delta');
|
|
36
37
|
var mdiUpload = 'M9,16V10H5L12,3L19,10H15V16H9M5,20V18H19V20H5Z';
|
|
37
38
|
var IconUpload = (react_1.default.createElement(core_1.SvgIcon, { role: "presentation" },
|
|
@@ -41,17 +42,20 @@ var ImageWithAltTextModal = function (_a) {
|
|
|
41
42
|
// setup state that is editable in the modal, based on the targeted image, if any
|
|
42
43
|
var _b = react_1.useState(imageToEdit === null || imageToEdit === void 0 ? void 0 : imageToEdit.alt), alt = _b[0], setAlt = _b[1];
|
|
43
44
|
var _c = react_1.useState(imageToEdit === null || imageToEdit === void 0 ? void 0 : imageToEdit.src), src = _c[0], setSrc = _c[1];
|
|
44
|
-
var _d = react_1.useState(
|
|
45
|
+
var _d = react_1.useState(imageToEdit === null || imageToEdit === void 0 ? void 0 : imageToEdit.nonPublic), nonPublic = _d[0], setNonPublic = _d[1];
|
|
46
|
+
var _e = react_1.useState(false), isSelectedFileInvalid = _e[0], setIsSelectedFileInvalid = _e[1];
|
|
45
47
|
// update local state based on the targeted image, if any
|
|
46
48
|
react_1.useEffect(function () {
|
|
47
49
|
if (imageToEdit) {
|
|
48
50
|
setAlt(imageToEdit.alt);
|
|
49
51
|
setSrc(imageToEdit.src);
|
|
52
|
+
setNonPublic(imageToEdit.nonPublic);
|
|
50
53
|
}
|
|
51
54
|
}, [imageToEdit]);
|
|
52
55
|
var onTryClose = function () {
|
|
53
56
|
setAlt(undefined);
|
|
54
57
|
setSrc(undefined);
|
|
58
|
+
setNonPublic(undefined);
|
|
55
59
|
// programmatically reset file selector
|
|
56
60
|
fileSelector.value = '';
|
|
57
61
|
onClose();
|
|
@@ -79,6 +83,7 @@ var ImageWithAltTextModal = function (_a) {
|
|
|
79
83
|
};
|
|
80
84
|
// get the file as a dataURI in base64
|
|
81
85
|
reader.readAsDataURL(file_1);
|
|
86
|
+
setNonPublic(false);
|
|
82
87
|
}
|
|
83
88
|
};
|
|
84
89
|
};
|
|
@@ -86,7 +91,7 @@ var ImageWithAltTextModal = function (_a) {
|
|
|
86
91
|
var insertImage = function () {
|
|
87
92
|
if (reactQuillRef) {
|
|
88
93
|
var quill = reactQuillRef.getEditor();
|
|
89
|
-
var imageToInsert = { src: src, alt: alt };
|
|
94
|
+
var imageToInsert = { src: src, alt: alt, nonPublic: nonPublic };
|
|
90
95
|
if (imageToEdit) {
|
|
91
96
|
imageToInsert.height = imageToEdit.height;
|
|
92
97
|
imageToInsert.width = imageToEdit.width;
|
|
@@ -107,13 +112,20 @@ var ImageWithAltTextModal = function (_a) {
|
|
|
107
112
|
};
|
|
108
113
|
return (react_1.default.createElement(ConnectedModal_1.default, { show: isOpen, onHide: onTryClose },
|
|
109
114
|
react_1.default.createElement(react_bootstrap_1.Modal.Header, { closeButton: true },
|
|
110
|
-
react_1.default.createElement(react_bootstrap_1.Modal.Title, { className: "f4" }, imageToEdit ? "Edit
|
|
115
|
+
react_1.default.createElement(react_bootstrap_1.Modal.Title, { className: "f4" }, imageToEdit ? "Edit Image" : "Image Upload")),
|
|
111
116
|
react_1.default.createElement(react_bootstrap_1.Modal.Body, null,
|
|
112
117
|
react_1.default.createElement("p", { className: "mb2 f7" }, "* Indicates required field"),
|
|
113
118
|
src && !isSelectedFileInvalid && (react_1.default.createElement(react_bootstrap_1.Row, null,
|
|
114
119
|
react_1.default.createElement("img", { className: "mb2 h5", src: src, alt: alt }))),
|
|
115
120
|
react_1.default.createElement("div", { "aria-live": "polite", className: "f7 mb2" }, isSelectedFileInvalid && (react_1.default.createElement(ErrorMessage_1.ErrorMessage, { message: "The chosen file type is not supported. Please use one of the image formats listed below." }))),
|
|
116
|
-
!imageToEdit && (react_1.default.createElement(
|
|
121
|
+
imageToEdit && !(imageToEdit === null || imageToEdit === void 0 ? void 0 : imageToEdit.alt) && !alt && (react_1.default.createElement(ImageWarning_1.ImageWarning, { text: "This image is missing alt text. Please provide it below." })),
|
|
122
|
+
(imageToEdit === null || imageToEdit === void 0 ? void 0 : imageToEdit.nonPublic) && nonPublic && (react_1.default.createElement(ImageWarning_1.ImageWarning, { isError: true, text: react_1.default.createElement(react_1.default.Fragment, null,
|
|
123
|
+
react_1.default.createElement("span", null, "This image is not publicly available"),
|
|
124
|
+
react_1.default.createElement("div", { className: "mv2" }, "Due to browser security issues, you'll need to upload this from your computer instead of linking from its original source. To fix this:"),
|
|
125
|
+
react_1.default.createElement("ol", { className: "mb1" },
|
|
126
|
+
react_1.default.createElement("li", null, "Right click on the image and save to your computer."),
|
|
127
|
+
react_1.default.createElement("li", null, "Then use the 'Change Image' button below to upload the saved file."))) })),
|
|
128
|
+
(!imageToEdit || (imageToEdit.nonPublic && nonPublic)) && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
117
129
|
react_1.default.createElement(core_1.Button, { className: "w-100-lt-xs pa2 mv1 btn-primary", onClick: onClickImageUpload },
|
|
118
130
|
src ? react_1.default.createElement(SwapVert_1.default, null) : IconUpload,
|
|
119
131
|
react_1.default.createElement("span", { className: "ml1" },
|
|
@@ -10,6 +10,7 @@ var accessibilityFix_1 = require("./accessibilityFix");
|
|
|
10
10
|
var Image_1 = require("./Formats/Image");
|
|
11
11
|
var List_1 = require("./Formats/List");
|
|
12
12
|
var Video_1 = require("./Formats/Video");
|
|
13
|
+
var ImageDropModule_1 = require("./ImageDropModule");
|
|
13
14
|
var TableModule_1 = require("./TableModule");
|
|
14
15
|
var registerQuill = function () {
|
|
15
16
|
react_quill_1.Quill.register('modules/blotFormatter', quill_blot_formatter_1.default);
|
|
@@ -21,6 +22,7 @@ var registerQuill = function () {
|
|
|
21
22
|
// replace image format
|
|
22
23
|
react_quill_1.Quill.register('formats/video', Video_1.Video, true);
|
|
23
24
|
react_quill_1.Quill.register('modules/table', TableModule_1.TableModule);
|
|
25
|
+
react_quill_1.Quill.register('modules/imageDrop', ImageDropModule_1.ImageDropModule);
|
|
24
26
|
// apply the accessibility label issues to each icons
|
|
25
27
|
accessibilityFix_1.applyAccessibilityToIcons(react_quill_1.Quill.import('ui/icons'));
|
|
26
28
|
};
|
package/lib/utils/quill.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Quill from 'quill';
|
|
2
|
+
import { ImageValue } from '../components/Quill/Formats/Image';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* @param
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Check for non-public in the provided quill contents. Update the ImageValue of an op if an image is found to be
|
|
5
|
+
* non-public.
|
|
6
|
+
* @param quill The Quill instance
|
|
7
|
+
* @param checkedImages A list of all images we have already processed in previous calls to this function.
|
|
8
|
+
* list of processed images
|
|
8
9
|
*/
|
|
9
|
-
export declare const
|
|
10
|
+
export declare const checkForNonPublicImages: (quill: Quill, checkedImages: ImageValue[]) => void;
|
package/lib/utils/quill.js
CHANGED
|
@@ -1,12 +1,73 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
2
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
17
|
+
exports.checkForNonPublicImages = void 0;
|
|
18
|
+
var quill_1 = __importDefault(require("quill"));
|
|
19
|
+
var uuid_1 = require("uuid");
|
|
20
|
+
var actionCreator_1 = require("../redux/actionCreator");
|
|
21
|
+
var actions_1 = require("../redux/actions");
|
|
22
|
+
var noStoreSaga_1 = require("../redux/sagas/noStoreSaga");
|
|
23
|
+
var Delta = quill_1.default.import('delta');
|
|
4
24
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @param
|
|
8
|
-
*
|
|
9
|
-
*
|
|
25
|
+
* Check for non-public in the provided quill contents. Update the ImageValue of an op if an image is found to be
|
|
26
|
+
* non-public.
|
|
27
|
+
* @param quill The Quill instance
|
|
28
|
+
* @param checkedImages A list of all images we have already processed in previous calls to this function.
|
|
29
|
+
* list of processed images
|
|
10
30
|
*/
|
|
11
|
-
var
|
|
12
|
-
|
|
31
|
+
var checkForNonPublicImages = function (quill, checkedImages) {
|
|
32
|
+
var _a, _b;
|
|
33
|
+
(_b = (_a = quill
|
|
34
|
+
.getContents()) === null || _a === void 0 ? void 0 : _a.ops // Is the op an image and have we not seen it before?
|
|
35
|
+
) === null || _b === void 0 ? void 0 : _b.filter(function (op) { var _a; return ((_a = op.insert) === null || _a === void 0 ? void 0 : _a.image) && !checkedImages.find(function (image) { return image.src === op.insert.image.src; }); }).forEach(function (op) {
|
|
36
|
+
// Check if the image is public. Do not check data:image urls
|
|
37
|
+
if (op.insert.image.src && op.insert.image.src.startsWith('http')) {
|
|
38
|
+
// Use the backend to check if the image is public
|
|
39
|
+
// Due to CSP and other security measures, we can't check this in the frontend
|
|
40
|
+
// uuidv5 is a deterministic uuid generator based on a namespace and a name
|
|
41
|
+
// uuidv5.URL is the namespace for urls
|
|
42
|
+
var hookId_1 = uuid_1.v5(op.insert.image.src, uuid_1.v5.URL);
|
|
43
|
+
noStoreSaga_1.noStoreHooks.registerNoStoreActionHook(hookId_1, function (data) {
|
|
44
|
+
var _a;
|
|
45
|
+
noStoreSaga_1.noStoreHooks.unregisterNoStoreActionHook(hookId_1);
|
|
46
|
+
// Backend fetches the URL. If there is no data returned or if the data returned has headers
|
|
47
|
+
// but those headers are not of image type (i.e. it was a 302 that, when followed returned HTML
|
|
48
|
+
// (I'm looking at you, Brightspace), then we update the image insert op to be nonPublic
|
|
49
|
+
if (!data ||
|
|
50
|
+
!((_a = data[Object.keys(data).find(function (k) { return k.toLowerCase() === 'content-type'; }) || '']) === null || _a === void 0 ? void 0 : _a[0].startsWith('image'))) {
|
|
51
|
+
var contents = quill.getContents();
|
|
52
|
+
var ops = contents.map(function (o) {
|
|
53
|
+
var _a, _b;
|
|
54
|
+
return ((_a = o.insert.image) === null || _a === void 0 ? void 0 : _a.src) && ((_b = op.insert.image) === null || _b === void 0 ? void 0 : _b.src) && o.insert.image.src === op.insert.image.src
|
|
55
|
+
? __assign(__assign({}, o), { insert: __assign(__assign({}, o.insert), { image: __assign(__assign({}, o.insert.image), { nonPublic: true }) }) }) : o;
|
|
56
|
+
});
|
|
57
|
+
var updatedContents = contents.diff(new Delta(ops));
|
|
58
|
+
quill.updateContents(updatedContents);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
// This endpoint returns headers for a url. If the url is nonexistent, it will return null
|
|
62
|
+
actionCreator_1.dispatchAction(actions_1.NET_ACTION.DATA_REQUESTED, {
|
|
63
|
+
modelName: 'urlChecker',
|
|
64
|
+
guid: hookId_1,
|
|
65
|
+
noStore: true,
|
|
66
|
+
queryParams: {
|
|
67
|
+
url: op.insert.image.src
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
exports.checkForNonPublicImages = checkForNonPublicImages;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "studiokit-scaffolding-js",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.2.0-next.1.2",
|
|
4
4
|
"description": "Common scaffolding for Studio apps at Purdue",
|
|
5
5
|
"repository": "https://gitlab.com/purdue-informatics/studiokit/studiokit-scaffolding-js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.ImageMissingAltTextWarning = void 0;
|
|
7
|
-
var Warning_1 = __importDefault(require("@material-ui/icons/Warning"));
|
|
8
|
-
var react_1 = __importDefault(require("react"));
|
|
9
|
-
var ImageMissingAltTextWarning = function (_a) {
|
|
10
|
-
var className = _a.className;
|
|
11
|
-
return (react_1.default.createElement("p", { className: "small pv1 mv1" + (className ? " " + className : '') },
|
|
12
|
-
react_1.default.createElement(Warning_1.default, { className: "fill-orange nt1 mr1" }),
|
|
13
|
-
"One or more images are missing alt text. Use right-click to set alt text on images."));
|
|
14
|
-
};
|
|
15
|
-
exports.ImageMissingAltTextWarning = ImageMissingAltTextWarning;
|