mirror of
https://github.com/futurepress/epub.js.git
synced 2025-10-03 14:59:18 +02:00
replace event-emitter package with NodeJS builtin events
This commit is contained in:
parent
f09089cf77
commit
d474f804bf
12 changed files with 40 additions and 124 deletions
93
package-lock.json
generated
93
package-lock.json
generated
|
@ -12,7 +12,6 @@
|
||||||
"@types/localforage": "0.0.34",
|
"@types/localforage": "0.0.34",
|
||||||
"@xmldom/xmldom": "^0.7.5",
|
"@xmldom/xmldom": "^0.7.5",
|
||||||
"core-js": "^3.18.3",
|
"core-js": "^3.18.3",
|
||||||
"event-emitter": "^0.3.5",
|
|
||||||
"jszip": "^3.7.1",
|
"jszip": "^3.7.1",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
@ -7364,51 +7363,6 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/event-emitter": {
|
|
||||||
"version": "0.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
|
|
||||||
"integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
|
|
||||||
"dependencies": {
|
|
||||||
"d": "1",
|
|
||||||
"es5-ext": "~0.10.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/event-emitter/node_modules/d": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
|
|
||||||
"dependencies": {
|
|
||||||
"es5-ext": "^0.10.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/event-emitter/node_modules/es5-ext": {
|
|
||||||
"version": "0.10.24",
|
|
||||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.24.tgz",
|
|
||||||
"integrity": "sha1-pVh3yZJLwMjZvTwsvhdJWsFwmxQ=",
|
|
||||||
"dependencies": {
|
|
||||||
"es6-iterator": "2",
|
|
||||||
"es6-symbol": "~3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/event-emitter/node_modules/es6-iterator": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz",
|
|
||||||
"integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=",
|
|
||||||
"dependencies": {
|
|
||||||
"d": "1",
|
|
||||||
"es5-ext": "^0.10.14",
|
|
||||||
"es6-symbol": "^3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/event-emitter/node_modules/es6-symbol": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
|
|
||||||
"integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
|
|
||||||
"dependencies": {
|
|
||||||
"d": "1",
|
|
||||||
"es5-ext": "~0.10.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/eventemitter3": {
|
"node_modules/eventemitter3": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
|
||||||
|
@ -26634,53 +26588,6 @@
|
||||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
|
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"event-emitter": {
|
|
||||||
"version": "0.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
|
|
||||||
"integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
|
|
||||||
"requires": {
|
|
||||||
"d": "1",
|
|
||||||
"es5-ext": "~0.10.14"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"d": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
|
|
||||||
"requires": {
|
|
||||||
"es5-ext": "^0.10.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"es5-ext": {
|
|
||||||
"version": "0.10.24",
|
|
||||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.24.tgz",
|
|
||||||
"integrity": "sha1-pVh3yZJLwMjZvTwsvhdJWsFwmxQ=",
|
|
||||||
"requires": {
|
|
||||||
"es6-iterator": "2",
|
|
||||||
"es6-symbol": "~3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"es6-iterator": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz",
|
|
||||||
"integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=",
|
|
||||||
"requires": {
|
|
||||||
"d": "1",
|
|
||||||
"es5-ext": "^0.10.14",
|
|
||||||
"es6-symbol": "^3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"es6-symbol": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
|
|
||||||
"integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
|
|
||||||
"requires": {
|
|
||||||
"d": "1",
|
|
||||||
"es5-ext": "~0.10.14"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"eventemitter3": {
|
"eventemitter3": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
|
||||||
|
|
|
@ -58,7 +58,6 @@
|
||||||
"@types/localforage": "0.0.34",
|
"@types/localforage": "0.0.34",
|
||||||
"@xmldom/xmldom": "^0.7.5",
|
"@xmldom/xmldom": "^0.7.5",
|
||||||
"core-js": "^3.18.3",
|
"core-js": "^3.18.3",
|
||||||
"event-emitter": "^0.3.5",
|
|
||||||
"jszip": "^3.7.1",
|
"jszip": "^3.7.1",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import EventEmitter from "event-emitter";
|
import { EventEmitter } from "events";
|
||||||
import EpubCFI from "./epubcfi";
|
import EpubCFI from "./epubcfi";
|
||||||
import { EVENTS } from "./utils/constants";
|
import { EVENTS } from "./utils/constants";
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ class Annotations {
|
||||||
* @param {object} styles CSS styles to assign to annotation
|
* @param {object} styles CSS styles to assign to annotation
|
||||||
* @returns {Annotation} annotation
|
* @returns {Annotation} annotation
|
||||||
*/
|
*/
|
||||||
class Annotation {
|
class Annotation extends EventEmitter {
|
||||||
|
|
||||||
constructor ({
|
constructor ({
|
||||||
type,
|
type,
|
||||||
|
@ -223,6 +223,7 @@ class Annotation {
|
||||||
className,
|
className,
|
||||||
styles
|
styles
|
||||||
}) {
|
}) {
|
||||||
|
super();
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.cfiRange = cfiRange;
|
this.cfiRange = cfiRange;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
@ -295,7 +296,7 @@ class Annotation {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EventEmitter(Annotation.prototype);
|
Object.assign(Annotation.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
|
|
||||||
export default Annotations
|
export default Annotations
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import EventEmitter from "event-emitter";
|
import { EventEmitter } from "events";
|
||||||
import {extend, defer} from "./utils/core";
|
import {extend, defer} from "./utils/core";
|
||||||
import Url from "./utils/url";
|
import Url from "./utils/url";
|
||||||
import Path from "./utils/path";
|
import Path from "./utils/path";
|
||||||
|
@ -47,8 +47,9 @@ const INPUT_TYPE = {
|
||||||
* @example new Book("/path/to/book.epub", {})
|
* @example new Book("/path/to/book.epub", {})
|
||||||
* @example new Book({ replacements: "blobUrl" })
|
* @example new Book({ replacements: "blobUrl" })
|
||||||
*/
|
*/
|
||||||
class Book {
|
class Book extends EventEmitter {
|
||||||
constructor(url, options) {
|
constructor(url, options) {
|
||||||
|
super();
|
||||||
// Allow passing just options to the Book
|
// Allow passing just options to the Book
|
||||||
if (typeof(options) === "undefined" &&
|
if (typeof(options) === "undefined" &&
|
||||||
typeof(url) !== "string" &&
|
typeof(url) !== "string" &&
|
||||||
|
@ -763,6 +764,6 @@ class Book {
|
||||||
}
|
}
|
||||||
|
|
||||||
//-- Enable binding events to book
|
//-- Enable binding events to book
|
||||||
EventEmitter(Book.prototype);
|
Object.assign(Book.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
export default Book;
|
export default Book;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import EventEmitter from "event-emitter";
|
import { EventEmitter } from "events";
|
||||||
import {isNumber, prefixed, borders, defaults} from "./utils/core";
|
import {isNumber, prefixed, borders, defaults} from "./utils/core";
|
||||||
import EpubCFI from "./epubcfi";
|
import EpubCFI from "./epubcfi";
|
||||||
import Mapping from "./mapping";
|
import Mapping from "./mapping";
|
||||||
|
@ -21,8 +21,9 @@ const TEXT_NODE = 3;
|
||||||
* @param {string} cfiBase Section component of CFIs
|
* @param {string} cfiBase Section component of CFIs
|
||||||
* @param {number} sectionIndex Index in Spine of Conntent's Section
|
* @param {number} sectionIndex Index in Spine of Conntent's Section
|
||||||
*/
|
*/
|
||||||
class Contents {
|
class Contents extends EventEmitter {
|
||||||
constructor(doc, content, cfiBase, sectionIndex) {
|
constructor(doc, content, cfiBase, sectionIndex) {
|
||||||
|
super();
|
||||||
// Blank Cfi for Parsing
|
// Blank Cfi for Parsing
|
||||||
this.epubcfi = new EpubCFI();
|
this.epubcfi = new EpubCFI();
|
||||||
|
|
||||||
|
@ -1259,6 +1260,6 @@ class Contents {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventEmitter(Contents.prototype);
|
Object.assign(Contents.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
export default Contents;
|
export default Contents;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { extend } from "./utils/core";
|
import { extend } from "./utils/core";
|
||||||
import { EVENTS } from "./utils/constants";
|
import { EVENTS } from "./utils/constants";
|
||||||
import EventEmitter from "event-emitter";
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Figures out the CSS values to apply for a layout
|
* Figures out the CSS values to apply for a layout
|
||||||
|
@ -11,8 +11,9 @@ import EventEmitter from "event-emitter";
|
||||||
* @param {number} [settings.minSpreadWidth=800]
|
* @param {number} [settings.minSpreadWidth=800]
|
||||||
* @param {boolean} [settings.evenSpreads=false]
|
* @param {boolean} [settings.evenSpreads=false]
|
||||||
*/
|
*/
|
||||||
class Layout {
|
class Layout extends EventEmitter {
|
||||||
constructor(settings) {
|
constructor(settings) {
|
||||||
|
super();
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.name = settings.layout || "reflowable";
|
this.name = settings.layout || "reflowable";
|
||||||
this._spread = (settings.spread === "none") ? false : true;
|
this._spread = (settings.spread === "none") ? false : true;
|
||||||
|
@ -255,6 +256,6 @@ class Layout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventEmitter(Layout.prototype);
|
Object.assign(Layout.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
export default Layout;
|
export default Layout;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {qs, sprint, locationOf, defer} from "./utils/core";
|
||||||
import Queue from "./utils/queue";
|
import Queue from "./utils/queue";
|
||||||
import EpubCFI from "./epubcfi";
|
import EpubCFI from "./epubcfi";
|
||||||
import { EVENTS } from "./utils/constants";
|
import { EVENTS } from "./utils/constants";
|
||||||
import EventEmitter from "event-emitter";
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find Locations for a Book
|
* Find Locations for a Book
|
||||||
|
@ -10,8 +10,9 @@ import EventEmitter from "event-emitter";
|
||||||
* @param {request} request
|
* @param {request} request
|
||||||
* @param {number} [pause=100]
|
* @param {number} [pause=100]
|
||||||
*/
|
*/
|
||||||
class Locations {
|
class Locations extends EventEmitter {
|
||||||
constructor(spine, request, pause) {
|
constructor(spine, request, pause) {
|
||||||
|
super();
|
||||||
this.spine = spine;
|
this.spine = spine;
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.pause = pause || 100;
|
this.pause = pause || 100;
|
||||||
|
@ -496,6 +497,6 @@ class Locations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventEmitter(Locations.prototype);
|
Object.assign(Locations.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
export default Locations;
|
export default Locations;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import EventEmitter from "event-emitter";
|
import { EventEmitter } from "events";
|
||||||
import {extend, defer, windowBounds, isNumber} from "../../utils/core";
|
import {extend, defer, windowBounds, isNumber} from "../../utils/core";
|
||||||
import scrollType from "../../utils/scrolltype";
|
import scrollType from "../../utils/scrolltype";
|
||||||
import Mapping from "../../mapping";
|
import Mapping from "../../mapping";
|
||||||
|
@ -7,8 +7,9 @@ import Stage from "../helpers/stage";
|
||||||
import Views from "../helpers/views";
|
import Views from "../helpers/views";
|
||||||
import { EVENTS } from "../../utils/constants";
|
import { EVENTS } from "../../utils/constants";
|
||||||
|
|
||||||
class DefaultViewManager {
|
class DefaultViewManager extends EventEmitter {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.name = "default";
|
this.name = "default";
|
||||||
this.optsSettings = options.settings;
|
this.optsSettings = options.settings;
|
||||||
|
@ -1072,6 +1073,6 @@ class DefaultViewManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
//-- Enable binding events to Manager
|
//-- Enable binding events to Manager
|
||||||
EventEmitter(DefaultViewManager.prototype);
|
Object.assign(DefaultViewManager.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
export default DefaultViewManager;
|
export default DefaultViewManager;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {extend, defer, requestAnimationFrame, prefixed} from "../../utils/core";
|
import {extend, defer, requestAnimationFrame, prefixed} from "../../utils/core";
|
||||||
import { EVENTS, DOM_EVENTS } from "../../utils/constants";
|
import { EVENTS, DOM_EVENTS } from "../../utils/constants";
|
||||||
import EventEmitter from "event-emitter";
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
// easing equations from https://github.com/danro/easing-js/blob/master/easing.js
|
// easing equations from https://github.com/danro/easing-js/blob/master/easing.js
|
||||||
const PI_D2 = (Math.PI / 2);
|
const PI_D2 = (Math.PI / 2);
|
||||||
|
@ -22,8 +22,9 @@ const EASING_EQUATIONS = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class Snap {
|
class Snap extends EventEmitter {
|
||||||
constructor(manager, options) {
|
constructor(manager, options) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.settings = extend({
|
this.settings = extend({
|
||||||
duration: 80,
|
duration: 80,
|
||||||
|
@ -333,6 +334,6 @@ class Snap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventEmitter(Snap.prototype);
|
Object.assign(Snap.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
export default Snap;
|
export default Snap;
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import EventEmitter from "event-emitter";
|
import { EventEmitter } from "events";
|
||||||
import {extend, borders, uuid, isNumber, bounds, defer, createBlobUrl, revokeBlobUrl} from "../../utils/core";
|
import {extend, borders, uuid, isNumber, bounds, defer, createBlobUrl, revokeBlobUrl} from "../../utils/core";
|
||||||
import EpubCFI from "../../epubcfi";
|
import EpubCFI from "../../epubcfi";
|
||||||
import Contents from "../../contents";
|
import Contents from "../../contents";
|
||||||
import { EVENTS } from "../../utils/constants";
|
import { EVENTS } from "../../utils/constants";
|
||||||
import { Pane, Highlight, Underline } from "marks-pane";
|
import { Pane, Highlight, Underline } from "marks-pane";
|
||||||
|
|
||||||
class IframeView {
|
class IframeView extends EventEmitter {
|
||||||
constructor(section, options) {
|
constructor(section, options) {
|
||||||
|
super();
|
||||||
this.settings = extend({
|
this.settings = extend({
|
||||||
ignoreClass : "",
|
ignoreClass : "",
|
||||||
axis: undefined, //options.layout && options.layout.props.flow === "scrolled" ? "vertical" : "horizontal",
|
axis: undefined, //options.layout && options.layout.props.flow === "scrolled" ? "vertical" : "horizontal",
|
||||||
|
@ -846,6 +847,6 @@ class IframeView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventEmitter(IframeView.prototype);
|
Object.assign(IframeView.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
export default IframeView;
|
export default IframeView;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import EventEmitter from "event-emitter";
|
import { EventEmitter } from "events";
|
||||||
import { extend, defer, isFloat } from "./utils/core";
|
import { extend, defer, isFloat } from "./utils/core";
|
||||||
import Hook from "./utils/hook";
|
import Hook from "./utils/hook";
|
||||||
import EpubCFI from "./epubcfi";
|
import EpubCFI from "./epubcfi";
|
||||||
|
@ -40,8 +40,9 @@ import ContinuousViewManager from "./managers/continuous/index";
|
||||||
* @param {boolean} [options.allowScriptedContent=false] enable running scripts in content
|
* @param {boolean} [options.allowScriptedContent=false] enable running scripts in content
|
||||||
* @param {boolean} [options.allowPopups=false] enable opening popup in content
|
* @param {boolean} [options.allowPopups=false] enable opening popup in content
|
||||||
*/
|
*/
|
||||||
class Rendition {
|
class Rendition extends EventEmitter {
|
||||||
constructor(book, options) {
|
constructor(book, options) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.settings = extend(this.settings || {}, {
|
this.settings = extend(this.settings || {}, {
|
||||||
width: null,
|
width: null,
|
||||||
|
@ -1064,6 +1065,6 @@ class Rendition {
|
||||||
}
|
}
|
||||||
|
|
||||||
//-- Enable binding events to Renderer
|
//-- Enable binding events to Renderer
|
||||||
EventEmitter(Rendition.prototype);
|
Object.assign(Rendition.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
export default Rendition;
|
export default Rendition;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {defer, isXml, parse} from "./utils/core";
|
||||||
import httpRequest from "./utils/request";
|
import httpRequest from "./utils/request";
|
||||||
import mime from "./utils/mime";
|
import mime from "./utils/mime";
|
||||||
import Path from "./utils/path";
|
import Path from "./utils/path";
|
||||||
import EventEmitter from "event-emitter";
|
import { EventEmitter } from "events";
|
||||||
import localforage from "localforage";
|
import localforage from "localforage";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,9 +12,10 @@ import localforage from "localforage";
|
||||||
* @param {function} [requester]
|
* @param {function} [requester]
|
||||||
* @param {function} [resolver]
|
* @param {function} [resolver]
|
||||||
*/
|
*/
|
||||||
class Store {
|
class Store extends EventEmitter {
|
||||||
|
|
||||||
constructor(name, requester, resolver) {
|
constructor(name, requester, resolver) {
|
||||||
|
super();
|
||||||
this.urlCache = {};
|
this.urlCache = {};
|
||||||
|
|
||||||
this.storage = undefined;
|
this.storage = undefined;
|
||||||
|
@ -379,6 +380,6 @@ class Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventEmitter(Store.prototype);
|
Object.assign(Store.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
export default Store;
|
export default Store;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue