Skip to content

Commit e391ea4

Browse files
committed
init package
0 parents  commit e391ea4

File tree

17 files changed

+3305
-0
lines changed

17 files changed

+3305
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

Configuration/Settings.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Neos:
2+
Neos:
3+
Ui:
4+
resources:
5+
javascript:
6+
'Prgx.Neos.StringsEditor':
7+
resource: 'resource://Prgfx.Neos.StringsEditor/Public/Plugin/Plugin.js'
8+
stylesheets:
9+
'Prgx.Neos.StringsEditor':
10+
resource: 'resource://Prgfx.Neos.StringsEditor/Public/Plugin/Plugin.css'
11+
userInterface:
12+
inspector:
13+
dataTypes:
14+
array<string>:
15+
editor: Prgfx.Neos.StringsEditor

LICENSE

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Prgfx.Neos.StringsEditor
2+
3+
Provides an inspector editor for editing simple `array<string>` properties.
4+
5+
`composer require prgfx/neos-stringseditor`
6+
7+
![Screenshot of the editor](./screenshot.png)
8+
9+
## Usage
10+
```yaml
11+
My.NodeType:
12+
properties:
13+
items:
14+
type: array<string>
15+
ui:
16+
inspector:
17+
# you can normally omit this, when you use array<string> as property type
18+
editor: Prgfx.Neos.StringsEditor
19+
editorOptions:
20+
placeholder: Placeholder text
21+
# optional number of items allowed to be entered
22+
maximumItems: 4
23+
# by default only unique items are allowed, but you can disable this rule
24+
unique: false
25+
```
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
module.exports = {
2+
'env': {
3+
'browser': true,
4+
'es2021': true,
5+
},
6+
'extends': [
7+
'eslint:recommended',
8+
'plugin:@typescript-eslint/recommended',
9+
'plugin:react/recommended',
10+
],
11+
'overrides': [
12+
{
13+
'env': {
14+
'node': true,
15+
},
16+
'files': [
17+
'.eslintrc.{js,cjs}',
18+
],
19+
'parserOptions': {
20+
'sourceType': 'script',
21+
},
22+
},
23+
],
24+
'parser': '@typescript-eslint/parser',
25+
'parserOptions': {
26+
'ecmaVersion': 'latest',
27+
'sourceType': 'module',
28+
},
29+
'plugins': [
30+
'@typescript-eslint',
31+
'react',
32+
'react-hooks',
33+
],
34+
'rules': {
35+
'indent': [
36+
'error',
37+
4,
38+
],
39+
'quotes': [
40+
'error',
41+
'single',
42+
],
43+
'semi': [
44+
'error',
45+
'always',
46+
],
47+
'comma-dangle': [
48+
'error',
49+
{
50+
'arrays': 'always-multiline',
51+
'objects': 'always-multiline',
52+
'imports': 'always-multiline',
53+
'exports': 'always-multiline',
54+
'functions': 'ignore',
55+
},
56+
],
57+
'object-curly-spacing': [
58+
'warn',
59+
'always',
60+
{
61+
'objectsInObjects': true,
62+
'arraysInObjects': true,
63+
},
64+
],
65+
'array-bracket-spacing': [
66+
'warn',
67+
'always',
68+
],
69+
'jsx-quotes': [
70+
'error',
71+
'prefer-double',
72+
],
73+
'react/jsx-curly-spacing': [
74+
'error',
75+
{
76+
'when': 'never',
77+
'children': true,
78+
},
79+
],
80+
'react/jsx-curly-brace-presence': [
81+
'error',
82+
{
83+
'props': 'never',
84+
'children': 'ignore',
85+
'propElementValues': 'always',
86+
},
87+
],
88+
'react-hooks/rules-of-hooks': 'error',
89+
'react-hooks/exhaustive-deps': 'warn',
90+
},
91+
};

Resources/Private/Plugin/build.mjs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* eslint-env node */
2+
import esbuild from "esbuild";
3+
import eslint from "esbuild-plugin-eslint";
4+
import {cssModules} from "esbuild-plugin-lightningcss-modules";
5+
import extensibilityMap from "@neos-project/neos-ui-extensibility/extensibilityMap.json" assert { type: 'json' };
6+
7+
const isWatch = process.argv.includes('--watch');
8+
9+
/** @type {import("esbuild").BuildOptions} */
10+
const options = {
11+
logLevel: 'info',
12+
bundle: true,
13+
target: 'es2020',
14+
entryPoints: { 'Plugin': 'src/index.js' },
15+
outdir: '../../Public/Plugin',
16+
alias: extensibilityMap,
17+
plugins: [
18+
eslint(),
19+
cssModules({}),
20+
],
21+
};
22+
23+
if (isWatch) {
24+
esbuild.context(options).then((ctx) => ctx.watch());
25+
} else {
26+
options.minify = true;
27+
esbuild.build(options);
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "@prgfx/neos-stringseditor",
3+
"private": true,
4+
"scripts": {
5+
"build": "node build.mjs",
6+
"watch": "node build.mjs --watch",
7+
"lint": "eslint src/"
8+
},
9+
"devDependencies": {
10+
"@neos-project/neos-ui-extensibility": "~8.3.0",
11+
"@neos-project/react-ui-components": "~8.3.0",
12+
"@types/react": "^17.0.6",
13+
"@typescript-eslint/eslint-plugin": "^6.7.2",
14+
"@typescript-eslint/parser": "^6.7.2",
15+
"esbuild": "~0.17.18",
16+
"esbuild-plugin-eslint": "^0.3.6",
17+
"esbuild-plugin-lightningcss-modules": "^0.1.0",
18+
"eslint": "^8.50.0",
19+
"eslint-plugin-react": "^7.33.2",
20+
"eslint-plugin-react-hooks": "^4.6.0",
21+
"typescript": "^5.2.2"
22+
},
23+
"peerDependencies": {
24+
"classnames": "^2.3.1",
25+
"react": "^17.0.2",
26+
"react-redux": "^7.2.4"
27+
}
28+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.inputWrapper {
2+
display: flex;
3+
}
4+
5+
.inputWrapper > div {
6+
flex-grow: 1;
7+
}
8+
9+
.list {
10+
margin-bottom: 0;
11+
}
12+
13+
.input {
14+
border-top-right-radius: 0;
15+
border-bottom-right-radius: 0;
16+
}
17+
18+
.submitButton {
19+
border-top-left-radius: 0;
20+
border-bottom-left-radius: 0;
21+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import React, { useMemo, useState } from 'react';
2+
import {
3+
IconButton,
4+
MultiSelectBox_ListPreviewSortable,
5+
SelectBox_Option_SingleLine,
6+
TextInput,
7+
} from '@neos-project/react-ui-components';
8+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
9+
// @ts-ignore
10+
import styles from './StringsEditor.module.css';
11+
12+
type StringsEditorProps = {
13+
id: string;
14+
value: string[];
15+
options: unknown;
16+
commit: (value: string[]) => void;
17+
i18nRegistry: { translate: (key: string) => string };
18+
disabled?: boolean;
19+
}
20+
21+
const valueAccessor = (x: { key: string }): string => x.key;
22+
23+
const isObject = (e: unknown): e is Record<string, unknown> => {
24+
return typeof e === 'object' && e !== null && !Array.isArray(e);
25+
};
26+
27+
type StringsEditorOptions = {
28+
unique: boolean;
29+
placeholder?: string;
30+
maximumItems?: number;
31+
}
32+
33+
const getOptions = (options: unknown): StringsEditorOptions => {
34+
const result: StringsEditorOptions = {
35+
unique: true,
36+
};
37+
38+
if (isObject(options)) {
39+
if ('unique' in options) {
40+
result.unique = options.unique !== false;
41+
}
42+
43+
if (typeof options.placeholder === 'string') {
44+
result.placeholder = options.placeholder;
45+
}
46+
47+
if (typeof options.maximumItems === 'number' && result.maximumItems > 0) {
48+
result.maximumItems = options.maximumItems;
49+
}
50+
}
51+
52+
return result;
53+
};
54+
55+
export const StringsEditor = (props: StringsEditorProps) => {
56+
console.log('StringsEditor', props);
57+
const [ input, setInput ] = useState('');
58+
59+
const {
60+
unique,
61+
placeholder,
62+
maximumItems,
63+
} = useMemo(() => {
64+
return getOptions(props.options);
65+
}, [ props.options ]);
66+
67+
const inputEmpty = input.trim().length === 0;
68+
const inputUnique = !unique || !props.value.includes(input);
69+
const hasMaxItems = maximumItems !== undefined && props.value.length >= maximumItems;
70+
const inputValid = !inputEmpty && inputUnique && !hasMaxItems;
71+
72+
const handleSubmit = () => {
73+
if (!inputValid) {
74+
return;
75+
}
76+
77+
props.commit([ ...props.value, input ]);
78+
setInput('');
79+
};
80+
81+
const handleKeyPress = (e: React.KeyboardEvent) => {
82+
if (e.key === 'Enter') {
83+
e.preventDefault();
84+
handleSubmit();
85+
}
86+
};
87+
88+
const options = useMemo(() => {
89+
return props.value.map(item => ({
90+
label: item,
91+
key: item,
92+
}));
93+
}, [ props.value ]);
94+
95+
return (
96+
<div>
97+
<ul className={styles.list}>
98+
<MultiSelectBox_ListPreviewSortable
99+
disabled={props.disabled}
100+
values={props.value}
101+
options={options}
102+
optionValueAccessor={valueAccessor}
103+
onValuesChange={props.commit}
104+
ListPreviewElement={SelectBox_Option_SingleLine}
105+
dndType="multiselect-box-value"
106+
/>
107+
</ul>
108+
<div className={styles.inputWrapper}>
109+
<TextInput
110+
id={props.id}
111+
value={input}
112+
onChange={setInput}
113+
onKeyUp={handleKeyPress}
114+
disabled={props.disabled || hasMaxItems}
115+
placeholder={placeholder ? props.i18nRegistry.translate(placeholder) : undefined}
116+
className={styles.input}
117+
/>
118+
<IconButton
119+
icon="check"
120+
onClick={handleSubmit}
121+
disabled={props.disabled || !inputValid}
122+
className={styles.submitButton}
123+
style="neutral"
124+
/>
125+
</div>
126+
</div>
127+
);
128+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './manifest';

0 commit comments

Comments
 (0)