xt-plugin-editor 1.0.0 → 1.3.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.
- package/README.md +1 -1
- package/fesm2022/xt-plugin-editor.mjs +770 -0
- package/fesm2022/xt-plugin-editor.mjs.map +1 -0
- package/package.json +37 -64
- package/types/xt-plugin-editor.d.ts +87 -0
- package/.editorconfig +0 -17
- package/.postcssrc.json +0 -1
- package/.prettierrc +0 -12
- package/.rush/temp/chunked-rush-logs/xt-plugin-editor.build.chunks.jsonl +0 -23
- package/.rush/temp/chunked-rush-logs/xt-plugin-editor.test.chunks.jsonl +0 -50
- package/.rush/temp/shrinkwrap-deps.json +0 -1200
- package/CHANGELOG.md +0 -2
- package/angular.json +0 -148
- package/projects/editor/README.md +0 -13
- package/projects/editor/ng-package.json +0 -7
- package/projects/editor/package.json +0 -43
- package/projects/editor/src/lib/editor/editor.component.css +0 -15
- package/projects/editor/src/lib/editor/editor.component.html +0 -29
- package/projects/editor/src/lib/editor/editor.component.spec.ts +0 -53
- package/projects/editor/src/lib/editor/editor.component.ts +0 -202
- package/projects/editor/src/lib/markdown/inline-markdown-pipe.spec.ts +0 -25
- package/projects/editor/src/lib/markdown/inline-markdown-pipe.ts +0 -30
- package/projects/editor/src/lib/markdown/markdown-pipe.spec.ts +0 -29
- package/projects/editor/src/lib/markdown/markdown-pipe.ts +0 -21
- package/projects/editor/src/lib/prose-mirror/basic-setup.ts +0 -655
- package/projects/editor/src/lib/register.ts +0 -33
- package/projects/editor/src/public-api.ts +0 -7
- package/projects/editor/tsconfig.lib.json +0 -13
- package/projects/editor/tsconfig.lib.prod.json +0 -11
- package/projects/editor/tsconfig.spec.json +0 -10
- package/projects/editor-plugin/federation.config.js +0 -33
- package/projects/editor-plugin/public/favicon.ico +0 -0
- package/projects/editor-plugin/src/app/app.component.css +0 -0
- package/projects/editor-plugin/src/app/app.component.html +0 -3
- package/projects/editor-plugin/src/app/app.component.spec.ts +0 -34
- package/projects/editor-plugin/src/app/app.component.ts +0 -23
- package/projects/editor-plugin/src/app/app.config.ts +0 -22
- package/projects/editor-plugin/src/app/app.routes.ts +0 -6
- package/projects/editor-plugin/src/app/editor-test-component/editor-test.component.css +0 -4
- package/projects/editor-plugin/src/app/editor-test-component/editor-test.component.html +0 -19
- package/projects/editor-plugin/src/app/editor-test-component/editor-test.component.spec.ts +0 -33
- package/projects/editor-plugin/src/app/editor-test-component/editor-test.component.ts +0 -67
- package/projects/editor-plugin/src/bootstrap.ts +0 -6
- package/projects/editor-plugin/src/index.html +0 -13
- package/projects/editor-plugin/src/main.ts +0 -6
- package/projects/editor-plugin/src/styles.css +0 -14
- package/projects/editor-plugin/tsconfig.app.json +0 -11
- package/projects/editor-plugin/tsconfig.federation.json +0 -13
- package/projects/editor-plugin/tsconfig.spec.json +0 -10
- package/rush-logs/xt-plugin-editor.build.error.log +0 -8
- package/rush-logs/xt-plugin-editor.build.log +0 -23
- package/rush-logs/xt-plugin-editor.test.error.log +0 -1
- package/rush-logs/xt-plugin-editor.test.log +0 -50
- package/tsconfig.json +0 -33
package/CHANGELOG.md
DELETED
package/angular.json
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
|
3
|
-
"version": 1,
|
|
4
|
-
"cli": {
|
|
5
|
-
"cache": {
|
|
6
|
-
"enabled": true
|
|
7
|
-
},
|
|
8
|
-
"analytics": "853114a2-918e-4fdd-a034-01da3328a1fa"
|
|
9
|
-
},
|
|
10
|
-
"newProjectRoot": "projects",
|
|
11
|
-
"projects": {
|
|
12
|
-
"editor": {
|
|
13
|
-
"projectType": "library",
|
|
14
|
-
"root": "projects/editor",
|
|
15
|
-
"sourceRoot": "projects/editor/src",
|
|
16
|
-
"prefix": "lib",
|
|
17
|
-
"architect": {
|
|
18
|
-
"build": {
|
|
19
|
-
"builder": "@angular/build:ng-packagr",
|
|
20
|
-
"configurations": {
|
|
21
|
-
"production": {
|
|
22
|
-
"tsConfig": "projects/editor/tsconfig.lib.prod.json"
|
|
23
|
-
},
|
|
24
|
-
"development": {
|
|
25
|
-
"tsConfig": "projects/editor/tsconfig.lib.json"
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
"defaultConfiguration": "development"
|
|
29
|
-
},
|
|
30
|
-
"test": {
|
|
31
|
-
"builder": "@angular/build:unit-test",
|
|
32
|
-
"options": {
|
|
33
|
-
"tsConfig": "projects/editor/tsconfig.spec.json"
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
"editor-plugin": {
|
|
39
|
-
"projectType": "application",
|
|
40
|
-
"schematics": {},
|
|
41
|
-
"root": "projects/editor-plugin",
|
|
42
|
-
"sourceRoot": "projects/editor-plugin/src",
|
|
43
|
-
"prefix": "app",
|
|
44
|
-
"architect": {
|
|
45
|
-
"build": {
|
|
46
|
-
"builder": "@angular-architects/native-federation:build",
|
|
47
|
-
"options": {},
|
|
48
|
-
"configurations": {
|
|
49
|
-
"production": {
|
|
50
|
-
"target": "editor-plugin:esbuild:production"
|
|
51
|
-
},
|
|
52
|
-
"development": {
|
|
53
|
-
"target": "editor-plugin:esbuild:development",
|
|
54
|
-
"dev": true
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
"defaultConfiguration": "production"
|
|
58
|
-
},
|
|
59
|
-
"serve": {
|
|
60
|
-
"builder": "@angular-architects/native-federation:build",
|
|
61
|
-
"options": {
|
|
62
|
-
"target": "editor-plugin:serve-original:development",
|
|
63
|
-
"rebuildDelay": 500,
|
|
64
|
-
"dev": true,
|
|
65
|
-
"cacheExternalArtifacts": false,
|
|
66
|
-
"port": 0
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
"test": {
|
|
70
|
-
"builder": "@angular/build:unit-test"
|
|
71
|
-
},
|
|
72
|
-
"esbuild": {
|
|
73
|
-
"builder": "@angular/build:application",
|
|
74
|
-
"options": {
|
|
75
|
-
"outputPath": "dist/xt-editor-plugin",
|
|
76
|
-
"index": "projects/editor-plugin/src/index.html",
|
|
77
|
-
"browser": "projects/editor-plugin/src/main.ts",
|
|
78
|
-
"preserveSymlinks": true,
|
|
79
|
-
"polyfills": [
|
|
80
|
-
"es-module-shims"
|
|
81
|
-
],
|
|
82
|
-
"tsConfig": "projects/editor-plugin/tsconfig.app.json",
|
|
83
|
-
"assets": [
|
|
84
|
-
{
|
|
85
|
-
"glob": "**/*",
|
|
86
|
-
"input": "projects/editor-plugin/public"
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
"glob": "prosemirror-view/style/*.css",
|
|
90
|
-
"input": "node_modules",
|
|
91
|
-
"output": "/"
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
"glob": "prosemirror-menu/style/*.css",
|
|
95
|
-
"input": "node_modules",
|
|
96
|
-
"output": "/"
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
"glob": "prosemirror-gapcursor/style/*.css",
|
|
100
|
-
"input": "node_modules",
|
|
101
|
-
"output": "/"
|
|
102
|
-
}
|
|
103
|
-
],
|
|
104
|
-
"styles": ["projects/editor-plugin/src/styles.css"]
|
|
105
|
-
},
|
|
106
|
-
"configurations": {
|
|
107
|
-
"production": {
|
|
108
|
-
"budgets": [
|
|
109
|
-
{
|
|
110
|
-
"type": "initial",
|
|
111
|
-
"maximumWarning": "1MB",
|
|
112
|
-
"maximumError": "2MB"
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
"type": "anyComponentStyle",
|
|
116
|
-
"maximumWarning": "4kB",
|
|
117
|
-
"maximumError": "8kB"
|
|
118
|
-
}
|
|
119
|
-
],
|
|
120
|
-
"outputHashing": "all"
|
|
121
|
-
},
|
|
122
|
-
"development": {
|
|
123
|
-
"optimization": false,
|
|
124
|
-
"extractLicenses": false,
|
|
125
|
-
"sourceMap": true
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
"defaultConfiguration": "development"
|
|
129
|
-
},
|
|
130
|
-
"serve-original": {
|
|
131
|
-
"builder": "@angular/build:dev-server",
|
|
132
|
-
"configurations": {
|
|
133
|
-
"production": {
|
|
134
|
-
"buildTarget": "editor-plugin:esbuild:production"
|
|
135
|
-
},
|
|
136
|
-
"development": {
|
|
137
|
-
"buildTarget": "editor-plugin:esbuild:development"
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
"defaultConfiguration": "development",
|
|
141
|
-
"options": {
|
|
142
|
-
"port": 4206
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-

|
|
2
|
-
|
|
3
|
-
# Plugin xt-editor
|
|
4
|
-
|
|
5
|
-
This plugin is part of the [ng-xtend framework](https://github.com/dont-code/ng-xtend/blob/main/README.md)
|
|
6
|
-
|
|
7
|
-
It enable [xt-components](https://github.com/dont-code/ng-xtend/tree/main/libs/xt-components) to
|
|
8
|
-
|
|
9
|
-
- Display and edit rich text notes
|
|
10
|
-

|
|
11
|
-
|
|
12
|
-
With it you are sure you can display / edit any type within any xt-components application like [xt-host](https://github.com/dont-code/ng-xtend/tree/main/libs/xt-host).
|
|
13
|
-
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "xt-plugin-editor",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"repository": {
|
|
5
|
-
"url": "https://github.com/dont-code/ng-xtend.git"
|
|
6
|
-
},
|
|
7
|
-
"peerDependencies": {
|
|
8
|
-
"@angular/animations": "^21.0.0",
|
|
9
|
-
"@angular/common": "^21.0.0",
|
|
10
|
-
"@angular/compiler": "^21.0.0",
|
|
11
|
-
"@angular/core": "^21.0.0",
|
|
12
|
-
"@angular/forms": "^21.0.0",
|
|
13
|
-
"@angular/platform-browser": "^21.0.0",
|
|
14
|
-
"@angular/platform-browser-dynamic": "^21.0.0",
|
|
15
|
-
"@angular/router": "^21.0.0",
|
|
16
|
-
"xt-components": "^1.0.0",
|
|
17
|
-
"rxjs": "^7.8.2",
|
|
18
|
-
"primeng": "^21.0.0",
|
|
19
|
-
"primeicons": "^7.0.0",
|
|
20
|
-
"prosemirror-model": "^1.25.11",
|
|
21
|
-
"prosemirror-transform": "^1.12.0",
|
|
22
|
-
"prosemirror-state": "^1.4.4",
|
|
23
|
-
"prosemirror-view": "^1.42.2",
|
|
24
|
-
"prosemirror-keymap": "^1.2.3",
|
|
25
|
-
"prosemirror-commands": "^1.0.0",
|
|
26
|
-
"prosemirror-schema-basic": "^1.2.4",
|
|
27
|
-
"prosemirror-markdown": "^1.13.5",
|
|
28
|
-
"prosemirror-schema-list": "^1.5.1",
|
|
29
|
-
"prosemirror-menu": "^1.3.2",
|
|
30
|
-
"prosemirror-history": "^1.5.0",
|
|
31
|
-
"prosemirror-inputrules": "^1.5.1",
|
|
32
|
-
"prosemirror-dropcursor": "^1.8.3",
|
|
33
|
-
"prosemirror-gapcursor": "^1.4.1",
|
|
34
|
-
"prosemirror-history-v2": "^1.1.3",
|
|
35
|
-
"markdown-it": "^15.0.2",
|
|
36
|
-
"crelt": "~1.0.7",
|
|
37
|
-
"marked": "~18.0.13"
|
|
38
|
-
},
|
|
39
|
-
"dependencies": {
|
|
40
|
-
"tslib": "^2.8.1"
|
|
41
|
-
},
|
|
42
|
-
"sideEffects": false
|
|
43
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
.editorContainer {
|
|
2
|
-
width: 60em;
|
|
3
|
-
border: white solid thin;
|
|
4
|
-
padding: 5px;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/*
|
|
8
|
-
* PrimeNG's tooltip preset (.p-tooltip-text) sets white-space: pre-line,
|
|
9
|
-
* which renders the newline characters emitted by the markdown pipe between
|
|
10
|
-
* block elements as visible blank lines ("large line heights").
|
|
11
|
-
* Normalize white-space here so tooltip and in-page rendering match.
|
|
12
|
-
*/
|
|
13
|
-
.toolTip {
|
|
14
|
-
white-space: normal;
|
|
15
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
@if (isInForm() ) {
|
|
2
|
-
<div class="editorContainer">
|
|
3
|
-
<div #editor [formGroup]="formGroup()">
|
|
4
|
-
</div>
|
|
5
|
-
</div>
|
|
6
|
-
<p-dialog [visible]="displayAttrDialog()" [modal]="true" header="Edit">
|
|
7
|
-
<form [formGroup]="editAttrForm">
|
|
8
|
-
<xt-render [valueType]="editAttrValueType" [formGroup]="editAttrForm" displayMode="FULL_EDITABLE" ></xt-render>
|
|
9
|
-
</form>
|
|
10
|
-
<ng-template #footer>
|
|
11
|
-
<button type="button" pButton severity="secondary" variant="outlined" (click)="displayAttrDialog.set(false)">Cancel</button>
|
|
12
|
-
<button type="button" pButton (click)="updateAttrsFromForm()">Save</button>
|
|
13
|
-
</ng-template>
|
|
14
|
-
</p-dialog>
|
|
15
|
-
} @else {
|
|
16
|
-
@let toDisplay=displayValue();
|
|
17
|
-
@if (toDisplay!=null) {
|
|
18
|
-
@if (context().displayMode=='FULL_VIEW') {
|
|
19
|
-
<div class="displayForm" [innerHTML]="displayValue() | markdown">
|
|
20
|
-
</div>
|
|
21
|
-
} @else {
|
|
22
|
-
<span [pTooltip]="fullText">{{ toDisplay | inlineMarkdown}}</span>
|
|
23
|
-
<ng-template #fullText>
|
|
24
|
-
<div class="toolTip" [innerHTML]="displayValue() | markdown">
|
|
25
|
-
</div>
|
|
26
|
-
</ng-template>
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
-
|
|
3
|
-
import { provideNoopAnimations } from '@angular/platform-browser/animations';
|
|
4
|
-
import { provideZonelessChangeDetection } from '@angular/core';
|
|
5
|
-
import { beforeEach, describe, expect, it } from 'vitest';
|
|
6
|
-
import { HostTestFormComponent, XtBaseContext, XtResolverService } from 'xt-components';
|
|
7
|
-
import { EditorComponent } from './editor.component';
|
|
8
|
-
import { By } from '@angular/platform-browser';
|
|
9
|
-
import { registerDefaultPlugin } from 'xt-plugin-default';
|
|
10
|
-
|
|
11
|
-
describe('EditorComponent', () => {
|
|
12
|
-
let component: EditorComponent;
|
|
13
|
-
let fixture: ComponentFixture<EditorComponent>;
|
|
14
|
-
|
|
15
|
-
beforeEach(async () => {
|
|
16
|
-
await TestBed.configureTestingModule({
|
|
17
|
-
imports: [EditorComponent],
|
|
18
|
-
providers: [provideNoopAnimations(), provideZonelessChangeDetection()]
|
|
19
|
-
|
|
20
|
-
})
|
|
21
|
-
.compileComponents();
|
|
22
|
-
// The editor dialog renders an xt-render for editorImageType/editorLinkType.
|
|
23
|
-
// Those composite types resolve to the generic object component from the default plugin.
|
|
24
|
-
registerDefaultPlugin(TestBed.inject(XtResolverService));
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should create', () => {
|
|
28
|
-
fixture = TestBed.createComponent(EditorComponent);
|
|
29
|
-
const context=new XtBaseContext('FULL_VIEW');
|
|
30
|
-
context.setDisplayValue("My Text to display");
|
|
31
|
-
fixture.componentRef.setInput('context', context);
|
|
32
|
-
component = fixture.componentInstance;
|
|
33
|
-
fixture.detectChanges();
|
|
34
|
-
expect(component).toBeTruthy();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should show editor in form', () => {
|
|
38
|
-
const hostFixture = TestBed.createComponent(HostTestFormComponent);
|
|
39
|
-
hostFixture.componentRef.setInput('type', EditorComponent);
|
|
40
|
-
hostFixture.componentRef.setInput('formDescription', {
|
|
41
|
-
testText: 'My text to edit'
|
|
42
|
-
});
|
|
43
|
-
hostFixture.componentRef.setInput('controlName', 'testText');
|
|
44
|
-
const host = hostFixture.componentInstance;
|
|
45
|
-
expect(host).toBeTruthy();
|
|
46
|
-
hostFixture.detectChanges();
|
|
47
|
-
|
|
48
|
-
const componentDebug = hostFixture.debugElement.query(By.directive(EditorComponent));
|
|
49
|
-
component=componentDebug.componentInstance;
|
|
50
|
-
expect(component).toBeTruthy();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
});
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AfterViewInit,
|
|
3
|
-
ChangeDetectionStrategy,
|
|
4
|
-
Component,
|
|
5
|
-
effect,
|
|
6
|
-
ElementRef, inject,
|
|
7
|
-
input,
|
|
8
|
-
signal,
|
|
9
|
-
viewChild
|
|
10
|
-
} from '@angular/core';
|
|
11
|
-
import { updateFormGroupWithValue, XtContext, XtRenderComponent, XtResolverService, XtSimpleComponent } from 'xt-components';
|
|
12
|
-
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
13
|
-
import { EditorState, NodeSelection, Transaction } from 'prosemirror-state';
|
|
14
|
-
import { EditorView } from 'prosemirror-view';
|
|
15
|
-
import { MarkType, Node, NodeType, Schema } from 'prosemirror-model';
|
|
16
|
-
import { defaultMarkdownParser, defaultMarkdownSerializer, schema } from 'prosemirror-markdown';
|
|
17
|
-
import { basicSetup, canInsert, markActive, openPrompt, TextField } from '../prose-mirror/basic-setup';
|
|
18
|
-
import { MarkdownPipe } from '../markdown/markdown-pipe';
|
|
19
|
-
import { InlineMarkdownPipe } from '../markdown/inline-markdown-pipe';
|
|
20
|
-
import { Tooltip } from 'primeng/tooltip';
|
|
21
|
-
import { Dialog } from 'primeng/dialog';
|
|
22
|
-
import { icons, MenuElement, MenuItem } from 'prosemirror-menu';
|
|
23
|
-
import { type } from '@ngrx/signals';
|
|
24
|
-
import { ButtonDirective } from 'primeng/button';
|
|
25
|
-
import { toggleMark } from 'prosemirror-commands';
|
|
26
|
-
|
|
27
|
-
@Component({
|
|
28
|
-
selector: 'xt-editor-editor',
|
|
29
|
-
imports: [
|
|
30
|
-
ReactiveFormsModule,
|
|
31
|
-
FormsModule,
|
|
32
|
-
MarkdownPipe, InlineMarkdownPipe, Tooltip, Dialog,
|
|
33
|
-
XtRenderComponent, ButtonDirective
|
|
34
|
-
],
|
|
35
|
-
templateUrl: './editor.component.html',
|
|
36
|
-
styleUrl: './editor.component.css',
|
|
37
|
-
changeDetection: ChangeDetectionStrategy.OnPush
|
|
38
|
-
})
|
|
39
|
-
export class EditorComponent extends XtSimpleComponent<any> implements AfterViewInit{
|
|
40
|
-
override context = input.required<XtContext<any>>();
|
|
41
|
-
|
|
42
|
-
protected resolver = inject(XtResolverService);
|
|
43
|
-
|
|
44
|
-
protected editor = viewChild<ElementRef<HTMLDivElement>>('editor');
|
|
45
|
-
|
|
46
|
-
protected sampleJson={ "type": "doc", "content": [ { "type": "heading", "attrs": { "level": 1 }, "content": [ { "type": "text", "text": "Example Text" } ] }, { "type": "paragraph", "content": [ { "type": "text", "text": "s it working ?" } ] } ] };
|
|
47
|
-
protected emptyJson={ "type": "doc", "content": [ { "type": "paragraph"} ] };
|
|
48
|
-
// Mix the nodes from prosemirror-schema-list into the basic schema to create a schema with list support.
|
|
49
|
-
/** mySchema: Schema = new Schema({
|
|
50
|
-
nodes: addListNodes(schema.spec.nodes, "paragraph block*", "block"),
|
|
51
|
-
marks: schema.spec.marks
|
|
52
|
-
});*/
|
|
53
|
-
// Creates the markdown schema
|
|
54
|
-
mySchema = schema;
|
|
55
|
-
|
|
56
|
-
protected view: EditorView|null=null;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Enable dialog box for image or link editing
|
|
60
|
-
* @protected
|
|
61
|
-
*/
|
|
62
|
-
protected displayAttrDialog = signal<boolean>(false);
|
|
63
|
-
protected editAttrForm = new FormGroup({});
|
|
64
|
-
protected editAttrNodeType:NodeType|MarkType=(this.mySchema as any)['image'];
|
|
65
|
-
protected editAttrValueType: string = "editorImageType";
|
|
66
|
-
|
|
67
|
-
constructor() {
|
|
68
|
-
super();
|
|
69
|
-
const imageType=this.resolver.typeResolver.findType('image');
|
|
70
|
-
const linkType=this.resolver.typeResolver.findType('link');
|
|
71
|
-
// Register the type with the image type if supported
|
|
72
|
-
this.resolver.registerTypes({
|
|
73
|
-
editorImageType: {
|
|
74
|
-
children: {
|
|
75
|
-
src: (imageType==null)?"string":"image",
|
|
76
|
-
title: "string",
|
|
77
|
-
alt: "string"
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
editorLinkType: {
|
|
81
|
-
children: {
|
|
82
|
-
href: (linkType==null)?"string":"link",
|
|
83
|
-
title: "string"
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
updateFormGroupWithValue (this.editAttrForm, {}, 'editorImageType', this.resolver.typeResolver);
|
|
89
|
-
|
|
90
|
-
// Setup the editor once the divs are available
|
|
91
|
-
effect(() => {
|
|
92
|
-
const editor=this.editor();
|
|
93
|
-
if( (editor!=null) && (this.view==null)) {
|
|
94
|
-
const value=this.context().formControlValue();
|
|
95
|
-
|
|
96
|
-
const doc = this.toProseMirrorDoc (value);
|
|
97
|
-
this.view = new EditorView(editor.nativeElement, {
|
|
98
|
-
state: EditorState.create({
|
|
99
|
-
doc: doc,
|
|
100
|
-
plugins: basicSetup({schema: this.mySchema, menuContent: {
|
|
101
|
-
image: this.imageMenu(this.mySchema.nodes['image'], this),
|
|
102
|
-
link: this.linkMenu (this.mySchema.marks['link'], this)
|
|
103
|
-
}})
|
|
104
|
-
}),
|
|
105
|
-
dispatchTransaction: this.handleTransactions.bind(this)
|
|
106
|
-
})
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
ngAfterViewInit() {
|
|
113
|
-
/* this.editor.valueChanges.pipe(takeUntil(this.unsubscribe)).subscribe((jsonDoc) => {
|
|
114
|
-
this.handleChange(jsonDoc);
|
|
115
|
-
});*/
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
protected imageMenu(imageNodeType:NodeType, that: EditorComponent):MenuElement {
|
|
119
|
-
return new MenuItem({
|
|
120
|
-
title: "Insert image",
|
|
121
|
-
label: "Image",
|
|
122
|
-
enable(state) {
|
|
123
|
-
return canInsert(state, imageNodeType)
|
|
124
|
-
},
|
|
125
|
-
run(state, _, view) {
|
|
126
|
-
let { from, to } = state.selection, attrs: {src?:string, title?:string, alt?:string} = { };
|
|
127
|
-
if (state.selection instanceof NodeSelection && state.selection.node.type == imageNodeType)
|
|
128
|
-
attrs = state.selection.node.attrs||{};
|
|
129
|
-
if (attrs.alt==null)
|
|
130
|
-
attrs.alt= state.doc.textBetween(from, to, " ");
|
|
131
|
-
that.editAttrValueType="editorImageType";
|
|
132
|
-
updateFormGroupWithValue(that.editAttrForm, attrs, that.editAttrValueType, that.resolver.typeResolver);
|
|
133
|
-
that.editAttrNodeType=imageNodeType;
|
|
134
|
-
that.displayAttrDialog.set(true);
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
protected updateAttrsFromForm() {
|
|
140
|
-
if (this.view != null) {
|
|
141
|
-
if (this.editAttrValueType == "editorImageType"){
|
|
142
|
-
this.view.dispatch(this.view.state.tr.replaceSelectionWith((this.mySchema.nodes['image']).createAndFill(this.editAttrForm.value)!))
|
|
143
|
-
} else if (this.editAttrValueType == "editorLinkType"){
|
|
144
|
-
toggleMark(this.editAttrNodeType as MarkType, this.editAttrForm.value)(this.view.state, this.view.dispatch)
|
|
145
|
-
}
|
|
146
|
-
this.view.focus()
|
|
147
|
-
}
|
|
148
|
-
this.displayAttrDialog.set(false);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
protected linkMenu(linkMarkType:MarkType, that: EditorComponent):MenuElement {
|
|
152
|
-
return new MenuItem({
|
|
153
|
-
title: "Add or Remove link",
|
|
154
|
-
icon: icons["link"],
|
|
155
|
-
active(state) { return markActive(state, linkMarkType) },
|
|
156
|
-
enable(state) { return !state.selection.empty },
|
|
157
|
-
run(state, _, view) {
|
|
158
|
-
let { from, to } = state.selection, attrs: {href?:string, title?:string} = { };
|
|
159
|
-
if (markActive(state, linkMarkType)) {
|
|
160
|
-
toggleMark(linkMarkType)(state, view.dispatch)
|
|
161
|
-
return true
|
|
162
|
-
}
|
|
163
|
-
that.editAttrValueType="editorLinkType";
|
|
164
|
-
updateFormGroupWithValue(that.editAttrForm, attrs, that.editAttrValueType, that.resolver.typeResolver);
|
|
165
|
-
that.editAttrNodeType=linkMarkType;
|
|
166
|
-
that.displayAttrDialog.set(true);
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
protected handleTransactions(tr: Transaction): void {
|
|
173
|
-
if (this.view!=null) {
|
|
174
|
-
const state = this.view.state.apply(tr);
|
|
175
|
-
this.view.updateState(state);
|
|
176
|
-
|
|
177
|
-
if (tr.docChanged) {
|
|
178
|
-
const markdown = defaultMarkdownSerializer.serialize(state.doc);
|
|
179
|
-
this.context().setFormValue(markdown, true);
|
|
180
|
-
}
|
|
181
|
-
} else {
|
|
182
|
-
console.error("Editor: View transaction received while no view defined.",tr.doc.toJSON());
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
private toProseMirrorDoc(value: any): Node {
|
|
188
|
-
if (value == null) {
|
|
189
|
-
return this.mySchema.nodeFromJSON ( this.emptyJson);
|
|
190
|
-
}
|
|
191
|
-
if (typeof value === 'string') {
|
|
192
|
-
// This is a markdown text,
|
|
193
|
-
return defaultMarkdownParser.parse(value);
|
|
194
|
-
}
|
|
195
|
-
return this.mySchema.nodeFromJSON (value);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
protected textJson (text:string){
|
|
199
|
-
return { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": text } ] } ] }
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { InlineMarkdownPipe } from './inline-markdown-pipe';
|
|
3
|
-
|
|
4
|
-
describe('InlineMarkdownPipe', () => {
|
|
5
|
-
it('create an instance', () => {
|
|
6
|
-
const pipe = new InlineMarkdownPipe();
|
|
7
|
-
expect(pipe).toBeTruthy();
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it('extract text from markdown', () => {
|
|
11
|
-
const pipe = new InlineMarkdownPipe();
|
|
12
|
-
const resp = pipe.transform("# Header\n\n**Simple** Text") as string;
|
|
13
|
-
|
|
14
|
-
expect(resp).toBe('Header Simple Text');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('supports long markdown', () => {
|
|
18
|
-
const pipe = new InlineMarkdownPipe();
|
|
19
|
-
const resp = pipe.transform("# Header 1\n\n## Header 1.1\n\n**Simple** Text\n\n- List Item 1\n- List Item 2\n\n## Header 1.2\n\nVery long text that should be clipped.") as string;
|
|
20
|
-
|
|
21
|
-
expect(resp.indexOf('clipped')).toBe(-1);
|
|
22
|
-
expect(resp.indexOf('List Item 1')).not.toBe(-1);
|
|
23
|
-
expect(resp.endsWith('...')).toBeTruthy();
|
|
24
|
-
});
|
|
25
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { Pipe, PipeTransform } from '@angular/core';
|
|
2
|
-
|
|
3
|
-
@Pipe({
|
|
4
|
-
name: 'inlineMarkdown',
|
|
5
|
-
})
|
|
6
|
-
export class InlineMarkdownPipe implements PipeTransform {
|
|
7
|
-
transform(value: unknown, ...args: unknown[]): unknown {
|
|
8
|
-
if( typeof value === 'string' ) {
|
|
9
|
-
let text = value as string;
|
|
10
|
-
if (text.length>100)
|
|
11
|
-
text=text.substring(0,100); // For inline text, we don't want all the text, just the start
|
|
12
|
-
text = text.replace(/!\[[^\]]*\]\([^)]*\)/g, ''); // images
|
|
13
|
-
text = text.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1');// links -> visible text
|
|
14
|
-
text = text.replace(/^#{1,6}\s*/gm, ''); // headers
|
|
15
|
-
text = text.replace(/^\s*[-*+]\s+/gm, ''); // bullet lists
|
|
16
|
-
text = text.replace(/^\s*\d+[.)]\s+/gm, ''); // numbered lists
|
|
17
|
-
text = text.replace(/^\s*[-*]\s+/gm, ''); // bullets that were bold markers
|
|
18
|
-
text = text.replace(/\*\*([^*]*)\*\*/g, '$1'); // bold
|
|
19
|
-
text = text.replace(/\*([^*]*)\*/g, '$1'); // italic *
|
|
20
|
-
text = text.replace(/(?<!\w)_([^_\n]+)_(?!\w)/g, '$1'); // italic _text_
|
|
21
|
-
text = text.replace(/\s*\n\s*/g, ' '); // newlines -> spaces
|
|
22
|
-
text = text.replace(/\s{2,}/g, ' ').trim(); // collapse
|
|
23
|
-
if (text.length>57) {
|
|
24
|
-
return text.substring(0,57)+'...';
|
|
25
|
-
}
|
|
26
|
-
return text;
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { MarkdownPipe } from './markdown-pipe';
|
|
3
|
-
import { TestBed } from '@angular/core/testing';
|
|
4
|
-
import { provideNoopAnimations } from '@angular/platform-browser/animations';
|
|
5
|
-
import { provideZonelessChangeDetection } from '@angular/core';
|
|
6
|
-
|
|
7
|
-
describe('MarkdownPipe', () => {
|
|
8
|
-
beforeEach(async () => {
|
|
9
|
-
await TestBed.configureTestingModule({
|
|
10
|
-
providers: [MarkdownPipe, provideNoopAnimations(), provideZonelessChangeDetection()]
|
|
11
|
-
|
|
12
|
-
})
|
|
13
|
-
.compileComponents();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('create an instance', () => {
|
|
17
|
-
const pipe = TestBed.inject(MarkdownPipe);
|
|
18
|
-
expect(pipe).toBeTruthy();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('Render correctly HTML', () => {
|
|
22
|
-
const pipe = TestBed.inject(MarkdownPipe);
|
|
23
|
-
const response = pipe.transform("# Header\n\nTest **Text**") as string;
|
|
24
|
-
expect(response.indexOf('<h1>')).not.toBe(-1);
|
|
25
|
-
expect(response.indexOf('Test')).not.toBe(-1);
|
|
26
|
-
expect(response.indexOf('<strong>')).not.toBe(-1);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Pipe, PipeTransform, SecurityContext } from '@angular/core';
|
|
2
|
-
import { marked } from 'marked';
|
|
3
|
-
import { DomSanitizer } from '@angular/platform-browser';
|
|
4
|
-
|
|
5
|
-
@Pipe({
|
|
6
|
-
name: 'markdown',
|
|
7
|
-
})
|
|
8
|
-
export class MarkdownPipe implements PipeTransform {
|
|
9
|
-
constructor(protected sanitizer: DomSanitizer) {
|
|
10
|
-
}
|
|
11
|
-
transform(value: unknown, ...args: unknown[]): unknown {
|
|
12
|
-
if( typeof value === 'string' ) {
|
|
13
|
-
const html= marked.parse(value.replace(/^[\u200B\u200C\u200D\u200E\u200F\uFEFF]/,""),
|
|
14
|
-
{
|
|
15
|
-
async:false
|
|
16
|
-
});
|
|
17
|
-
return this.sanitizer.sanitize(SecurityContext.HTML, html);
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
}
|