5htp-core 0.5.1-3 → 0.5.1-5

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "5htp-core",
3
3
  "description": "Convenient TypeScript framework designed for Performance and Productivity.",
4
- "version": "0.5.1-3",
4
+ "version": "0.5.1-5",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp-core.git",
7
7
  "license": "MIT",
@@ -0,0 +1,56 @@
1
+
2
+ import { HeadingNode, HeadingTagType } from '@lexical/rich-text';
3
+ import { AutoLinkNode, LinkNode } from '@lexical/link';
4
+
5
+ export default class ReferenceLinkNode extends LinkNode {
6
+
7
+ public referenceTo?: string;
8
+
9
+ // Adding a static method to register the custom node
10
+ static getType() {
11
+ return 'reference-link';
12
+ }
13
+
14
+ static clone(node) {
15
+ return new ReferenceLinkNode(node.__url, {
16
+ rel: node.__rel,
17
+ target: node.__target,
18
+ title: node.__title
19
+ }, node.__key);
20
+ }
21
+
22
+ // Add a `referenceTo` attribute to the serialized JSON
23
+ static importJSON(serializedNode) {
24
+ const node = new ReferenceLinkNode(serializedNode.url);
25
+ node.referenceTo = serializedNode.referenceTo;
26
+ return node;
27
+ }
28
+
29
+ // Ensure the `referenceTo` attribute is serialized in JSON
30
+ exportJSON() {
31
+ return {
32
+ ...super.exportJSON(),
33
+ type: ReferenceLinkNode.getType(),
34
+ referenceTo: this.referenceTo
35
+ };
36
+ }
37
+
38
+ // Override createDOM to set the `referenceTo` attribute
39
+ createDOM(config) {
40
+ const dom = super.createDOM(config);
41
+ if (this.referenceTo) {
42
+ dom.setAttribute('class', this.referenceTo);
43
+ }
44
+ return dom;
45
+ }
46
+
47
+ // Update the DOM to reflect changes in the `referenceTo` attribute
48
+ updateDOM(prevNode, dom, config) {
49
+ const updated = super.updateDOM(prevNode, dom, config);
50
+ if (this.referenceTo !== prevNode.referenceTo) {
51
+ dom.setAttribute('class', this.referenceTo);
52
+ return true;
53
+ }
54
+ return updated;
55
+ }
56
+ }
@@ -5,14 +5,16 @@
5
5
  position: relative;
6
6
  overflow: hidden;
7
7
 
8
- border-radius: @radius;
9
- border: dashed 3px var(--cLine);
10
- background: @cBgPage + #090909;
11
-
12
- padding: @spacing;
13
- width: 100%;
14
- min-height: 200px;
15
- height: 14.15rem;
8
+ &.box {
9
+ border-radius: @radius;
10
+ border: dashed 3px var(--cLine);
11
+ background: @cBgPage + #090909;
12
+
13
+ padding: @spacing;
14
+ width: 100%;
15
+ min-height: 200px;
16
+ height: 14.15rem;
17
+ }
16
18
 
17
19
  .preview,
18
20
  .indication,
@@ -6,7 +6,7 @@ import React from 'react';
6
6
  import { ComponentChild } from 'preact';
7
7
 
8
8
  // Composants généraux
9
- import Bouton from '@client/components/button';
9
+ import Button, { Props as BtnProps } from '@client/components/button';
10
10
 
11
11
  // Core libs
12
12
  import { InputWrapper } from '../base';
@@ -67,6 +67,7 @@ export type Props = {
67
67
  emptyText?: ComponentChild,
68
68
  className?: string,
69
69
  previewUrl?: string,
70
+ button?: boolean | BtnProps,
70
71
 
71
72
  // Actions
72
73
  onChange: (file: FileToUpload | undefined) => void
@@ -85,11 +86,12 @@ export default (props: Props) => {
85
86
  let {
86
87
  // Input
87
88
  value: file,
88
- className,
89
+ className: customClassName,
89
90
 
90
91
  // Display
91
92
  emptyText = 'Click here to select a File',
92
93
  previewUrl: previewUrlInit,
94
+ button,
93
95
 
94
96
  // Actions
95
97
  onChange
@@ -97,7 +99,13 @@ export default (props: Props) => {
97
99
 
98
100
  const [previewUrl, setPreviewUrl] = React.useState<string | undefined>(previewUrlInit);
99
101
 
100
- className = 'input upload ' + (className === undefined ? '' : ' ' + className);
102
+ let className = 'input upload';
103
+
104
+ if (!button)
105
+ className += ' box';
106
+
107
+ if (customClassName !== undefined)
108
+ className += ' ' + customClassName;
101
109
 
102
110
  /*----------------------------------
103
111
  - ACTIONS
@@ -131,33 +139,49 @@ export default (props: Props) => {
131
139
 
132
140
  <div class={className}>
133
141
 
134
- {file && <>
135
- <div class="preview">
136
-
137
- {previewUrl ? (
138
- <img src={previewUrl} />
139
- ) : typeof file === 'string' ? <>
142
+ {button ? <>
143
+
144
+ <Button type="secondary"
145
+ {...button === true ? {} : button}
146
+ >
147
+ {emptyText}
148
+ </Button>
149
+
150
+ </> : <>
151
+
152
+ {file && <>
153
+ <div class="preview">
154
+
155
+ {previewUrl ? (
156
+ <img src={previewUrl} />
157
+ ) : typeof file === 'string' ? <>
158
+ <strong>A file has been selected</strong>
159
+ </> : file ? <>
160
+ <strong>{file.name}</strong>
161
+ </> : null}
162
+ </div>
163
+
164
+ <div class="row actions sp-05">
165
+
166
+ {typeof file === 'string' && <>
167
+ <Button type="secondary" icon="eye" shape="pill" size="s" link={file} />
168
+ </>}
169
+
170
+ <Button class='bg error' icon="trash" shape="pill" size="s"
171
+ async onClick={() => onChange(undefined)} />
172
+ </div>
173
+ </>}
174
+
175
+ <div class="indication col al-center">
176
+ {typeof file === 'string' ? <>
140
177
  <strong>A file has been selected</strong>
141
178
  </> : file ? <>
142
179
  <strong>{file.name}</strong>
143
- </> : null}
144
- </div>
145
-
146
- <div class="row actions sp-05">
147
-
148
- {typeof file === 'string' && <>
149
- <Bouton type="secondary" icon="eye" shape="pill" size="s" link={file} />
150
- </>}
151
-
152
- <Bouton class='bg error' icon="trash" shape="pill" size="s"
153
- async onClick={() => onChange(undefined)} />
180
+ </> : emptyText}
154
181
  </div>
182
+
155
183
  </>}
156
184
 
157
- <div class="indication col al-center">
158
- {emptyText}
159
- </div>
160
-
161
185
  <input type="file" onChange={selectFile} />
162
186
  </div>
163
187
  </InputWrapper>
@@ -36,6 +36,7 @@ import { TweetNode } from '@client/components/inputv3/Rte/nodes/TweetNode';
36
36
  import { YouTubeNode } from '@client/components/inputv3/Rte/nodes/YouTubeNode';
37
37
 
38
38
  import HeadingWithAnchorNode from '@client/components/inputv3/Rte/nodes/HeadingNode';
39
+ import ReferenceLinkNode from '@client/components/inputv3/Rte/nodes/ReferenceLinkNode';
39
40
 
40
41
  const PlaygroundNodes: Array<Klass<LexicalNode>> = [
41
42
  /*HeadingNode, */HeadingWithAnchorNode,
@@ -74,6 +75,9 @@ const PlaygroundNodes: Array<Klass<LexicalNode>> = [
74
75
  PageBreakNode,
75
76
  LayoutContainerNode,
76
77
  LayoutItemNode,
78
+
79
+ // Custom
80
+ ReferenceLinkNode
77
81
  ];
78
82
 
79
83
  export default PlaygroundNodes;
@@ -24,7 +24,21 @@ import ServerResponse from '../response';
24
24
  - TYPES
25
25
  ----------------------------------*/
26
26
 
27
- const localeFilter = (input: any) => typeof input === 'string' && ISO6391.validate(input) ? input : undefined;
27
+ const localeFilter = (input: any) => {
28
+
29
+ // Data type
30
+ if (typeof input !== 'string')
31
+ return;
32
+
33
+ // Extract ISO code
34
+ let lang = input.trim().split(/[-_]/)[0].toLowerCase();
35
+
36
+ // Check size
37
+ if (!ISO6391.validate(lang))
38
+ return;
39
+
40
+ return lang.toUpperCase();
41
+ }
28
42
 
29
43
  export type UploadedFile = With<FileToUpload, 'md5'|'ext'>
30
44
 
@@ -132,6 +146,9 @@ export default class ServerRequest<
132
146
  'EN'
133
147
  )
134
148
 
149
+ console.log("locale", this.req.acceptsLanguages(), locale);
150
+
151
+
135
152
  return locale ? locale.toUpperCase() : 'EN'
136
153
  }
137
154
 
@@ -29,7 +29,7 @@ type LexicalState = {
29
29
  root: LexicalNode
30
30
  }
31
31
 
32
- type LexicalNode = {
32
+ export type LexicalNode = {
33
33
  version: number,
34
34
  type: string,
35
35
  children?: LexicalNode[],
@@ -42,6 +42,15 @@ type LexicalNode = {
42
42
  }
43
43
 
44
44
  type TRenderOptions = {
45
+
46
+ transform?: RteUtils["transformNode"],
47
+
48
+ render?: (
49
+ node: LexicalNode,
50
+ parent: LexicalNode | null,
51
+ options: TRenderOptions
52
+ ) => Promise<LexicalNode>,
53
+
45
54
  attachements?: {
46
55
  disk: Driver,
47
56
  directory: string,
@@ -103,8 +112,8 @@ export class RteUtils {
103
112
  }
104
113
  }
105
114
 
106
- const root = await this.processContent(json.root, async (node) => {
107
- return await this.transformNode(node, assets, options);
115
+ const root = await this.processContent(json.root, null, async (node, parent) => {
116
+ return await this.transformNode(node, parent, assets, options);
108
117
  });
109
118
 
110
119
  json = { ...json, root };
@@ -113,29 +122,30 @@ export class RteUtils {
113
122
  const attachementOptions = options?.attachements;
114
123
  if (attachementOptions && attachementOptions.prevVersion !== undefined) {
115
124
 
116
- await this.processContent(root, async (node) => {
125
+ await this.processContent(root, null, async (node) => {
117
126
  return await this.deleteUnusedFile(node, assets, attachementOptions);
118
127
  });
119
128
  }
120
129
 
121
130
  // Convert json to HTML
122
- const html = await this.jsonToHtml( json );
131
+ const html = await this.jsonToHtml( json, options );
123
132
 
124
133
  return { html, json: content, ...assets };
125
134
  }
126
135
 
127
136
  private async processContent(
128
137
  node: LexicalNode,
129
- callback: (node: LexicalNode) => Promise<LexicalNode>
138
+ parent: LexicalNode | null,
139
+ callback: (node: LexicalNode, parent: LexicalNode | null) => Promise<LexicalNode>
130
140
  ) {
131
141
 
132
- node = await callback(node);
142
+ node = await callback(node, parent);
133
143
 
134
144
  // Recursion
135
145
  if (node.children) {
136
146
  for (let i = 0; i < node.children.length; i++) {
137
147
 
138
- node.children[ i ] = await this.processContent( node.children[ i ], callback );
148
+ node.children[ i ] = await this.processContent( node.children[ i ], node, callback );
139
149
 
140
150
  }
141
151
  }
@@ -143,23 +153,34 @@ export class RteUtils {
143
153
  return node;
144
154
  }
145
155
 
146
- private async transformNode(node: LexicalNode, assets: TContentAssets, options: TRenderOptions) {
156
+ private async transformNode(
157
+ node: LexicalNode,
158
+ parent: LexicalNode | null,
159
+ assets: TContentAssets,
160
+ options: TRenderOptions
161
+ ): Promise<LexicalNode> {
147
162
 
148
- // Attachment: Upload attachments and replace blobs by URLs
163
+ // Images and files
149
164
  if (node.type === 'image' || node.type === 'file') {
150
165
 
166
+ // Upload images and files and replace blobs by URLs
151
167
  await this.processAttachement(
152
168
  node as With<LexicalNode, 'src'>,
153
169
  assets,
154
170
  options,
155
171
  );
156
172
 
173
+ // Headings
157
174
  } else if (node.type === 'anchored-heading') {
158
175
 
176
+ // Create skeleton
159
177
  await this.processHeading(node, assets);
160
178
 
161
179
  }
162
180
 
181
+ if (options.transform)
182
+ node = await options.transform(node, parent, assets, options);
183
+
163
184
  return node;
164
185
  }
165
186
 
@@ -261,7 +282,17 @@ export class RteUtils {
261
282
  return node;
262
283
  }
263
284
 
264
- public async jsonToHtml( json: LexicalState ) {
285
+ public async jsonToHtml( json: LexicalState, options: TRenderOptions = {} ) {
286
+
287
+ // Transform before rendering
288
+ const renderTransform = options.render;
289
+ if (renderTransform)
290
+ json = {
291
+ ...json,
292
+ root: await this.processContent(json.root, null, async (node, parent) => {
293
+ return await renderTransform(node, parent, options);
294
+ })
295
+ }
265
296
 
266
297
  // Server side: simulate DOM environment
267
298
  const dom = new JSDOM(`<!DOCTYPE html><body></body>`);
@@ -37,31 +37,43 @@ export class Slug {
37
37
 
38
38
  // Check if already existing
39
39
  if (SQL !== undefined) {
40
-
41
- const escapedSlug = escapeStringRegexp(slug);
42
-
43
- const duplicates = await SQL.selectVal<number>(`
44
- SELECT
45
- IF( ${column} LIKE ${SQL.esc(slug)},
46
- 1,
47
- CAST(SUBSTRING_INDEX(slug, '-', -1) AS UNSIGNED)
48
- ) AS duplicates
49
- FROM ${table}
50
- WHERE
51
- ${column} LIKE ${SQL.esc(slug)}
52
- OR
53
- ${column} REGEXP '^${escapedSlug}-[0-9]+$'
54
- ORDER BY duplicates DESC
55
- LIMIT 1
56
- `);
57
-
58
- if (duplicates && duplicates > 0)
59
- slug += `-${duplicates + 1}`;
40
+ slug = await this.Correct(slug, SQL, table, column);
60
41
  }
61
42
 
62
43
  return slug;
63
44
  }
64
45
 
46
+ public async Correct(
47
+ slug: string,
48
+ SQL: SQL,
49
+ table: string,
50
+ column: string
51
+ ) {
52
+
53
+ const escapedSlug = escapeStringRegexp(slug);
54
+
55
+ const duplicates = await SQL.selectVal<number>(`
56
+ SELECT
57
+ IF( ${column} LIKE ${SQL.esc(slug)},
58
+ 1,
59
+ CAST(SUBSTRING_INDEX(slug, '-', -1) AS UNSIGNED)
60
+ ) AS duplicates
61
+ FROM ${table}
62
+ WHERE
63
+ ${column} LIKE ${SQL.esc(slug)}
64
+ OR
65
+ ${column} REGEXP '^${escapedSlug}-[0-9]+$'
66
+ ORDER BY duplicates DESC
67
+ LIMIT 1
68
+ `);
69
+
70
+ if (duplicates && duplicates > 0)
71
+ slug += `-${duplicates + 1}`;
72
+
73
+ return slug;
74
+
75
+ }
76
+
65
77
  }
66
78
 
67
79
  export default new Slug;