diff --git a/package.json b/package.json
index 32c4461db..2bd80880b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "sub-store-front-end",
- "version": "2.14.206",
+ "version": "2.14.207",
"private": true,
"scripts": {
"dev": "vite --host",
@@ -24,7 +24,6 @@
"@lezer/javascript": "^1.4.13",
"@nutui/nutui": "^3.3.8",
"@replit/codemirror-indentation-markers": "^6.5.0",
- "@uiw/codemirror-extensions-hyper-link": "^4.21.21",
"@vueuse/core": "^8.9.2",
"@vueuse/integrations": "^8.9.2",
"axios": "^0.27.2",
diff --git a/src/views/editCode/cmView.vue b/src/views/editCode/cmView.vue
index 44007fd4f..998bab240 100644
--- a/src/views/editCode/cmView.vue
+++ b/src/views/editCode/cmView.vue
@@ -65,7 +65,7 @@ import {
} from "@codemirror/commands";
import { closeBrackets, autocompletion } from "@codemirror/autocomplete";
import { Compartment, EditorState } from "@codemirror/state";
-import { hyperLink } from "@uiw/codemirror-extensions-hyper-link";
+import { hyperLink } from "@/views/editCode/link";
import { indentationMarkers } from "@replit/codemirror-indentation-markers";
import useV3Clipboard from "vue-clipboard3";
import copyimg from "@/views/editCode/svg/copy.svg";
diff --git a/src/views/editCode/link/README.md b/src/views/editCode/link/README.md
new file mode 100644
index 000000000..3d0efc28a
--- /dev/null
+++ b/src/views/editCode/link/README.md
@@ -0,0 +1,123 @@
+
+
+# Hyper link Extensions
+
+
+
+[![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-048754?logo=buymeacoffee)](https://jaywcjlove.github.io/#/sponsor)
+[![npm version](https://img.shields.io/npm/v/@uiw/codemirror-extensions-hyper-link.svg)](https://www.npmjs.com/package/@uiw/codemirror-extensions-hyper-link)
+
+Hyper link Extensions for CodeMirror6.
+
+## Install
+
+```bash
+npm install @uiw/codemirror-extensions-hyper-link --save
+```
+
+```jsx
+import { hyperLink, hyperLinkExtension, hyperLinkStyle } from '@uiw/codemirror-extensions-hyper-link';
+```
+
+## Usage
+
+```jsx
+import CodeMirror from '@uiw/react-codemirror';
+import { hyperLink } from '@uiw/codemirror-extensions-hyper-link';
+
+const code = `https://uiwjs.github.io/react-codemirror`;
+
+function App() {
+ return ;
+}
+export default App;
+```
+
+```js
+import { EditorView } from '@codemirror/view';
+import { EditorState } from '@codemirror/state';
+import { hyperLink } from '@uiw/codemirror-extensions-hyper-link';
+
+const code = `https://uiwjs.github.io/react-codemirror`;
+
+const state = EditorState.create({
+ doc: code,
+ extensions: [hyperLink],
+});
+
+const view = new EditorView({
+ parent: document.querySelector('#editor'),
+ state,
+});
+```
+
+Custom match content
+
+```tsx
+import { EditorView } from '@codemirror/view';
+import { EditorState } from '@codemirror/state';
+import { hyperLinkExtension, hyperLinkStyle } from '@uiw/codemirror-extensions-hyper-link';
+
+const code = `Hyper Link\n====`;
+
+export const hyperLink: Extension = [
+ hyperLinkExtension({
+ regexp: /Hyper/gi,
+ match: { Hyper: 'https://google.com' },
+ handle: (value, input, from, to) => {
+ if (value === 'Hyper') return 'https://google.com';
+ return value;
+ },
+ }),
+ hyperLinkStyle,
+];
+
+const state = EditorState.create({
+ doc: code,
+ extensions: [hyperLink],
+});
+
+const view = new EditorView({
+ parent: document.querySelector('#editor'),
+ state,
+});
+```
+
+## API
+
+```ts
+import { ViewPlugin, DecorationSet, MatchDecorator, ViewUpdate } from '@codemirror/view';
+import { Extension } from '@codemirror/state';
+export interface HyperLinkState {
+ at: number;
+ url: string;
+ anchor: HyperLinkExtensionOptions['anchor'];
+}
+export type HyperLinkExtensionOptions = {
+ regexp?: RegExp;
+ match?: Record;
+ handle?: (value: string, input: string, from: number, to: number) => string;
+ anchor?: (dom: HTMLAnchorElement) => HTMLAnchorElement;
+};
+export declare function hyperLinkExtension({ regexp, match, handle, anchor }?: HyperLinkExtensionOptions): ViewPlugin<{
+ decorator?: MatchDecorator | undefined;
+ decorations: DecorationSet;
+ update(update: ViewUpdate): void;
+}>;
+export declare const hyperLinkStyle: Extension;
+export declare const hyperLink: Extension;
+```
+
+## Contributors
+
+As always, thanks to our amazing contributors!
+
+
+
+
+
+Made with [github-action-contributors](https://github.com/jaywcjlove/github-action-contributors).
+
+## License
+
+Licensed under the MIT License.
diff --git a/src/views/editCode/link/cjs/index.d.ts b/src/views/editCode/link/cjs/index.d.ts
new file mode 100644
index 000000000..c36baf995
--- /dev/null
+++ b/src/views/editCode/link/cjs/index.d.ts
@@ -0,0 +1,20 @@
+import { ViewPlugin, DecorationSet, MatchDecorator, ViewUpdate } from '@codemirror/view';
+import { Extension } from '@codemirror/state';
+export interface HyperLinkState {
+ at: number;
+ url: string;
+ anchor: HyperLinkExtensionOptions['anchor'];
+}
+export type HyperLinkExtensionOptions = {
+ regexp?: RegExp;
+ match?: Record;
+ handle?: (value: string, input: string, from: number, to: number) => string;
+ anchor?: (dom: HTMLAnchorElement) => HTMLAnchorElement;
+};
+export declare function hyperLinkExtension({ regexp, match, handle, anchor }?: HyperLinkExtensionOptions): ViewPlugin<{
+ decorator?: MatchDecorator | undefined;
+ decorations: DecorationSet;
+ update(update: ViewUpdate): void;
+}>;
+export declare const hyperLinkStyle: Extension;
+export declare const hyperLink: Extension;
diff --git a/src/views/editCode/link/cjs/index.js b/src/views/editCode/link/cjs/index.js
new file mode 100644
index 000000000..c8d3f9f9d
--- /dev/null
+++ b/src/views/editCode/link/cjs/index.js
@@ -0,0 +1,144 @@
+"use strict";
+
+var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault")["default"];
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.hyperLink = void 0;
+exports.hyperLinkExtension = hyperLinkExtension;
+exports.hyperLinkStyle = void 0;
+var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
+var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
+var _callSuper2 = _interopRequireDefault(require("@babel/runtime/helpers/callSuper"));
+var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized"));
+var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
+var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
+var _view = require("@codemirror/view");
+var pathStr = "";
+var defaultRegexp = /\b((?:https?|ftp):\/\/[^\s/$.?#,].[^\s,]*)\b/gi;
+var HyperLinkIcon = /*#__PURE__*/function (_WidgetType) {
+ (0, _inherits2["default"])(HyperLinkIcon, _WidgetType);
+ function HyperLinkIcon(state) {
+ var _this;
+ (0, _classCallCheck2["default"])(this, HyperLinkIcon);
+ _this = (0, _callSuper2["default"])(this, HyperLinkIcon);
+ (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "state", void 0);
+ _this.state = state;
+ return _this;
+ }
+ (0, _createClass2["default"])(HyperLinkIcon, [{
+ key: "eq",
+ value: function eq(other) {
+ return this.state.url === other.state.url && this.state.at === other.state.at;
+ }
+ }, {
+ key: "toDOM",
+ value: function toDOM() {
+ var wrapper = document.createElement('a');
+ wrapper.href = this.state.url;
+ wrapper.target = '_blank';
+ wrapper.innerHTML = pathStr;
+ wrapper.className = 'cm-hyper-link-icon';
+ wrapper.rel = 'nofollow';
+ var anchor = this.state.anchor && this.state.anchor(wrapper);
+ return anchor || wrapper;
+ }
+ }]);
+ return HyperLinkIcon;
+}(_view.WidgetType);
+function hyperLinkDecorations(view, anchor) {
+ var widgets = [];
+ var doc = view.state.doc;
+ var match;
+ while ((match = defaultRegexp.exec(doc.toString())) !== null) {
+ var _from = match.index;
+ var _to = _from + match[0].length;
+ var widget = _view.Decoration.widget({
+ widget: new HyperLinkIcon({
+ at: _to,
+ url: match[0],
+ anchor: anchor
+ }),
+ side: 1
+ });
+ widgets.push(widget.range(_to));
+ }
+ return _view.Decoration.set(widgets);
+}
+var linkDecorator = function linkDecorator(regexp, matchData, matchFn, anchor) {
+ return new _view.MatchDecorator({
+ regexp: regexp || defaultRegexp,
+ decorate: function decorate(add, from, to, match, view) {
+ var url = match[0];
+ var urlStr = matchFn && typeof matchFn === 'function' ? matchFn(url, match.input, from, to) : url;
+ if (matchData && matchData[url]) {
+ urlStr = matchData[url];
+ }
+ var start = to,
+ end = to;
+ var linkIcon = new HyperLinkIcon({
+ at: start,
+ url: urlStr,
+ anchor: anchor
+ });
+ add(from, to, _view.Decoration.mark({
+ "class": 'cm-hyper-link-underline'
+ }));
+ add(start, end, _view.Decoration.widget({
+ widget: linkIcon,
+ side: 1
+ }));
+ }
+ });
+};
+function hyperLinkExtension() {
+ var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
+ regexp = _ref.regexp,
+ match = _ref.match,
+ handle = _ref.handle,
+ anchor = _ref.anchor;
+ return _view.ViewPlugin.fromClass( /*#__PURE__*/function () {
+ function HyperLinkView(view) {
+ (0, _classCallCheck2["default"])(this, HyperLinkView);
+ (0, _defineProperty2["default"])(this, "decorator", void 0);
+ (0, _defineProperty2["default"])(this, "decorations", void 0);
+ if (regexp) {
+ this.decorator = linkDecorator(regexp, match, handle, anchor);
+ this.decorations = this.decorator.createDeco(view);
+ } else {
+ this.decorations = hyperLinkDecorations(view, anchor);
+ }
+ }
+ (0, _createClass2["default"])(HyperLinkView, [{
+ key: "update",
+ value: function update(_update) {
+ if (_update.docChanged || _update.viewportChanged) {
+ if (regexp && this.decorator) {
+ this.decorations = this.decorator.updateDeco(_update, this.decorations);
+ } else {
+ this.decorations = hyperLinkDecorations(_update.view, anchor);
+ }
+ }
+ }
+ }]);
+ return HyperLinkView;
+ }(), {
+ decorations: function decorations(v) {
+ return v.decorations;
+ }
+ });
+}
+var hyperLinkStyle = exports.hyperLinkStyle = _view.EditorView.baseTheme({
+ '.cm-hyper-link-icon': {
+ display: 'inline-block',
+ verticalAlign: 'middle',
+ marginLeft: '0.2ch'
+ },
+ '.cm-hyper-link-icon svg': {
+ display: 'block'
+ },
+ '.cm-hyper-link-underline': {
+ textDecoration: 'underline'
+ }
+});
+var hyperLink = exports.hyperLink = [hyperLinkExtension(), hyperLinkStyle];
\ No newline at end of file
diff --git a/src/views/editCode/link/esm/index.d.ts b/src/views/editCode/link/esm/index.d.ts
new file mode 100644
index 000000000..c36baf995
--- /dev/null
+++ b/src/views/editCode/link/esm/index.d.ts
@@ -0,0 +1,20 @@
+import { ViewPlugin, DecorationSet, MatchDecorator, ViewUpdate } from '@codemirror/view';
+import { Extension } from '@codemirror/state';
+export interface HyperLinkState {
+ at: number;
+ url: string;
+ anchor: HyperLinkExtensionOptions['anchor'];
+}
+export type HyperLinkExtensionOptions = {
+ regexp?: RegExp;
+ match?: Record;
+ handle?: (value: string, input: string, from: number, to: number) => string;
+ anchor?: (dom: HTMLAnchorElement) => HTMLAnchorElement;
+};
+export declare function hyperLinkExtension({ regexp, match, handle, anchor }?: HyperLinkExtensionOptions): ViewPlugin<{
+ decorator?: MatchDecorator | undefined;
+ decorations: DecorationSet;
+ update(update: ViewUpdate): void;
+}>;
+export declare const hyperLinkStyle: Extension;
+export declare const hyperLink: Extension;
diff --git a/src/views/editCode/link/esm/index.js b/src/views/editCode/link/esm/index.js
new file mode 100644
index 000000000..78db0975e
--- /dev/null
+++ b/src/views/editCode/link/esm/index.js
@@ -0,0 +1,111 @@
+import { ViewPlugin, EditorView, Decoration, MatchDecorator, WidgetType } from '@codemirror/view';
+var pathStr = "";
+var defaultRegexp = /\b((?:https?|ftp):\/\/[^\s/$.?#,].[^\s,]*)\b/gi;
+class HyperLinkIcon extends WidgetType {
+ constructor(state) {
+ super();
+ this.state = void 0;
+ this.state = state;
+ }
+ eq(other) {
+ return this.state.url === other.state.url && this.state.at === other.state.at;
+ }
+ toDOM() {
+ var wrapper = document.createElement('a');
+ wrapper.href = this.state.url;
+ wrapper.target = '_blank';
+ wrapper.innerHTML = pathStr;
+ wrapper.className = 'cm-hyper-link-icon';
+ wrapper.rel = 'nofollow';
+ var anchor = this.state.anchor && this.state.anchor(wrapper);
+ return anchor || wrapper;
+ }
+}
+function hyperLinkDecorations(view, anchor) {
+ var widgets = [];
+ var doc = view.state.doc;
+ var match;
+ while ((match = defaultRegexp.exec(doc.toString())) !== null) {
+ var _from = match.index;
+ var _to = _from + match[0].length;
+ var widget = Decoration.widget({
+ widget: new HyperLinkIcon({
+ at: _to,
+ url: match[0],
+ anchor
+ }),
+ side: 1
+ });
+ widgets.push(widget.range(_to));
+ }
+ return Decoration.set(widgets);
+}
+var linkDecorator = (regexp, matchData, matchFn, anchor) => new MatchDecorator({
+ regexp: regexp || defaultRegexp,
+ decorate: (add, from, to, match, view) => {
+ var url = match[0];
+ var urlStr = matchFn && typeof matchFn === 'function' ? matchFn(url, match.input, from, to) : url;
+ if (matchData && matchData[url]) {
+ urlStr = matchData[url];
+ }
+ var start = to,
+ end = to;
+ var linkIcon = new HyperLinkIcon({
+ at: start,
+ url: urlStr,
+ anchor
+ });
+ add(from, to, Decoration.mark({
+ class: 'cm-hyper-link-underline'
+ }));
+ add(start, end, Decoration.widget({
+ widget: linkIcon,
+ side: 1
+ }));
+ }
+});
+export function hyperLinkExtension(_temp) {
+ var {
+ regexp,
+ match,
+ handle,
+ anchor
+ } = _temp === void 0 ? {} : _temp;
+ return ViewPlugin.fromClass(class HyperLinkView {
+ constructor(view) {
+ this.decorator = void 0;
+ this.decorations = void 0;
+ if (regexp) {
+ this.decorator = linkDecorator(regexp, match, handle, anchor);
+ this.decorations = this.decorator.createDeco(view);
+ } else {
+ this.decorations = hyperLinkDecorations(view, anchor);
+ }
+ }
+ update(update) {
+ if (update.docChanged || update.viewportChanged) {
+ if (regexp && this.decorator) {
+ this.decorations = this.decorator.updateDeco(update, this.decorations);
+ } else {
+ this.decorations = hyperLinkDecorations(update.view, anchor);
+ }
+ }
+ }
+ }, {
+ decorations: v => v.decorations
+ });
+}
+export var hyperLinkStyle = EditorView.baseTheme({
+ '.cm-hyper-link-icon': {
+ display: 'inline-block',
+ verticalAlign: 'middle',
+ marginLeft: '0.2ch'
+ },
+ '.cm-hyper-link-icon svg': {
+ display: 'block'
+ },
+ '.cm-hyper-link-underline': {
+ textDecoration: 'underline'
+ }
+});
+export var hyperLink = [hyperLinkExtension(), hyperLinkStyle];
\ No newline at end of file
diff --git a/src/views/editCode/link/index.ts b/src/views/editCode/link/index.ts
new file mode 100644
index 000000000..b60799416
--- /dev/null
+++ b/src/views/editCode/link/index.ts
@@ -0,0 +1,136 @@
+import {
+ ViewPlugin,
+ EditorView,
+ Decoration,
+ DecorationSet,
+ MatchDecorator,
+ WidgetType,
+ ViewUpdate,
+} from '@codemirror/view';
+import { Extension, Range } from '@codemirror/state';
+
+const pathStr = ``;
+const defaultRegexp = /\b((?:https?|ftp):\/\/[^\s/$.?#,].[^\s,]*)\b/gi;
+
+export interface HyperLinkState {
+ at: number;
+ url: string;
+ anchor: HyperLinkExtensionOptions['anchor'];
+}
+
+class HyperLinkIcon extends WidgetType {
+ private readonly state: HyperLinkState;
+ constructor(state: HyperLinkState) {
+ super();
+ this.state = state;
+ }
+ eq(other: HyperLinkIcon) {
+ return this.state.url === other.state.url && this.state.at === other.state.at;
+ }
+ toDOM() {
+ const wrapper = document.createElement('a');
+ wrapper.href = this.state.url;
+ wrapper.target = '_blank';
+ wrapper.innerHTML = pathStr;
+ wrapper.className = 'cm-hyper-link-icon';
+ wrapper.rel = 'nofollow';
+ const anchor = this.state.anchor && this.state.anchor(wrapper);
+ return anchor || wrapper;
+ }
+}
+
+function hyperLinkDecorations(view: EditorView, anchor?: HyperLinkExtensionOptions['anchor']) {
+ const widgets: Array> = [];
+ const doc = view.state.doc;
+ let match;
+
+ while ((match = defaultRegexp.exec(doc.toString())) !== null) {
+ const from = match.index;
+ const to = from + match[0].length;
+ const widget = Decoration.widget({
+ widget: new HyperLinkIcon({
+ at: to,
+ url: match[0],
+ anchor,
+ }),
+ side: 1,
+ });
+ widgets.push(widget.range(to));
+ }
+
+ return Decoration.set(widgets);
+}
+
+const linkDecorator = (
+ regexp?: RegExp,
+ matchData?: Record,
+ matchFn?: (str: string, input: string, from: number, to: number) => string,
+ anchor?: HyperLinkExtensionOptions['anchor'],
+) =>
+ new MatchDecorator({
+ regexp: regexp || defaultRegexp,
+ decorate: (add, from, to, match, view) => {
+ const url = match[0];
+ let urlStr = matchFn && typeof matchFn === 'function' ? matchFn(url, match.input, from, to) : url;
+ if (matchData && matchData[url]) {
+ urlStr = matchData[url];
+ }
+ const start = to,
+ end = to;
+ const linkIcon = new HyperLinkIcon({ at: start, url: urlStr, anchor });
+ add(from, to, Decoration.mark({ class: 'cm-hyper-link-underline' }));
+ add(start, end, Decoration.widget({ widget: linkIcon, side: 1 }));
+ },
+ });
+
+export type HyperLinkExtensionOptions = {
+ regexp?: RegExp;
+ match?: Record;
+ handle?: (value: string, input: string, from: number, to: number) => string;
+ anchor?: (dom: HTMLAnchorElement) => HTMLAnchorElement;
+};
+
+export function hyperLinkExtension({ regexp, match, handle, anchor }: HyperLinkExtensionOptions = {}) {
+ return ViewPlugin.fromClass(
+ class HyperLinkView {
+ decorator?: MatchDecorator;
+ decorations: DecorationSet;
+ constructor(view: EditorView) {
+ if (regexp) {
+ this.decorator = linkDecorator(regexp, match, handle, anchor);
+ this.decorations = this.decorator.createDeco(view);
+ } else {
+ this.decorations = hyperLinkDecorations(view, anchor);
+ }
+ }
+ update(update: ViewUpdate) {
+ if (update.docChanged || update.viewportChanged) {
+ if (regexp && this.decorator) {
+ this.decorations = this.decorator.updateDeco(update, this.decorations);
+ } else {
+ this.decorations = hyperLinkDecorations(update.view, anchor);
+ }
+ }
+ }
+ },
+ {
+ decorations: (v) => v.decorations,
+ },
+ );
+}
+
+export const hyperLinkStyle = EditorView.baseTheme({
+ '.cm-hyper-link-icon': {
+ display: 'inline-block',
+ verticalAlign: 'middle',
+ marginLeft: '0.2ch',
+ },
+ '.cm-hyper-link-icon svg': {
+ display: 'block',
+ },
+ '.cm-hyper-link-underline': {
+ textDecoration: 'underline',
+ },
+});
+
+export const hyperLink: Extension = [hyperLinkExtension(), hyperLinkStyle];
diff --git a/src/views/editCode/link/package.json b/src/views/editCode/link/package.json
new file mode 100644
index 000000000..8ae14834d
--- /dev/null
+++ b/src/views/editCode/link/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "@uiw/codemirror-extensions-hyper-link",
+ "version": "4.21.24",
+ "description": "Hyper link Extensions for CodeMirror6.",
+ "homepage": "https://uiwjs.github.io/react-codemirror/#/extensions/hyper-link",
+ "funding": "https://jaywcjlove.github.io/#/sponsor",
+ "author": "kenny wong ",
+ "license": "MIT",
+ "main": "./cjs/index.js",
+ "module": "./esm/index.js",
+ "scripts": {
+ "watch": "tsbb watch src/*.ts --use-babel",
+ "build": "tsbb build src/*.ts --use-babel"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/uiwjs/react-codemirror.git"
+ },
+ "files": [
+ "src",
+ "esm",
+ "cjs"
+ ],
+ "peerDependencies": {
+ "@codemirror/state": ">=6.0.0",
+ "@codemirror/view": ">=6.0.0"
+ },
+ "devDependencies": {
+ "@codemirror/state": "^6.1.0",
+ "@codemirror/view": "^6.0.0"
+ },
+ "keywords": [
+ "codemirror",
+ "codemirror6",
+ "link",
+ "url",
+ "hyper-link",
+ "extensions",
+ "ide",
+ "code"
+ ],
+ "jest": {
+ "coverageReporters": [
+ "lcov",
+ "json-summary"
+ ]
+ }
+}
diff --git a/src/views/editCode/link/src/index.ts b/src/views/editCode/link/src/index.ts
new file mode 100644
index 000000000..b60799416
--- /dev/null
+++ b/src/views/editCode/link/src/index.ts
@@ -0,0 +1,136 @@
+import {
+ ViewPlugin,
+ EditorView,
+ Decoration,
+ DecorationSet,
+ MatchDecorator,
+ WidgetType,
+ ViewUpdate,
+} from '@codemirror/view';
+import { Extension, Range } from '@codemirror/state';
+
+const pathStr = ``;
+const defaultRegexp = /\b((?:https?|ftp):\/\/[^\s/$.?#,].[^\s,]*)\b/gi;
+
+export interface HyperLinkState {
+ at: number;
+ url: string;
+ anchor: HyperLinkExtensionOptions['anchor'];
+}
+
+class HyperLinkIcon extends WidgetType {
+ private readonly state: HyperLinkState;
+ constructor(state: HyperLinkState) {
+ super();
+ this.state = state;
+ }
+ eq(other: HyperLinkIcon) {
+ return this.state.url === other.state.url && this.state.at === other.state.at;
+ }
+ toDOM() {
+ const wrapper = document.createElement('a');
+ wrapper.href = this.state.url;
+ wrapper.target = '_blank';
+ wrapper.innerHTML = pathStr;
+ wrapper.className = 'cm-hyper-link-icon';
+ wrapper.rel = 'nofollow';
+ const anchor = this.state.anchor && this.state.anchor(wrapper);
+ return anchor || wrapper;
+ }
+}
+
+function hyperLinkDecorations(view: EditorView, anchor?: HyperLinkExtensionOptions['anchor']) {
+ const widgets: Array> = [];
+ const doc = view.state.doc;
+ let match;
+
+ while ((match = defaultRegexp.exec(doc.toString())) !== null) {
+ const from = match.index;
+ const to = from + match[0].length;
+ const widget = Decoration.widget({
+ widget: new HyperLinkIcon({
+ at: to,
+ url: match[0],
+ anchor,
+ }),
+ side: 1,
+ });
+ widgets.push(widget.range(to));
+ }
+
+ return Decoration.set(widgets);
+}
+
+const linkDecorator = (
+ regexp?: RegExp,
+ matchData?: Record,
+ matchFn?: (str: string, input: string, from: number, to: number) => string,
+ anchor?: HyperLinkExtensionOptions['anchor'],
+) =>
+ new MatchDecorator({
+ regexp: regexp || defaultRegexp,
+ decorate: (add, from, to, match, view) => {
+ const url = match[0];
+ let urlStr = matchFn && typeof matchFn === 'function' ? matchFn(url, match.input, from, to) : url;
+ if (matchData && matchData[url]) {
+ urlStr = matchData[url];
+ }
+ const start = to,
+ end = to;
+ const linkIcon = new HyperLinkIcon({ at: start, url: urlStr, anchor });
+ add(from, to, Decoration.mark({ class: 'cm-hyper-link-underline' }));
+ add(start, end, Decoration.widget({ widget: linkIcon, side: 1 }));
+ },
+ });
+
+export type HyperLinkExtensionOptions = {
+ regexp?: RegExp;
+ match?: Record;
+ handle?: (value: string, input: string, from: number, to: number) => string;
+ anchor?: (dom: HTMLAnchorElement) => HTMLAnchorElement;
+};
+
+export function hyperLinkExtension({ regexp, match, handle, anchor }: HyperLinkExtensionOptions = {}) {
+ return ViewPlugin.fromClass(
+ class HyperLinkView {
+ decorator?: MatchDecorator;
+ decorations: DecorationSet;
+ constructor(view: EditorView) {
+ if (regexp) {
+ this.decorator = linkDecorator(regexp, match, handle, anchor);
+ this.decorations = this.decorator.createDeco(view);
+ } else {
+ this.decorations = hyperLinkDecorations(view, anchor);
+ }
+ }
+ update(update: ViewUpdate) {
+ if (update.docChanged || update.viewportChanged) {
+ if (regexp && this.decorator) {
+ this.decorations = this.decorator.updateDeco(update, this.decorations);
+ } else {
+ this.decorations = hyperLinkDecorations(update.view, anchor);
+ }
+ }
+ }
+ },
+ {
+ decorations: (v) => v.decorations,
+ },
+ );
+}
+
+export const hyperLinkStyle = EditorView.baseTheme({
+ '.cm-hyper-link-icon': {
+ display: 'inline-block',
+ verticalAlign: 'middle',
+ marginLeft: '0.2ch',
+ },
+ '.cm-hyper-link-icon svg': {
+ display: 'block',
+ },
+ '.cm-hyper-link-underline': {
+ textDecoration: 'underline',
+ },
+});
+
+export const hyperLink: Extension = [hyperLinkExtension(), hyperLinkStyle];