1
1
from typing import Any , Dict , Iterable , Optional , TypeVar
2
2
3
3
from django .db import models
4
- from django .utils .html import escape , mark_safe
4
+ from django .utils .html import escape , escapejs , mark_safe
5
5
6
6
from cms .plugin_rendering import ContentRenderer
7
7
from rest_framework import serializers
@@ -70,55 +70,94 @@ def render_cms_plugin(
70
70
return serializer_cls (plugin_instance , context = context ).data
71
71
72
72
73
+ # Template for a collapsable key-value pair
74
+ DETAILS_TEMPLATE = (
75
+ '<details open><summary><span class="key">"{key}"</span>: {open}</summary>'
76
+ '<div class="indent">{value}</div></details>{close}<span class="sep">,</span>'
77
+ )
78
+
79
+ # Template for a collapsable object/list
80
+ OBJ_TEMPLATE = (
81
+ '<details open><summary>{open}</summary>'
82
+ '<div class="indent">{value}</div></details>{close}<span class="sep">,</span>'
83
+ )
84
+
85
+ # Tempalte for a single line key-value pair
86
+ SIMPLE_TEMPLATE = (
87
+ '<span class="key">"{key}"</span>: {value}<span class="sep">,</span>'
88
+ )
89
+
90
+ def escapestr (s : str ) -> str :
91
+ """
92
+ Escape a string for safe HTML rendering.
93
+ """
94
+ return escape (s ).replace ('"' , '\"' ).replace ('\n ' , '\n' )
95
+
96
+
73
97
def highlight_data (json_data : Any ) -> str :
74
98
"""
75
99
Highlight JSON data using Pygments.
76
100
"""
77
101
if isinstance (json_data , str ):
78
- classes = "str "
79
- if len (json_data ) > 100 :
80
- classes += " ellipsis"
81
- return f'<span class="{ classes } ">"{ escape (json_data )} "</span>'
102
+ ellipsis = ""
103
+ if len (json_data ) > 60 :
104
+ return f'<span class="str">"<span class=" ellipsis"> { escapestr ( json_data ) } </span>"</span>'
105
+ return f'<span class="str ">"{ escapestr (json_data )} "</span>'
82
106
if isinstance (json_data , (int , float )):
83
107
return f'<span class="num">{ json_data } </span>'
84
108
if isinstance (json_data , bool ):
85
109
return f'<span class="bool">{ str (json_data ).lower ()} </span>'
86
110
if json_data is None :
87
111
return '<span class="null">null</span>'
88
112
if isinstance (json_data , dict ):
89
- return highlight_json (json_data )
113
+ return OBJ_TEMPLATE . format ( ** highlight_json (json_data )) if json_data else '{}'
90
114
if isinstance (json_data , list ):
91
- return highlight_list (json_data )
115
+ return OBJ_TEMPLATE . format ( ** highlight_list (json_data )) if json_data else '[]'
92
116
93
117
return f'<span class="obj">{ json_data } </span>'
94
118
95
119
96
120
def highlight_json (
97
121
json_data : Dict [str , Any ], children : Iterable | None = None , field : str = "children"
98
- ) -> str :
122
+ ) -> dict [ str , str ] :
99
123
has_children = children is not None
100
124
if field in json_data :
101
125
del json_data [field ]
102
126
103
- if not json_data and not has_children :
104
- return "{}"
105
127
items = [
106
- f'<div class="js-kvp"><span class="toggle"></span><span class="key">"{ escape (key )} "</span>: '
107
- f'{ highlight_data (value )} <span class="sep">,</span></div>'
128
+ DETAILS_TEMPLATE .format (
129
+ key = escape (key ),
130
+ value = highlight_data (value ),
131
+ open = '' ,
132
+ close = '' ,
133
+ ) if isinstance (value , (dict , list )) and value else SIMPLE_TEMPLATE .format (
134
+ key = escape (key ),
135
+ value = highlight_data (value ),
136
+ )
108
137
for key , value in json_data .items ()
109
138
]
110
139
if has_children :
111
- rendered_children = (
112
- f'<div class="js-kvp"><span class="toggle"></span><span class="children">"{ field } "</span>: '
113
- f'[<div class="indent">{ ", " .join (children )} </div></div>]'
140
+ rendered_children = DETAILS_TEMPLATE .format (
141
+ key = escape (field ),
142
+ value = '' .join (children ),
143
+ open = '[' ,
144
+ close = ']' ,
114
145
)
115
146
items .append (rendered_children )
116
- return f'{{<div class="indent">{ "" .join (items )} </div>}}'
147
+ return {
148
+ "open" : '{' ,
149
+ "close" : '}' ,
150
+ "value" : "<br>" .join (items ),
151
+ }
117
152
118
153
119
- def highlight_list (json_data : list ) -> str :
154
+ def highlight_list (json_data : list ) -> dict [ str , str ] :
120
155
items = [highlight_data (item ) for item in json_data ]
121
- return f'[<div class="indent">{ ",<br>" .join (items )} </div>]'
156
+ return {
157
+ "open" : '[' ,
158
+ "close" : ']' ,
159
+ "value" : '' .join (items ),
160
+ }
122
161
123
162
124
163
class RESTRenderer (ContentRenderer ):
@@ -140,7 +179,7 @@ def render_plugin(
140
179
)
141
180
for child in getattr (instance , "child_plugin_instances" , [])
142
181
] or None
143
- content = highlight_json (data , children = children )
182
+ content = OBJ_TEMPLATE . format ( ** highlight_json (data , children = children ) )
144
183
145
184
if editable :
146
185
content = self .plugin_edit_template .format (
@@ -169,11 +208,13 @@ def render_plugins(
169
208
render_plugins = False ,
170
209
).data
171
210
172
- yield highlight_json (
173
- placeholder_data ,
174
- children = super ().render_plugins (
175
- placeholder , language , context , editable = editable , template = template
176
- ),
177
- field = "content" ,
211
+ yield OBJ_TEMPLATE .format (
212
+ ** highlight_json (
213
+ placeholder_data ,
214
+ children = super ().render_plugins (
215
+ placeholder , language , context , editable = editable , template = template
216
+ ),
217
+ field = "content" ,
218
+ )
178
219
)
179
220
yield "</div>"
0 commit comments