generated from idea2app/Next-Bootstrap-ts
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[add] GitHub OAuth 2.0 API [optimize] upgrade to KoAJAX 1, PNPM 9, Sentry 8 & other latest Upstream packages
- Loading branch information
Showing
16 changed files
with
7,549 additions
and
4,518 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { debounce } from 'lodash'; | ||
import { Component } from 'react'; | ||
import { Form } from 'react-bootstrap'; | ||
import { uniqueID } from 'web-utility'; | ||
|
||
export interface CascadeProps { | ||
required: boolean; | ||
} | ||
|
||
interface LevelItem { | ||
label?: string; | ||
list: string[]; | ||
} | ||
|
||
export abstract class CascadeSelect< | ||
P extends CascadeProps, | ||
> extends Component<P> { | ||
UID = uniqueID(); | ||
|
||
innerPath: string[] = []; | ||
|
||
list: LevelItem[] = []; | ||
|
||
get path() { | ||
return this.innerPath.filter(Boolean).slice(0, -1).join('/'); | ||
} | ||
|
||
get name() { | ||
return this.innerPath.slice(-1)[0]; | ||
} | ||
|
||
get pathName() { | ||
return this.innerPath.filter(Boolean).join('/'); | ||
} | ||
|
||
reset() { | ||
const { innerPath, list } = this; | ||
|
||
this.innerPath = [innerPath[0]]; | ||
this.list = [list[0]]; | ||
} | ||
|
||
componentDidMount() { | ||
this.changeLevel(-1, ''); | ||
} | ||
|
||
abstract getNextLevel(): Promise<LevelItem | undefined>; | ||
|
||
changeLevel = debounce(async (index: number, value: string) => { | ||
const { innerPath, list } = this; | ||
|
||
innerPath.splice(index, Infinity, value); | ||
|
||
const level = await this.getNextLevel(); | ||
|
||
if (level != null) list.splice(++index, Infinity, level); | ||
else list.length = ++index; | ||
|
||
this.list = list; | ||
}); | ||
|
||
render() { | ||
const { UID, list } = this, | ||
{ required } = this.props; | ||
|
||
return ( | ||
<> | ||
{list.map(({ label, list }, index) => { | ||
const IID = `input-${UID}-${index}`, | ||
LID = `list-${UID}-${index}`; | ||
|
||
return ( | ||
<span key={IID} className="form-inline d-inline-flex"> | ||
<Form.Control | ||
id={IID} | ||
list={LID} | ||
onChange={({ target: { value } }) => | ||
(value = value.trim()) && this.changeLevel(index, value) | ||
} | ||
required={!index && required} | ||
/> | ||
<datalist id={LID}> | ||
{list.map(item => ( | ||
<option value={item} key={item} /> | ||
))} | ||
</datalist> | ||
<label htmlFor={IID} className="pl-2 pr-2"> | ||
{label} | ||
</label> | ||
</span> | ||
); | ||
})} | ||
</> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { FC } from 'react'; | ||
|
||
const type_map = { | ||
string: { title: 'Inline text', icon: 'grip-lines' }, | ||
text: { title: 'Rows text', icon: 'align-left' }, | ||
object: { title: 'Key-value list', icon: 'list-ul' }, | ||
array: { title: 'Ordered list', icon: 'list-ol' }, | ||
}; | ||
|
||
export interface AddBarProps { | ||
onSelect: (type: string) => void; | ||
} | ||
|
||
export const AddBar: FC<AddBarProps> = ({ onSelect }) => ( | ||
<nav> | ||
{Object.entries(type_map).map(([key, { title, icon }]) => ( | ||
<button | ||
key={key} | ||
type="button" | ||
className={'btn btn-sm btn-success m-1 fas fa-' + icon} | ||
title={title} | ||
onClick={onSelect.bind(null, key)} | ||
/> | ||
))} | ||
</nav> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import { observable } from 'mobx'; | ||
import { observer } from 'mobx-react'; | ||
import { ChangeEvent, Component, ReactNode, SyntheticEvent } from 'react'; | ||
import { Form } from 'react-bootstrap'; | ||
|
||
import { AddBar } from './AddBar'; | ||
|
||
export interface DataMeta { | ||
type: string; | ||
key?: string | number; | ||
value: any; | ||
children?: DataMeta[]; | ||
} | ||
|
||
export interface FieldProps { | ||
value: object | any[] | null; | ||
onChange?: (event: ChangeEvent) => void; | ||
} | ||
|
||
@observer | ||
export class ListField extends Component<FieldProps> { | ||
@observable | ||
accessor innerValue = ListField.metaOf(this.props.value); | ||
|
||
static metaOf(value: any): DataMeta { | ||
if (value instanceof Array) | ||
return { | ||
type: 'array', | ||
value, | ||
children: Array.from(value, (value, key) => ({ | ||
...this.metaOf(value), | ||
key, | ||
})), | ||
}; | ||
|
||
if (value instanceof Object) | ||
return { | ||
type: 'object', | ||
value, | ||
children: Object.entries(value).map(([key, value]) => ({ | ||
...this.metaOf(value), | ||
key, | ||
})), | ||
}; | ||
|
||
return { | ||
type: /[\r\n]/.test(value) ? 'text' : 'string', | ||
value, | ||
}; | ||
} | ||
|
||
addItem = (type: string) => { | ||
var item: DataMeta = { type, value: [] }, | ||
{ innerValue } = this; | ||
|
||
switch (type) { | ||
case 'string': | ||
item = ListField.metaOf(''); | ||
break; | ||
case 'text': | ||
item = ListField.metaOf('\n'); | ||
break; | ||
case 'object': | ||
item = ListField.metaOf({}); | ||
break; | ||
case 'array': | ||
item = ListField.metaOf([]); | ||
} | ||
|
||
this.innerValue = { | ||
...innerValue, | ||
children: [...(innerValue.children || []), item], | ||
}; | ||
}; | ||
|
||
protected dataChange = | ||
(method: (item: DataMeta, newKey: string) => any) => | ||
( | ||
index: number, | ||
{ currentTarget: { value: data } }: SyntheticEvent<any>, | ||
) => { | ||
const { children = [] } = this.innerValue; | ||
|
||
const item = children[index]; | ||
|
||
if (!item) return; | ||
|
||
method.call(this, item, data); | ||
|
||
this.props.onChange?.({ | ||
target: { value: this.innerValue.value }, | ||
} as unknown as ChangeEvent); | ||
}; | ||
|
||
setKey = this.dataChange((item: DataMeta, newKey: string) => { | ||
const { value, children = [] } = this.innerValue; | ||
|
||
item.key = newKey; | ||
|
||
for (let oldKey in value) | ||
if (!children.some(({ key }) => key === oldKey)) { | ||
value[newKey] = value[oldKey]; | ||
|
||
delete value[oldKey]; | ||
return; | ||
} | ||
|
||
value[newKey] = item.value; | ||
}); | ||
|
||
setValue = this.dataChange((item: DataMeta, newValue: any) => { | ||
const { value } = this.innerValue; | ||
|
||
if (newValue instanceof Array) newValue = [...newValue]; | ||
else if (typeof newValue === 'object') newValue = { ...newValue }; | ||
|
||
item.value = newValue; | ||
|
||
if (item.key != null) value[item.key + ''] = newValue; | ||
else if (value instanceof Array) item.key = value.push(newValue) - 1; | ||
}); | ||
|
||
fieldOf(index: number, type: string, value: any) { | ||
switch (type) { | ||
case 'string': | ||
return ( | ||
<Form.Control | ||
defaultValue={value} | ||
placeholder="Value" | ||
onBlur={this.setValue.bind(this, index)} | ||
/> | ||
); | ||
case 'text': | ||
return ( | ||
<Form.Control | ||
as="textarea" | ||
defaultValue={value} | ||
placeholder="Value" | ||
onBlur={this.setValue.bind(this, index)} | ||
/> | ||
); | ||
default: | ||
return ( | ||
<ListField value={value} onChange={this.setValue.bind(this, index)} /> | ||
); | ||
} | ||
} | ||
|
||
wrapper(slot: ReactNode) { | ||
const Tag = this.innerValue.type === 'array' ? 'ol' : 'ul'; | ||
|
||
return <Tag className="inline-form">{slot}</Tag>; | ||
} | ||
|
||
render() { | ||
const { type: field_type, children = [] } = this.innerValue; | ||
|
||
return this.wrapper( | ||
<> | ||
<li className="form-group"> | ||
<AddBar onSelect={this.addItem} /> | ||
</li> | ||
{children.map(({ type, key, value }, index) => ( | ||
<li className="input-group input-group-sm" key={key}> | ||
{field_type === 'object' && ( | ||
<Form.Control | ||
defaultValue={key} | ||
required | ||
placeholder="Key" | ||
onBlur={this.setKey.bind(this, index)} | ||
/> | ||
)} | ||
{this.fieldOf(index, type, value)} | ||
</li> | ||
))} | ||
</>, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import TurnDown from 'turndown'; | ||
// @ts-ignore | ||
import { gfm } from 'turndown-plugin-gfm'; | ||
|
||
const Empty_HREF = /^(#|javascript:\s*void\(0\);?\s*)$/; | ||
|
||
type TurnDownGFM = (td: TurnDown) => void; | ||
|
||
export default class extends TurnDown { | ||
constructor(options?: any) { | ||
super({ | ||
headingStyle: 'atx', | ||
hr: '---', | ||
bulletListMarker: '-', | ||
codeBlockStyle: 'fenced', | ||
linkStyle: 'referenced', | ||
...options, | ||
}); | ||
|
||
this.use(gfm as TurnDownGFM) | ||
.addRule('non_url', { | ||
filter: node => | ||
['a', 'area'].includes(node.nodeName.toLowerCase()) && | ||
Empty_HREF.test(node.getAttribute('href') || ''), | ||
replacement: (content, node) => | ||
content.trim() || | ||
(node instanceof HTMLElement ? node.title.trim() : ''), | ||
}) | ||
.addRule('asset_code', { | ||
filter: ['style', 'script'], | ||
replacement: () => '', | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
.editor { | ||
position: relative; | ||
min-height: 2.35rem; | ||
&::before { | ||
position: absolute; | ||
right: 1px; | ||
top: 1px; | ||
padding: 0.3rem 0.5rem; | ||
background: white; | ||
content: 'count: ' attr(data-count) !important; | ||
} | ||
&:focus::before { | ||
content: none; | ||
} | ||
img { | ||
display: block; | ||
margin: 1rem auto; | ||
max-height: 70vh; | ||
} | ||
} |
Oops, something went wrong.