mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-06 08:22:24 +01:00
fix (form): enhance form app
This commit is contained in:
parent
2babd4205b
commit
8d80cabba0
7 changed files with 75 additions and 32 deletions
|
|
@ -6,6 +6,11 @@
|
|||
line-height: 1em;
|
||||
text-align: justify;
|
||||
}
|
||||
.formbuilder a {
|
||||
text-decoration: underline;
|
||||
text-decoration-style: wavy;
|
||||
text-decoration-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.formbuilder input::placeholder, .formbuilder textarea::placeholder {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
|
@ -46,3 +51,11 @@
|
|||
border-bottom: 2px solid rgba(70, 99, 114, 0.1);
|
||||
transition: border-color 0.2s ease-out;
|
||||
}
|
||||
|
||||
.formbuilder .banner {
|
||||
background: var(--bg-color);
|
||||
border: 2px solid rgba(0, 0, 0, 0.05);
|
||||
border-radius: 3px;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ async function createFormNodes(node, { renderNode, renderLeaf, renderInput, path
|
|||
else {
|
||||
const currentPath = path.concat(key);
|
||||
const $leaf = renderLeaf({
|
||||
...withMarkdown(node[key]),
|
||||
...node[key],
|
||||
path: currentPath,
|
||||
label: key,
|
||||
});
|
||||
|
|
@ -122,15 +122,3 @@ export async function createForm(node, opts) {
|
|||
});
|
||||
return $container;
|
||||
}
|
||||
|
||||
function withMarkdown(obj) {
|
||||
if (!("description" in obj)) return obj;
|
||||
obj["description"] = toMarkdown(obj["description"]);
|
||||
return obj;
|
||||
}
|
||||
|
||||
function toMarkdown(str = "") {
|
||||
str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<a href=\"$2\">$1</a>");
|
||||
str = str.replaceAll("\n", "<br>");
|
||||
return str;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import rxjs from "../../lib/rx.js";
|
|||
export function renderLeaf({ format, label, description, type }) {
|
||||
if (label === "banner") return createElement(`
|
||||
<div class="banner">
|
||||
${description}
|
||||
${fromMarkdown(description)}
|
||||
</div>
|
||||
`);
|
||||
const $el = createElement(`
|
||||
|
|
@ -29,6 +29,12 @@ export function renderLeaf({ format, label, description, type }) {
|
|||
return $el;
|
||||
}
|
||||
|
||||
function fromMarkdown(str = "") {
|
||||
str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<a href=\"$2\">$1</a>");
|
||||
str = str.replaceAll("\n", "<br>");
|
||||
return str;
|
||||
}
|
||||
|
||||
export function useForm$($inputNodeList) {
|
||||
return rxjs.pipe(
|
||||
rxjs.mergeMap(() => $inputNodeList()),
|
||||
|
|
|
|||
|
|
@ -98,9 +98,6 @@
|
|||
}
|
||||
.component_page_admin .page_container a {
|
||||
color: var(--dark);
|
||||
text-decoration: underline;
|
||||
text-decoration-style: wavy;
|
||||
text-decoration-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.component_page_admin .page_container a:hover {
|
||||
opacity: 0.8;
|
||||
|
|
@ -181,13 +178,6 @@
|
|||
.component_page_admin .formbuilder textarea::placeholder {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.component_page_admin .formbuilder .banner {
|
||||
background: var(--bg-color);
|
||||
border: 2px solid rgba(0, 0, 0, 0.05);
|
||||
border-radius: 3px;
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
}
|
||||
.component_page_admin .formbuilder .banner pre {
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
|
|
|
|||
|
|
@ -58,9 +58,6 @@
|
|||
.component_formviewer > .formviewer_container .box .formbuilder label.no-select > div > span span.mandatory {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.component_formviewer > .formviewer_container .box .formbuilder label.no-select > div div.description a {
|
||||
border-bottom: 1px dashed var(--color);
|
||||
}
|
||||
|
||||
.component_formviewer .formbuilder .component_input[disabled],
|
||||
.component_formviewer .formbuilder .component_input[readonly],
|
||||
|
|
|
|||
|
|
@ -64,7 +64,12 @@ export default function(render, { acl$, getFilename, getDownloadUrl }) {
|
|||
}
|
||||
return $el;
|
||||
},
|
||||
renderLeaf: ({ label, format, description, required }) => createElement(`
|
||||
renderLeaf: ({ label, format, description, required }) => label === "banner"
|
||||
? createElement(`
|
||||
<div class="banner">
|
||||
${fromMarkdown(safe(description))}
|
||||
</div>
|
||||
`) : createElement(`
|
||||
<label class="no-select">
|
||||
<div>
|
||||
<span class="ellipsis">
|
||||
|
|
@ -75,7 +80,7 @@ export default function(render, { acl$, getFilename, getDownloadUrl }) {
|
|||
</div>
|
||||
<div>
|
||||
<span class="nothing"></span>
|
||||
<div class="description">${safe(description)}</div>
|
||||
<div class="description">${fromMarkdown(safe(description))}</div>
|
||||
</div>
|
||||
</label>
|
||||
`),
|
||||
|
|
@ -170,3 +175,9 @@ function readOnlyForm(formSpec) {
|
|||
}
|
||||
return formSpec;
|
||||
}
|
||||
|
||||
function fromMarkdown(str = "") {
|
||||
str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<a href=\"$2\">$1</a>");
|
||||
str = str.replaceAll("\n", "<br>");
|
||||
return str;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,21 +53,34 @@ func (this PSQL) Cat(path string) (io.ReadCloser, error) {
|
|||
forms := make([]FormElement, len(c))
|
||||
for i, _ := range columns {
|
||||
forms[i] = createFormElement(col[i], columns[i])
|
||||
if columnComment := _findCommentColumn(this.ctx, this.db, l.table, columns[i].Name); columnComment != "" {
|
||||
forms[i].Description = columnComment
|
||||
}
|
||||
if slices.Contains(columns[i].Constraint, "PRIMARY KEY") && forms[i].Value != nil {
|
||||
forms[i].ReadOnly = true
|
||||
} else if slices.Contains(columns[i].Constraint, "FOREIGN KEY") {
|
||||
if link, err := _findRelation(this.ctx, this.db, columns[i]); err == nil {
|
||||
forms[i].Description = _createDescription(columns[i], link)
|
||||
if len(link.values) > 0 {
|
||||
forms[i].Type = "select"
|
||||
forms[i].Opts = link.values
|
||||
}
|
||||
if forms[i].Description == "" {
|
||||
forms[i].Description = _createDescription(columns[i], link)
|
||||
}
|
||||
}
|
||||
} else if values, err := _findEnumValues(this.ctx, this.db, columns[i]); err == nil && len(values) > 0 {
|
||||
forms[i].Type = "select"
|
||||
forms[i].Opts = values
|
||||
}
|
||||
|
||||
}
|
||||
if comment := _findCommentTable(this.ctx, this.db, l.table); comment != "" {
|
||||
forms = append([]FormElement{
|
||||
{
|
||||
Name: "banner",
|
||||
Type: "hidden",
|
||||
Description: comment,
|
||||
},
|
||||
}, forms...)
|
||||
}
|
||||
b, err := Form{Elmnts: forms}.MarshalJSON()
|
||||
if err != nil {
|
||||
|
|
@ -78,11 +91,36 @@ func (this PSQL) Cat(path string) (io.ReadCloser, error) {
|
|||
|
||||
func _createDescription(el Column, link LocationColumn) string {
|
||||
if slices.Contains(el.Constraint, "FOREIGN KEY") {
|
||||
return fmt.Sprintf("points to <%s> → <%s>", link.table, link.column)
|
||||
return fmt.Sprintf("points to [<%s> → <%s>](/files/%s/)", link.table, link.column, link.table)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func _findCommentTable(ctx context.Context, db *sql.DB, tableName string) string {
|
||||
var comment string
|
||||
if err := db.QueryRowContext(ctx, `
|
||||
SELECT obj_description(c.oid)
|
||||
FROM pg_class c
|
||||
WHERE c.relname = $1 AND c.relkind = 'r'
|
||||
`, tableName).Scan(&comment); err != nil {
|
||||
return ""
|
||||
}
|
||||
return comment
|
||||
}
|
||||
|
||||
func _findCommentColumn(ctx context.Context, db *sql.DB, tableName, columnName string) string {
|
||||
var comment string
|
||||
if err := db.QueryRowContext(ctx, `
|
||||
SELECT col_description(c.oid, a.attnum)
|
||||
FROM pg_class c
|
||||
JOIN pg_attribute a ON a.attrelid = c.oid
|
||||
WHERE c.relname = $1 AND a.attname = $2 AND c.relkind = 'r'
|
||||
`, tableName, columnName).Scan(&comment); err != nil {
|
||||
return ""
|
||||
}
|
||||
return comment
|
||||
}
|
||||
|
||||
func _findRelation(ctx context.Context, db *sql.DB, el Column) (LocationColumn, error) {
|
||||
l := LocationColumn{}
|
||||
rows, err := db.QueryContext(ctx, `
|
||||
|
|
|
|||
Loading…
Reference in a new issue