glib-web 6.4.1 → 6.5.1
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.
- checksums.yaml +4 -4
- data/app/controllers/concerns/glib/json/libs.rb +1 -1
- data/app/controllers/concerns/glib/json/traversal.rb +1 -1
- data/app/helpers/glib/json_ui/abstract_builder.rb +0 -15
- data/app/helpers/glib/json_ui/action_builder/components.rb +3 -0
- data/app/helpers/glib/json_ui/action_builder/logics.rb +3 -0
- data/app/helpers/glib/json_ui/action_builder.rb +2 -2
- data/app/helpers/glib/json_ui/menu_builder.rb +1 -1
- data/app/helpers/glib/json_ui/view_builder/panels.rb +73 -0
- data/app/helpers/glib/json_ui/view_builder.rb +7 -3
- data/app/views/json_ui/garage/forms/new_rich_text.json.jbuilder +1 -1
- data/app/views/json_ui/garage/forms/rich_text.json.jbuilder +1 -1
- data/app/views/json_ui/garage/test_page/duplicate_names.json.jbuilder +84 -0
- data/app/views/json_ui/garage/test_page/inputs_outside_form.json.jbuilder +123 -0
- data/app/views/json_ui/garage/test_page/logics_run_multi_form.json.jbuilder +3 -0
- data/lib/glib/json_crawler/action_crawlers/forms_submit.rb +2 -2
- data/lib/glib/json_crawler/router.rb +15 -18
- data/lib/glib/test_helpers.rb +2 -2
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 530b28412dbe8757ccb9cf80e746d3d9891f8d403ee013a2c505a4bc6681b8ac
|
|
4
|
+
data.tar.gz: 6754295e8de270915d25603a432197b7e50d372d6c4a227f6bd581b0ede0cb65
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 54a71bbd3cfb00f390dd5908e356db7bcb5f4576e66c3bef6be29e05841bd7a7c4e014344cab7784c9e0dec549ed4e83b46e77b214b5545d55b31e70ac26dd18
|
|
7
|
+
data.tar.gz: 47975dd0401a0b59cf404aa8bcaba12b9560bd0760fe4aa428c3516d10555749b21eea7ae71fdab38cb7516929cd0cde518099f9b4eeae35ab50eeccdcfb40d5
|
|
@@ -28,21 +28,6 @@ module Glib
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
# TODO: Consider removing this in favour of non 'v1' suffix
|
|
32
|
-
def add_singleton_element_v1(type, name, *args)
|
|
33
|
-
name_components = name.to_s.split('_')
|
|
34
|
-
json.set! type, "#{name_components.join('/')}-v1" if type
|
|
35
|
-
args = [yield] if block_given?
|
|
36
|
-
"#{self.class.name}::#{name_components.map { |str| capitalize_first_letter_and_leave_others_alone(str) }.join('::')}".constantize.new(json, @page).props(*args)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# TODO: Consider removing this in favour of non 'v1' suffix
|
|
40
|
-
def add_element_to_array_v1(type, name, *args)
|
|
41
|
-
json.child! do
|
|
42
|
-
add_singleton_element_v1 type, name, *args
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
31
|
end
|
|
47
32
|
|
|
48
33
|
class JsonUiElement
|
|
@@ -37,6 +37,9 @@ class Glib::JsonUi::ActionBuilder
|
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Deprecated: use `logics_set` instead. `logics_set` is a superset of this
|
|
41
|
+
# action (same `targetId`/`data`/`onSet`/`skipOnChange`, plus
|
|
42
|
+
# `conditionalData`, `targetIds`, `variables` and `cacheData`).
|
|
40
43
|
class Set < Action
|
|
41
44
|
string :targetId
|
|
42
45
|
hash :data
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
class Glib::JsonUi::ActionBuilder
|
|
2
2
|
module Logics
|
|
3
|
+
# The canonical setter action. Prefer this over the deprecated
|
|
4
|
+
# `components_set`: it supports plain static `data` as well as
|
|
5
|
+
# `conditionalData` (JsonLogic), `targetIds`, `variables` and `cacheData`.
|
|
3
6
|
class Set < Action
|
|
4
7
|
string :targetId
|
|
5
8
|
array :targetIds
|
|
@@ -36,7 +36,7 @@ module Glib
|
|
|
36
36
|
|
|
37
37
|
def method_missing(m, *args)
|
|
38
38
|
if @multiple
|
|
39
|
-
|
|
39
|
+
add_element_to_array 'action', m, *args
|
|
40
40
|
else
|
|
41
41
|
if !Rails.env.production? && @singleton_action_set
|
|
42
42
|
raise "Multiple actions called on a singleton ActionBuilder. " \
|
|
@@ -46,7 +46,7 @@ module Glib
|
|
|
46
46
|
end
|
|
47
47
|
@singleton_action_set = true
|
|
48
48
|
@singleton_action_name = m
|
|
49
|
-
|
|
49
|
+
add_singleton_element 'action', m, *args
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -496,6 +496,20 @@ class Glib::JsonUi::ViewBuilder
|
|
|
496
496
|
class BulkEdit2 < View
|
|
497
497
|
hash :import, required: [:submitUrl, :paramName]
|
|
498
498
|
|
|
499
|
+
# Override
|
|
500
|
+
def created
|
|
501
|
+
# bulkEdit2 is a form PEER, not a form child: it answers the same
|
|
502
|
+
# forms_submit action a form does (forms/directSubmit) and submits its
|
|
503
|
+
# rows itself to `import.submitUrl`. Inside a form, its repeated
|
|
504
|
+
# per-row input names would collide with the form's FormData scans
|
|
505
|
+
# (duplicate-name warnings, dirty-baseline pollution) while adding
|
|
506
|
+
# nothing to the form's submission -- so, like a nested form, reject
|
|
507
|
+
# the arrangement at render time.
|
|
508
|
+
if page.current_form
|
|
509
|
+
raise 'panels_bulkEdit2 is not allowed inside a form; place it as a sibling and target it with forms_submit targetId'
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
|
|
499
513
|
# Maximum number of data rows an uploaded file may contain; files with
|
|
500
514
|
# more rows are rejected with an error dialog. No limit when unset.
|
|
501
515
|
int :maxRows
|
|
@@ -533,6 +547,65 @@ class Glib::JsonUi::ViewBuilder
|
|
|
533
547
|
|
|
534
548
|
end
|
|
535
549
|
|
|
550
|
+
# The Surfguard members-import table: a fork of bulkEdit2's builder for
|
|
551
|
+
# glib-web-npm's bulkEditSg component (same skeleton — form-peer
|
|
552
|
+
# submission, per-row POSTs, viewCells/viewHeaders — plus the Surfguard
|
|
553
|
+
# parse inputs). Deliberately SG-specific; consolidate with BulkEdit2 when
|
|
554
|
+
# a second consumer proves which props generalize.
|
|
555
|
+
class BulkEditSg < View
|
|
556
|
+
hash :import, required: [:submitUrl, :paramName]
|
|
557
|
+
|
|
558
|
+
# Override
|
|
559
|
+
def created
|
|
560
|
+
# Same form-peer rule as bulkEdit2 (see its comment).
|
|
561
|
+
if page.current_form
|
|
562
|
+
raise 'panels_bulkEditSg is not allowed inside a form; place it as a sibling and target it with forms_submit targetId'
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
# Maximum number of data rows an uploaded file may contain; files with
|
|
567
|
+
# more rows are rejected with an error dialog. No limit when unset.
|
|
568
|
+
int :maxRows
|
|
569
|
+
|
|
570
|
+
array :viewHeaders
|
|
571
|
+
|
|
572
|
+
# string :paramNameForFormData
|
|
573
|
+
string :paramNameForRowId
|
|
574
|
+
action :onCellChange
|
|
575
|
+
action :onRowsLoad
|
|
576
|
+
action :onRowsSelect
|
|
577
|
+
action :onRowsSucceed
|
|
578
|
+
# Fires when non-importable rows drain out of the table on save (they
|
|
579
|
+
# never POST), so the page can keep its Loaded/Uploaded/Skipped
|
|
580
|
+
# arithmetic honest.
|
|
581
|
+
action :onRowsSkip
|
|
582
|
+
action :onSubmitStart
|
|
583
|
+
action :onSubmitEnd
|
|
584
|
+
|
|
585
|
+
# Surfguard file shape: CSV header => form field. The parser follows
|
|
586
|
+
# this map, so a header-spelling fix is a one-line server change.
|
|
587
|
+
hash :headerMap
|
|
588
|
+
array :requiredHeaders
|
|
589
|
+
string :statusFilter
|
|
590
|
+
|
|
591
|
+
def viewCells(values)
|
|
592
|
+
json.set! 'viewCells' do
|
|
593
|
+
values.each do |value|
|
|
594
|
+
value.call(page.view_builder)
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def dataRows(value)
|
|
600
|
+
json.dataRows value
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
def viewRows(value)
|
|
604
|
+
json.viewRows value
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
end
|
|
608
|
+
|
|
536
609
|
# deprecated
|
|
537
610
|
class BulkEdit < View
|
|
538
611
|
hash :nextPage
|
|
@@ -89,9 +89,9 @@ module Glib
|
|
|
89
89
|
|
|
90
90
|
def add_view(name, *args)
|
|
91
91
|
if @multiple
|
|
92
|
-
|
|
92
|
+
add_element_to_array 'view', name, *args
|
|
93
93
|
else
|
|
94
|
-
|
|
94
|
+
add_singleton_element 'view', name, *args
|
|
95
95
|
end
|
|
96
96
|
end
|
|
97
97
|
|
|
@@ -115,7 +115,11 @@ module Glib
|
|
|
115
115
|
include Builder::MouseEvents
|
|
116
116
|
|
|
117
117
|
string :id
|
|
118
|
-
string :key
|
|
118
|
+
string :key # Deprecated: use `reuseKey` instead.
|
|
119
|
+
# Controls whether the frontend reuses the underlying DOM/component when a
|
|
120
|
+
# view is re-rendered. Maps to Vue's `:key`: keep it stable to allow reuse,
|
|
121
|
+
# or change it to force a fresh render. Supersedes the deprecated `key`.
|
|
122
|
+
string :reuseKey
|
|
119
123
|
length :width
|
|
120
124
|
length :height
|
|
121
125
|
color :backgroundColor
|
|
@@ -15,7 +15,7 @@ page.form url: json_ui_garage_url(path: 'forms/basic_post'), method: 'post', pad
|
|
|
15
15
|
# form.fields_richText name: 'user[bio]', width: 'matchParent', label: 'Content', images: images, value: '<p>Test {{image1}}</p>'
|
|
16
16
|
|
|
17
17
|
json.child! do
|
|
18
|
-
json.view 'fields/newRichText
|
|
18
|
+
json.view 'fields/newRichText'
|
|
19
19
|
json.width 'matchParent'
|
|
20
20
|
json.label 'Content'
|
|
21
21
|
json.name 'user[bio]'
|
|
@@ -62,7 +62,7 @@ page.form url: json_ui_garage_url(path: 'forms/basic_post'), method: 'post', pad
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
# json.child! do
|
|
65
|
-
# json.view 'fields/richText
|
|
65
|
+
# json.view 'fields/richText'
|
|
66
66
|
# json.width 'matchParent'
|
|
67
67
|
# json.label 'Content'
|
|
68
68
|
# json.name 'user[bio]'
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
json.title 'Test Page (Duplicate Names)'
|
|
2
|
+
|
|
3
|
+
page = json_ui_page json
|
|
4
|
+
|
|
5
|
+
render 'json_ui/garage/test_page/header', json: json, page: page
|
|
6
|
+
|
|
7
|
+
# Contract: inputs sharing a name are only a problem when an action logic
|
|
8
|
+
# READS the shared name -- getFormData then yields an ambiguous (array)
|
|
9
|
+
# value. Repeated names that nothing reads are a legitimate pattern (e.g. a
|
|
10
|
+
# multi-file upload renders one hidden input per signed file, all sharing the
|
|
11
|
+
# field name), so they must never warn -- not at mount, not on input events,
|
|
12
|
+
# and not when a logic reads OTHER fields.
|
|
13
|
+
page.body(
|
|
14
|
+
childViews: ->(body) do
|
|
15
|
+
body.panels_responsive(
|
|
16
|
+
padding: glib_json_padding_body,
|
|
17
|
+
childViews: ->(res) do
|
|
18
|
+
res.h2 text: 'Duplicate field names in a form'
|
|
19
|
+
res.spacer height: 8
|
|
20
|
+
res.panels_form(
|
|
21
|
+
url: json_ui_garage_url(path: 'forms/generic_post'),
|
|
22
|
+
method: 'post',
|
|
23
|
+
childViews: ->(form) do
|
|
24
|
+
# Never read by any logic -- mirrors the multi-file upload shape.
|
|
25
|
+
form.fields_text width: 'matchParent', name: 'attachment_meta', label: 'Attachment meta 1', value: 'a'
|
|
26
|
+
form.fields_text width: 'matchParent', name: 'attachment_meta', label: 'Attachment meta 2', value: 'b'
|
|
27
|
+
# Read by the button below -- the ambiguous read must warn.
|
|
28
|
+
form.fields_text width: 'matchParent', name: 'user[level]', label: 'Level 1', value: 'one'
|
|
29
|
+
form.fields_text width: 'matchParent', name: 'user[level]', label: 'Level 2', value: 'two'
|
|
30
|
+
# A multi-value select renders one hidden input per selected
|
|
31
|
+
# value, all sharing the (bare, explicit) field name -- an
|
|
32
|
+
# INTENTIONAL duplicate that stays silent even when read (setEq
|
|
33
|
+
# exists precisely to compare such array values).
|
|
34
|
+
form.fields_select(
|
|
35
|
+
width: 'matchParent',
|
|
36
|
+
name: 'tags',
|
|
37
|
+
label: 'Tags',
|
|
38
|
+
multiple: true,
|
|
39
|
+
options: [
|
|
40
|
+
{ text: 'Tag A', value: 'a' },
|
|
41
|
+
{ text: 'Tag B', value: 'b' }
|
|
42
|
+
],
|
|
43
|
+
value: ['a', 'b']
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
)
|
|
47
|
+
res.spacer height: 8
|
|
48
|
+
res.button(
|
|
49
|
+
text: 'Check level',
|
|
50
|
+
onClick: ->(action) do
|
|
51
|
+
action.logics_run(
|
|
52
|
+
condition: { '==': [{ 'var': 'user[level]' }, 'one'] },
|
|
53
|
+
onTrue: ->(subaction) do
|
|
54
|
+
subaction.logics_set targetId: 'dup_status', data: { text: 'TRUE' }
|
|
55
|
+
end,
|
|
56
|
+
onFalse: ->(subaction) do
|
|
57
|
+
subaction.logics_set targetId: 'dup_status', data: { text: 'FALSE' }
|
|
58
|
+
end
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
)
|
|
62
|
+
res.spacer height: 8
|
|
63
|
+
res.label id: 'dup_status', text: 'PENDING'
|
|
64
|
+
res.spacer height: 8
|
|
65
|
+
res.button(
|
|
66
|
+
text: 'Check tags',
|
|
67
|
+
onClick: ->(action) do
|
|
68
|
+
action.logics_run(
|
|
69
|
+
condition: { 'setEq': [{ 'var': 'tags' }, %w[a b]] },
|
|
70
|
+
onTrue: ->(subaction) do
|
|
71
|
+
subaction.logics_set targetId: 'tags_status', data: { text: 'MATCH' }
|
|
72
|
+
end,
|
|
73
|
+
onFalse: ->(subaction) do
|
|
74
|
+
subaction.logics_set targetId: 'tags_status', data: { text: 'NO MATCH' }
|
|
75
|
+
end
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
)
|
|
79
|
+
res.spacer height: 8
|
|
80
|
+
res.label id: 'tags_status', text: 'PENDING'
|
|
81
|
+
end
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
json.title 'Test Page (Inputs Outside Form)'
|
|
2
|
+
|
|
3
|
+
page = json_ui_page json
|
|
4
|
+
|
|
5
|
+
render 'json_ui/garage/test_page/header', json: json, page: page
|
|
6
|
+
|
|
7
|
+
# Contract: inputs outside a glib form mount SILENTLY, named or not. Whether
|
|
8
|
+
# an outside-form input is a mistake depends on intent -- a named select that
|
|
9
|
+
# only navigates via onChange is legitimate; a field an action logic expects
|
|
10
|
+
# to read is a bug -- and intent is invisible at mount. So the warning fires
|
|
11
|
+
# at READ time instead: when an action logic (logics/run, logics/set
|
|
12
|
+
# conditions, fields/getValues) reads a name whose only input lives outside a
|
|
13
|
+
# glib form, i.e. exactly when the silent-read bug manifests.
|
|
14
|
+
page.body(
|
|
15
|
+
childViews: ->(body) do
|
|
16
|
+
body.panels_responsive(
|
|
17
|
+
padding: glib_json_padding_body,
|
|
18
|
+
childViews: ->(res) do
|
|
19
|
+
res.h2 text: 'Inputs outside a glib form'
|
|
20
|
+
res.label text: 'Every input below mounts silently. Warnings fire only when one of the ' \
|
|
21
|
+
'buttons makes an action logic read a field that no glib form can provide.'
|
|
22
|
+
res.spacer height: 12
|
|
23
|
+
res.hr width: 'matchParent'
|
|
24
|
+
res.spacer height: 12
|
|
25
|
+
|
|
26
|
+
res.h2 text: 'Named input (silent at mount, warns when read)'
|
|
27
|
+
res.spacer height: 8
|
|
28
|
+
res.fields_text name: 'orphan_named', width: 'matchParent'
|
|
29
|
+
|
|
30
|
+
res.spacer height: 16
|
|
31
|
+
res.hr width: 'matchParent'
|
|
32
|
+
res.spacer height: 16
|
|
33
|
+
|
|
34
|
+
# A navigation-style control: named, outside any form, consumed only
|
|
35
|
+
# through its own onChange -- never through form data. Must stay
|
|
36
|
+
# silent forever.
|
|
37
|
+
res.h2 text: 'Nav select (onChange only, never warns)'
|
|
38
|
+
res.spacer height: 8
|
|
39
|
+
res.fields_select(
|
|
40
|
+
id: 'nav_year',
|
|
41
|
+
name: 'nav_year',
|
|
42
|
+
label: 'Year',
|
|
43
|
+
width: 300,
|
|
44
|
+
options: [
|
|
45
|
+
{ text: '2025', value: '2025' },
|
|
46
|
+
{ text: '2026', value: '2026' }
|
|
47
|
+
],
|
|
48
|
+
value: '2026',
|
|
49
|
+
onChange: ->(action) do
|
|
50
|
+
action.logics_set targetId: 'nav_status', data: { text: 'NAVIGATED' }
|
|
51
|
+
end
|
|
52
|
+
)
|
|
53
|
+
res.spacer height: 8
|
|
54
|
+
res.label id: 'nav_status', text: 'IDLE'
|
|
55
|
+
|
|
56
|
+
res.spacer height: 16
|
|
57
|
+
res.hr width: 'matchParent'
|
|
58
|
+
res.spacer height: 16
|
|
59
|
+
|
|
60
|
+
res.h2 text: 'Nameless readOnly display (silent)'
|
|
61
|
+
res.spacer height: 8
|
|
62
|
+
res.fields_text value: 'https://example.com/copy-this-link', readOnly: true, width: 'matchParent'
|
|
63
|
+
|
|
64
|
+
res.spacer height: 16
|
|
65
|
+
res.hr width: 'matchParent'
|
|
66
|
+
res.spacer height: 16
|
|
67
|
+
|
|
68
|
+
res.h2 text: 'Action logics reading the orphan (warn at read time)'
|
|
69
|
+
res.spacer height: 8
|
|
70
|
+
res.panels_flow(
|
|
71
|
+
innerPadding: { bottom: 0 },
|
|
72
|
+
width: 'matchParent',
|
|
73
|
+
childViews: ->(flow) do
|
|
74
|
+
flow.button(
|
|
75
|
+
text: 'Read orphan (condition)',
|
|
76
|
+
onClick: ->(action) do
|
|
77
|
+
action.logics_run(
|
|
78
|
+
condition: { '==': [{ 'var': 'orphan_named' }, 'never'] },
|
|
79
|
+
onTrue: ->(subaction) do
|
|
80
|
+
subaction.logics_set targetId: 'orphan_condition_status', data: { text: 'TRUE' }
|
|
81
|
+
end,
|
|
82
|
+
onFalse: ->(subaction) do
|
|
83
|
+
subaction.logics_set targetId: 'orphan_condition_status', data: { text: 'FALSE' }
|
|
84
|
+
end
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
)
|
|
88
|
+
flow.spacer width: 8
|
|
89
|
+
flow.button(
|
|
90
|
+
text: 'Read orphan (set)',
|
|
91
|
+
onClick: ->(action) do
|
|
92
|
+
action.logics_set(
|
|
93
|
+
targetId: 'orphan_set_target',
|
|
94
|
+
conditionalData: {
|
|
95
|
+
'text' => { '??' => [{ 'var' => 'orphan_named' }, 'SET MISSING'] }
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
)
|
|
100
|
+
flow.spacer width: 8
|
|
101
|
+
flow.button(
|
|
102
|
+
text: 'Read orphan (getValues)',
|
|
103
|
+
onClick: ->(action) do
|
|
104
|
+
action.fields_getValues(
|
|
105
|
+
fieldNames: { 'orphan_named' => nil },
|
|
106
|
+
onGet: ->(subaction) do
|
|
107
|
+
subaction.logics_set targetId: 'orphan_get_status', data: { text: 'VALUES READ' }
|
|
108
|
+
end
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
)
|
|
114
|
+
res.spacer height: 8
|
|
115
|
+
res.label id: 'orphan_condition_status', text: 'PENDING'
|
|
116
|
+
res.spacer height: 4
|
|
117
|
+
res.label id: 'orphan_set_target', text: 'SET PENDING'
|
|
118
|
+
res.spacer height: 4
|
|
119
|
+
res.label id: 'orphan_get_status', text: 'GET PENDING'
|
|
120
|
+
end
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
)
|
|
@@ -16,6 +16,8 @@ page.body childViews: ->(body) do
|
|
|
16
16
|
method: 'post',
|
|
17
17
|
childViews: ->(form) do
|
|
18
18
|
form.fields_text width: 'matchParent', name: 'user[status]', label: 'First form status', value: 'first'
|
|
19
|
+
# Divergent collision that NO logic reads -- must stay silent.
|
|
20
|
+
form.fields_text width: 'matchParent', name: 'unread[shared]', label: 'First unread shared', value: 'a'
|
|
19
21
|
end
|
|
20
22
|
res.spacer height: 8
|
|
21
23
|
res.panels_form \
|
|
@@ -23,6 +25,7 @@ page.body childViews: ->(body) do
|
|
|
23
25
|
method: 'post',
|
|
24
26
|
childViews: ->(form) do
|
|
25
27
|
form.fields_text width: 'matchParent', name: 'user[status]', label: 'Second form status', value: 'second'
|
|
28
|
+
form.fields_text width: 'matchParent', name: 'unread[shared]', label: 'Second unread shared', value: 'b'
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
res.spacer height: 8
|
|
@@ -16,7 +16,7 @@ module Glib
|
|
|
16
16
|
raise "Submit action needs to be inside a form: #{http.router.last_log}" unless form
|
|
17
17
|
|
|
18
18
|
case form['view']
|
|
19
|
-
when 'panels/bulkEdit2
|
|
19
|
+
when 'panels/bulkEdit2'
|
|
20
20
|
method = 'post'
|
|
21
21
|
action = "forms/#{method}"
|
|
22
22
|
url = form['import']['submitUrl']
|
|
@@ -57,7 +57,7 @@ module Glib
|
|
|
57
57
|
fields << child
|
|
58
58
|
|
|
59
59
|
include_params = case name
|
|
60
|
-
when 'fields/check'
|
|
60
|
+
when 'fields/check'
|
|
61
61
|
child['checkValue'] == child['value']
|
|
62
62
|
else
|
|
63
63
|
true
|
|
@@ -67,14 +67,14 @@ module Glib
|
|
|
67
67
|
def step(http, args)
|
|
68
68
|
# TODO: Refactor
|
|
69
69
|
case args['view']
|
|
70
|
-
when 'fields/submit
|
|
70
|
+
when 'fields/submit'
|
|
71
71
|
@depth += 1
|
|
72
72
|
# forms = @visitor.forms
|
|
73
73
|
# JsonCrawler::FormsSubmit.new(http, args, forms.last)
|
|
74
74
|
JsonCrawler::FormsSubmit.new(http, args)
|
|
75
75
|
@depth -= 1
|
|
76
76
|
return
|
|
77
|
-
when 'panels/web
|
|
77
|
+
when 'panels/web'
|
|
78
78
|
# A panels/web embeds content by URL in an inline viewer -- a file
|
|
79
79
|
# preview (PDF/image), an inline HTML preview, etc. There's no onClick
|
|
80
80
|
# action to catch here, so when the URL is one of our own (same-host)
|
|
@@ -83,7 +83,7 @@ module Glib
|
|
|
83
83
|
# (it replays each per user and snapshots the response). We only record
|
|
84
84
|
# (no fetch); external embeds are filtered out by internal_url?.
|
|
85
85
|
url = args['url']
|
|
86
|
-
http_actions.add(['panels/web
|
|
86
|
+
http_actions.add(['panels/web', url]) if url.present? && internal_url?(url)
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
if args.is_a?(Hash) && args['rel'] != 'nofollow'
|
|
@@ -112,10 +112,9 @@ module Glib
|
|
|
112
112
|
# @read_only_actions.add([action, params['url']])
|
|
113
113
|
http_actions.add([action, params['url']])
|
|
114
114
|
JsonCrawler::NavInitiate.new(http, params, action)
|
|
115
|
-
when 'runMultiple
|
|
115
|
+
when 'runMultiple'
|
|
116
116
|
JsonCrawler::RunMultiple.new(http, params, action)
|
|
117
|
-
when 'windows/open
|
|
118
|
-
'dialogs/open', 'windows/reload', 'windows/openWeb', 'windows/openWeb-v1'
|
|
117
|
+
when 'windows/open', 'dialogs/open', 'windows/reload', 'windows/openWeb'
|
|
119
118
|
if allowed?(params['url'])
|
|
120
119
|
# @read_only_actions.add([action, params['url']])
|
|
121
120
|
http_actions.add([action, params['url']])
|
|
@@ -141,24 +140,22 @@ module Glib
|
|
|
141
140
|
http_actions.add([action, params['url']]) if internal_url?(params['url'])
|
|
142
141
|
self.log action, params['url']
|
|
143
142
|
end
|
|
144
|
-
when 'dialogs/show
|
|
143
|
+
when 'dialogs/show', 'popovers/show'
|
|
145
144
|
JsonCrawler::DialogsShow.new(http, params, action)
|
|
146
|
-
when 'sheets/select
|
|
145
|
+
when 'sheets/select'
|
|
147
146
|
JsonCrawler::Menu.new(http, params, action)
|
|
148
|
-
when 'http/post
|
|
147
|
+
when 'http/post'
|
|
149
148
|
JsonCrawler::ActionHttp.new(:post, http, params, action)
|
|
150
|
-
when 'forms/submit
|
|
149
|
+
when 'forms/submit'
|
|
151
150
|
# forms = @visitor.forms
|
|
152
151
|
# JsonCrawler::FormsSubmit.new(http, params, forms.last)
|
|
153
152
|
JsonCrawler::FormsSubmit.new(http, params)
|
|
154
|
-
when 'dialogs/alert
|
|
153
|
+
when 'dialogs/alert'
|
|
155
154
|
JsonCrawler::DialogsAlert.new(http, params, action)
|
|
156
|
-
when 'dialogs/close
|
|
155
|
+
when 'dialogs/close', 'popovers/close'
|
|
157
156
|
JsonCrawler::DialogsClose.new(http, params, action)
|
|
158
157
|
else
|
|
159
158
|
unless [
|
|
160
|
-
'http/delete-v1',
|
|
161
|
-
'dialogs/oauth-v1',
|
|
162
159
|
'http/delete',
|
|
163
160
|
'dialogs/oauth'
|
|
164
161
|
].include?(
|
|
@@ -271,13 +268,13 @@ module Glib
|
|
|
271
268
|
params ||= {}
|
|
272
269
|
|
|
273
270
|
case action.to_s.downcase
|
|
274
|
-
when 'http/post
|
|
271
|
+
when 'http/post', 'forms/post'
|
|
275
272
|
http.post(url, action, params)
|
|
276
|
-
when 'http/patch
|
|
273
|
+
when 'http/patch', 'forms/patch'
|
|
277
274
|
http.patch(url, action, params)
|
|
278
|
-
when 'http/put
|
|
275
|
+
when 'http/put', 'forms/put'
|
|
279
276
|
http.put(url, action, params)
|
|
280
|
-
when 'http/delete
|
|
277
|
+
when 'http/delete'
|
|
281
278
|
http.delete(url, action, params)
|
|
282
279
|
else
|
|
283
280
|
http.get(url, action, params)
|
data/lib/glib/test_helpers.rb
CHANGED
|
@@ -232,7 +232,7 @@ module Glib
|
|
|
232
232
|
view.is_a?(Hash) && (
|
|
233
233
|
view['view']&.start_with?('panels/form') ||
|
|
234
234
|
view['view']&.start_with?('panels/fullPageForm') ||
|
|
235
|
-
view['view'] == 'panels/bulkEdit2
|
|
235
|
+
view['view'] == 'panels/bulkEdit2' ||
|
|
236
236
|
view['view'] == 'panels/splitForm'
|
|
237
237
|
)
|
|
238
238
|
end
|
|
@@ -247,7 +247,7 @@ module Glib
|
|
|
247
247
|
data_prefixes = data.keys.map { |k| "#{k}[" }
|
|
248
248
|
|
|
249
249
|
__extract_form_fields(form).each do |field|
|
|
250
|
-
next unless field[:view] == 'fields/hidden'
|
|
250
|
+
next unless field[:view] == 'fields/hidden'
|
|
251
251
|
next if data_prefixes.any? { |prefix| field[:name]&.start_with?(prefix) }
|
|
252
252
|
|
|
253
253
|
params[field[:name]] = field[:value]
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: glib-web
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 6.
|
|
4
|
+
version: 6.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activestorage
|
|
@@ -432,6 +432,7 @@ files:
|
|
|
432
432
|
- app/views/json_ui/garage/test_page/dialog.json.jbuilder
|
|
433
433
|
- app/views/json_ui/garage/test_page/dialog_open.json.jbuilder
|
|
434
434
|
- app/views/json_ui/garage/test_page/dirty_state.json.jbuilder
|
|
435
|
+
- app/views/json_ui/garage/test_page/duplicate_names.json.jbuilder
|
|
435
436
|
- app/views/json_ui/garage/test_page/fields.json.jbuilder
|
|
436
437
|
- app/views/json_ui/garage/test_page/fields_captcha.json.jbuilder
|
|
437
438
|
- app/views/json_ui/garage/test_page/fields_creditCard.json.jbuilder
|
|
@@ -465,6 +466,7 @@ files:
|
|
|
465
466
|
- app/views/json_ui/garage/test_page/horizontal.json.jbuilder
|
|
466
467
|
- app/views/json_ui/garage/test_page/http.json.jbuilder
|
|
467
468
|
- app/views/json_ui/garage/test_page/image.json.jbuilder
|
|
469
|
+
- app/views/json_ui/garage/test_page/inputs_outside_form.json.jbuilder
|
|
468
470
|
- app/views/json_ui/garage/test_page/lifecycle.json.jbuilder
|
|
469
471
|
- app/views/json_ui/garage/test_page/list.json.jbuilder
|
|
470
472
|
- app/views/json_ui/garage/test_page/list_editable.json.jbuilder
|