diff --git a/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-resources/src/main/resources/flamingo/editclass.vm b/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-resources/src/main/resources/flamingo/editclass.vm index 9722e77f9047..87b6da84fdcf 100644 --- a/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-resources/src/main/resources/flamingo/editclass.vm +++ b/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-resources/src/main/resources/flamingo/editclass.vm @@ -60,12 +60,16 @@ $xwiki.jsfx.use('js/xwiki/editors/dataeditors.js', true)## #macro (displayProperty $field)
- $services.icon.renderHTML('caret-down') -

- $!escapetool.xml($doc.displayView($field.xWikiClass.get('prettyName'), "${field.name}_" , $field)) - ($doc.displayView($field.xWikiClass.get('name'), "${field.name}_" , $field): +

+

+ +
#set ($deletePropURL = $doc.getURL('propdelete', $escapetool.url({ 'propname': $field.name, @@ -73,7 +77,7 @@ $xwiki.jsfx.use('js/xwiki/editors/dataeditors.js', true)## }))) - $services.icon.renderHTML('cross') + $services.icon.renderHTML('trash') $services.localization.render( 'core.editors.class.deleteProperty.text')
@@ -176,8 +180,13 @@ $xwiki.jsfx.use('js/xwiki/editors/dataeditors.js', true)##
- $services.icon.renderHTML('caret-down') -

${escapetool.xml($class.name)}

+

+ +

#addPropertyForm()
diff --git a/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-resources/src/main/resources/flamingo/editobject.vm b/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-resources/src/main/resources/flamingo/editobject.vm index 4efda110a452..9c53fcc7ae73 100644 --- a/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-resources/src/main/resources/flamingo/editobject.vm +++ b/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-resources/src/main/resources/flamingo/editobject.vm @@ -39,13 +39,17 @@
#getFieldSummary($obj $firstField $firstFieldSummary)
- $services.icon.renderHTML('caret-down') -

#cleanClassname(${class.name}) ${obj.number}#if ($firstField): $firstFieldSummary#end - $services.icon.renderHTML('cross') - #if (!$hasCustomObject) - $services.icon.renderHTML('pencil') - #end +

+

+ $services.icon.renderHTML('trash') + #if (!$hasCustomObject) + $services.icon.renderHTML('pencil') + #end
#if ($isNew == "true") @@ -89,7 +93,7 @@
+ title="$escapetool.xml($services.localization.render('core.editors.object.removeDeprecatedProperties.link.tooltip'))"> $services.icon.renderHTML('cross') $services.localization.render('core.editors.object.removeDeprecatedProperties.link') @@ -152,8 +156,13 @@ #set ($object = $doc.getObject($class.name, $customObject)) #if ($object)
- $services.icon.renderHTML('caret-down') -

${escapetool.xml($class.name)}

+

+ +

#displayObject($object $class $props $titleField) @@ -173,9 +182,13 @@ #end #set ($objectCount = $objects.size())
- $services.icon.renderHTML('caret-down') -

$services.localization.render('core.editors.object.objectsForClass', [${escapetool.xml($class.name)}]) - ($objectCount) +

+

@@ -195,7 +208,7 @@ #macro(displayAddObject $class) #end @@ -247,7 +260,7 @@ #if ($mustSync) #end #end ## checkPropertyDeprecation @@ -355,7 +368,7 @@ #set ($fullEditorURL = "$doc.getURL('edit', 'editor=object')") #end #set ($redirect = $xwiki.relativeRequestURL) diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/ApplicationResources.properties b/xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/ApplicationResources.properties index 5e39c4ab3554..6f66a883fc2d 100644 --- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/ApplicationResources.properties +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/ApplicationResources.properties @@ -1298,6 +1298,7 @@ core.editors.object.newObjectForClass.tooltip=New {0} object core.editors.object.editAllObjects=\u00ABEdit all the objects defined in this page core.editors.object.editSingleObject=[Edit only this object] core.editors.object.editSingleObject.tooltip=Edit only this object +core.editors.object.toggleDisplay.tooltip=Toggle detail displaying for the object: core.editors.object.removeObject=[Remove this object] core.editors.object.removeObject.tooltip=Remove this object core.editors.object.invalidPropertyName=No such property: {0} @@ -1321,6 +1322,8 @@ core.editors.object.removeDeprecatedProperties.failed=Failed to remove deprecate core.editors.class.title=Editing class {0} core.editors.class.switchClass=Edit another class core.editors.class.switchClass.confirm=Do you want to save this class before leaving the editor? +core.editors.class.toggleDisplay.tooltip=Toggle detail displaying for the class: +core.editors.class.property.toggleDisplay.tooltip=Toggle detail displaying for the property: core.editors.class.addProperty.name.label=Add new property core.editors.class.addProperty.type.label=Type core.editors.class.addProperty.submit=Add @@ -1328,7 +1331,7 @@ core.editors.class.addProperty.inProgress=Adding property... core.editors.class.addProperty.done=Property added core.editors.class.addProperty.failed=Failed: -core.editors.class.moveProperty.handle.label=Drag and drop to change the order +core.editors.class.moveProperty.handle.label=Drag and drop or use up/down arrows to change the order of this property. core.editors.class.deleteProperty.text=delete core.editors.class.deleteProperty.tooltip=Delete property {0} diff --git a/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-war/src/main/webapp/resources/js/xwiki/editors/dataeditors.css b/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-war/src/main/webapp/resources/js/xwiki/editors/dataeditors.css index 099b50c55639..e5a0459c5db2 100644 --- a/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-war/src/main/webapp/resources/js/xwiki/editors/dataeditors.css +++ b/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-war/src/main/webapp/resources/js/xwiki/editors/dataeditors.css @@ -13,13 +13,6 @@ color: $theme.textPrimaryColor; } -#body .xclass-title h2 span { - border: none; - color: inherit; - display: inline; - margin: 0; - position: static; -} div#xwikiobjects input[type="checkbox"], div#xwikiclassproperties input[type="checkbox"], div#xwikiobjects input[type="radio"], div#xwikiclassproperties input[type="radio"] { @@ -35,11 +28,13 @@ div#xwikiobjects input[type="radio"], div#xwikiclassproperties input[type="radio /* Generic links */ .more-actions { padding: 8px 2px; + display: flex; + align-items: center; } .more-actions .edit-all { display: block; - padding: 2px 18px; + padding: .2rem .5rem; } /* Object editor */ @@ -56,10 +51,6 @@ div#xwikiobjects input[type="radio"], div#xwikiclassproperties input[type="radio padding: 0; } -.xclass-content .xobject-title h3 { - padding: 0; -} - .xclass-content { margin: 0 0 0 16px; } @@ -72,12 +63,6 @@ div#xwikiobjects input[type="radio"], div#xwikiclassproperties input[type="radio background: transparent; } -.xobject:hover .xobject-title, -.xproperty:hover .xproperty-title { - background-color: $theme.highlightColor; - color: $theme.textPrimaryColor; -} - .xobject-title h3 { border: none; font-size: 100%; @@ -94,34 +79,24 @@ div#xwikiobjects input[type="radio"], div#xwikiclassproperties input[type="radio margin-bottom: 2px; } -/* XProperty */ -.xobject dl, .xproperty dl { - margin: 0; -} - -.xobject-title:hover, .xclass-title:hover, .xproperty-title:hover { - cursor: default; -} - -.xobject-action { - display: block; - position: absolute; - top: 2px; -} - .xobject-title .delete { - right: 0; color: $theme.notificationErrorColor; } -.xobject-title .edit { - right: 16px; +.xobject-content xdt { + font-size: 75%; + font-weight: bold; +} + +/* XProperty and XObjects */ +.xobject dl, .xproperty dl { + margin: 0; } /* XProperty definition */ .xproperty { border: none; - margin-top: .5em; + margin-top: .1rem; } .xproperty-content { @@ -143,20 +118,33 @@ div#xwikiobjects input[type="radio"], div#xwikiclassproperties input[type="radio width: 2em; } -.xproperty-title h2 { +.xproperty-title h3 { display: block; padding: 2px 20px 2px 18px; margin: 0; font-size: 1em; font-weight: 100; + min-height: 16px; +} + +/* We want to keep the property displays relatively compact. We reduce the default padding and margins. */ +.xproperty-title h3 button { + padding-bottom: 0; + padding-top: 0; +} + +/* Default state for the icon representing the property. */ +.xproperty-title h3 button .iconHolder { background: transparent url("$xwiki.getSkinFile('icons/datamodel/propertyType-Generic.png')") 0 center no-repeat; + display: inline-block; + min-width: 16px; min-height: 16px; - flex-grow: 1; + vertical-align: middle; } .xproperty-title label { font-size: 60%; - line-height: 1em; + line-height: 1rem; } .xproperty-content { @@ -168,21 +156,53 @@ div#xwikiobjects input[type="radio"], div#xwikiclassproperties input[type="radio clear: both; } -.xobject-content xdt { - font-size: 75%; - font-weight: bold; -} - /* Expand/collapse */ /* We need to increase the priority of the rule in order to override Toucan styles */ -#body .xobject > .xobject-title, #body .xclass > .xclass-title, +#body .xobject > .xobject-title, #body .xproperty > .xproperty-title { display: flex; gap: .5em; align-items: baseline; } +.xclass-title h2, +.xclass-content .xobject-title h3, +.xproperty .xproperty-title h3 { + flex-grow: 1; + padding: 0; +} + +.xclass-title h2 button, +.xobject-title h3 button, +.xproperty-title h3 button { + border: none; + margin: 0; + background: transparent; + width: 100%; + text-align: left; + /* We overwrite the small font size that is set with btn-xs. */ + font-size: 1em; +} + +.xclass-title h2 button:hover, +.xclass-title h2 button:focus, +.xobject .xobject-title:hover h3 button, +.xobject .xobject-title:focus-within h3 button, +.xproperty .xproperty-title:hover h3 button, +.xproperty .xproperty-title:focus-within h3 button { + border: none; + background-color: $theme.highlightColor; + color: $theme.textPrimaryColor; +} + +.xclass-title h2 button:active, +.xobject .xobject-title h3 button:active, +.xproperty .xproperty-title h3 button:active { + border: none; + box-shadow: none; +} + /* Before Javascript, the collapsable and collapsed classes are not here. In order to avoid flickering, we need to take into account this initial state in the style rulesets. The default state is collapsed for xobjects and xproperties, but expanded for xclasses. */ @@ -198,16 +218,18 @@ The default state is collapsed for xobjects and xproperties, but expanded for xc display: inherit; } -.xclass.collapsed > .xclass-title > .toggle-collapsable > *, -.xobject > .xobject-title > .toggle-collapsable > *, -.xproperty > .xproperty-title > .toggle-collapsable > * { +.xclass.collapsed > .xclass-title .toggle-collapsable > *, +.xobject > .xobject-title .toggle-collapsable > *, +.xproperty > .xproperty-title .toggle-collapsable > * { transform: rotate(-90deg); + transition: transform .2s ease-out; } -.xclass.collapsable:not(.collapsed) > .xclass-title > .toggle-collapsable > *, -.xobject.collapsable:not(.collapsed) > .xobject-title > .toggle-collapsable > *, -.xproperty.collapsable:not(.collapsed) > .xproperty-title > .toggle-collapsable > * { +.xclass.collapsable:not(.collapsed) > .xclass-title .toggle-collapsable > *, +.xobject.collapsable:not(.collapsed) > .xobject-title .toggle-collapsable > *, +.xproperty.collapsable:not(.collapsed) > .xproperty-title .toggle-collapsable > * { transform: rotate(0); + transition: transform .2s ease-out; } /* Disabled/Deprecated */ @@ -219,7 +241,9 @@ The default state is collapsed for xobjects and xproperties, but expanded for xc font-style: italic; } -.disabled .xproperty-title h2, #body #xwikiclassproperties .disabled dt label, #body #xwikiobjects dt.disabled label { +.disabled .xproperty-title h3, +#body #xwikiclassproperties .disabled dt label, +#body #xwikiobjects dt.disabled label { color: $theme.textColor; } @@ -235,19 +259,20 @@ The default state is collapsed for xobjects and xproperties, but expanded for xc } .xproperty-title .tools { - align-self: center; + display: inline-flex; + align-items: baseline; } .xproperty-title .tools .tool { width: 16px; height: 16px; - float: left; background: transparent none center no-repeat; } .xproperty-title .tools .move { cursor: move; color: $theme.linkColor; + margin: 0; } .xproperty-title .tools .delete { @@ -259,51 +284,51 @@ The default state is collapsed for xobjects and xproperties, but expanded for xc } /* Property type icons */ -.StringClass h2 { +.StringClass h3 button .iconHolder { background-image: url("$xwiki.getSkinFile('icons/datamodel/string.png')"); } -.DateClass h2 { +.DateClass h3 button .iconHolder { background-image: url("$xwiki.getSkinFile('icons/datamodel/calendar.png')"); } -.TextAreaClass h2 { +.TextAreaClass h3 button .iconHolder { background-image: url("$xwiki.getSkinFile('icons/datamodel/textarea.png')"); } -.NumberClass h2 { +.NumberClass h3 button .iconHolder { background-image: url("$xwiki.getSkinFile('icons/datamodel/number.png')"); } -.BooleanClass h2 { +.BooleanClass h3 button .iconHolder { background-image: url("$xwiki.getSkinFile('icons/datamodel/boolean.png')"); } -.StaticListClass h2 { +.StaticListClass h3 button .iconHolder { background-image: url("$xwiki.getSkinFile('icons/datamodel/staticlist.png')"); } -.GroupsClass h2 { +.GroupsClass h3 button .iconHolder { background-image: url("$xwiki.getSkinFile('icons/datamodel/groups.png')"); } -.UsersClass h2 { +.UsersClass h3 button .iconHolder { background-image: url("$xwiki.getSkinFile('icons/datamodel/users.png')"); } -.LevelsClass h2 { +.LevelsClass h3 button .iconHolder { background-image: url("$xwiki.getSkinFile('icons/datamodel/rights.png')"); } -.DBListClass h2 { +.DBListClass h3 button .iconHolder { background-image: url("$xwiki.getSkinFile('icons/datamodel/database.png')"); } -.DBTreeListClass h2 { +.DBTreeListClass h3 button .iconHolder { background-image: url("$xwiki.getSkinFile('icons/datamodel/tree.png')"); } -.PasswordClass h2 { +.PasswordClass h3 button .iconHolder { background-image: url("$xwiki.getSkinFile('icons/datamodel/password.png')"); } diff --git a/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-war/src/main/webapp/resources/js/xwiki/editors/dataeditors.js b/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-war/src/main/webapp/resources/js/xwiki/editors/dataeditors.js index 9b3b0b38196a..9d0a466d7265 100644 --- a/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-war/src/main/webapp/resources/js/xwiki/editors/dataeditors.js +++ b/xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-war/src/main/webapp/resources/js/xwiki/editors/dataeditors.js @@ -652,10 +652,10 @@ editors.XDataEditors = Class.create({ if (objectContent.childElementCount === 0) { object.addClassName('collapsed'); } - var objectTitle = object.down('.xobject-title'); + var objectToggle = object.down('.xobject-title h3 button'); var xclassName = this.getXClassNameFromXObjectId(object.id); var xObjectNumber = this.getXObjectNumberFromXObjectId(object.id); - objectTitle.observe('click', function(event) { + objectToggle.observe('click', function() { var isAlreadyLoaded = objectContent.childElementCount > 0; if (!isAlreadyLoaded && !object.hasClassName('loading')) { object.addClassName('loading'); @@ -750,15 +750,46 @@ editors.XDataEditors = Class.create({ }); // Create and insert move button element.select('.xproperty-title .tools').each(function(item) { - var movebutton = new Element('span', { - 'class': 'tool move', + var movebutton = new Element('button', { + 'class': 'btn btn-secondary btn-xs tool move', title: $jsontool.serialize($services.localization.render('core.editors.class.moveProperty.handle.label')) - }).update($jsontool.serialize($services.icon.renderHTML('reposition'))); + }).update('' + + $jsontool.serialize($services.localization.render('core.editors.class.moveProperty.handle.label')) + + '' + + $jsontool.serialize($services.icon.renderHTML('reposition'))); item.makePositioned(); item.appendChild(movebutton); movebutton.observe('click', function(event) { event.stop(); }.bindAsEventListener()); + // Extend the Sortable items with arrow keys support + movebutton.observe('keydown', function(event){ + // We only do something when the key is ArrowUp (keyCode: 38) or ArrowDown (keyCode: 40) + if (![38, 40].includes(event.keyCode)) { return; } + var sequence = Sortable.sequence($('xclassContent')); + var sequenceLength = sequence.length; + var item = event.target.parentElement.parentElement.parentElement; + /* We need to recompute the name of the entry similarly to what's done in scriptaculous + to make sure it matches the one we get in the sequence. + See https://github.com/madrobby/scriptaculous/blob/b0a8422f7f6f2e2e17f0d5ddfef1d9a6f5428472/src/dragdrop.js#L592 + for the source of the format regex. */ + let format = /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/; + let currentElementName = item.id.match(format) ? item.id.match(format)[1] : ''; + let currentElementIndex = sequence.indexOf(currentElementName); + let swapWithIndex; + if (event.keyCode === 38) { // UP ARROW + swapWithIndex = (currentElementIndex + sequenceLength - 1) % sequenceLength; + } else if (event.keyCode === 40) { // DOWN ARROW + swapWithIndex = (currentElementIndex + sequenceLength + 1) % sequenceLength; + } + [ sequence[currentElementIndex], sequence[swapWithIndex] ] = [ sequence[swapWithIndex], sequence[currentElementIndex] ]; + /* We update the content of the sortable object.*/ + Sortable.setSequence($('xclassContent'), sequence); + /* We make sure to trigger the listener that should be called with such an update. */ + Sortable.sortables['xclassContent'].onUpdate(Sortable.sortables['xclassContent'].containment); + /* We refocus the element, so that the focus isn't lost when moving it. */ + event.target.focus(); + }.bindAsEventListener()); }); // Attach behavior to the move buttons Sortable.create($('xclassContent'), {