Commit 84b5f7ce authored by Romain Courteaud's avatar Romain Courteaud

erp5_run_my_doc: add slideshow editor gadget

This gadget only works in ERP5JS, as it depends on multiple ERP5JS only gadgets.

Some functionnalities have not been backported for now:
* image upload, which seems broken on xhtml style
* test template (I was not able to trigger something from the UI)

Normally, it could be possible to create an OfficeJS app with this editor.
parent fb57726c
Pipeline #10010 failed with stage
in 0 seconds
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_view</string>
<string>action_type/object_onlyxhtml_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
<value> <string>object_onlyxhtml_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_onlyjio_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_onlyjio_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Javascript SlideShow editor (modifies the order of the slide show)</string> </value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>view_slideshow_editor_with_gadget</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>Modify portal content</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Action Information</string> </value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>1.5</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Edit Slideshow</string> </value>
Please register or sign in to reply
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/TestPage_viewEditSlideShowWithGadget</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5 Form" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string>Base_edit</string> </value>
</item>
<item>
<key> <string>action_title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left (Page Properties)</string>
<string>right (Publication)</string>
<string>center</string>
<string>bottom</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list>
<string>my_text_content</string>
</list>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left (Page Properties)</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>right (Publication)</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>TestPage_viewEditSlideShowWithGadget</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>TestPage_viewEditSlideShowWithGadget</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_view</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Edit SlideShow</string> </value>
  • I think this is where this failure comes from

    ======================================================================
    FAIL: testNamingConvention (testERP5NamingConvention.TestNamingConvention)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/srv/slapgrid/slappart5/srv/testnode/aai/soft/77c7b7765fa9d9ae8d801d6601052311/parts/erp5/Products/ERP5Type/tests/testERP5NamingConvention.py", line 120, in testNamingConvention
        self.assertEqual(0, len(final_result_list), "\n".join(final_result_list))
    AssertionError: erp5_run_my_doc/TestPage_viewEditSlideShowWithGadget : Title of the Form itself : 'SlideShow' is not titlecased
    

    What's the proper term ? Slideshow, Slide Show or SlideShow ?

    BTW, in bt5/erp5_corporate_identity/ActionTemplateItem/portal_types/Web%20Page/export_slideshow.xml we used "Export as SlideShow", but this is not covered by testERP5NamingConvention ( most new business templates are not covered actually. I looked a bit how we could make testERP5NamingConvention run as part of test coding style, but this would need a little work, I believe we should start by rewriting ERP5Site_checkNamingConventions by BusinessTemplate_checkNamingConventions to show only errors on one business templates... anyway, several bt are not covered in testNamingConvention at the moment and it would take a few days to make testNamingConvention run in test coding style )

  • What's the proper term ? Slideshow, Slide Show or SlideShow ?

    according to wikipedia page for "Slide show"

    When referring to the video or computer-based visual equivalent, in which the slides are not individual physical objects, the term is often written as one word, slideshow.[citation needed]

    https://forum.wordreference.com/threads/slide-show-or-slideshow.1868132/ or https://www.dictionary.com/browse/slide--show says both "slide show" and "slideshow" are OK. At least we should not use SlideShow I believe.

  • For today, I changed to Slideshow in 656ffa54 so that tests are green again

  • Sorry for introducing the error and thanks for the fix.

    About the erp5_run_my_doc coding style, as you said, there are many issues to fix, and I completely skip this (no time to do it for now on my side). My idea was for now to use the same terms without trying to change/improve them. But it seems I failed :/

  • Thanks for feedback. Yes it looks easy, but it's hard to use consistent terms in the user interface. I saw it with the login forms recently ... we have almost 10 different login form pages, each using different "Name", "Username", "User or Email" or "Login" terms.

Please register or sign in to reply
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>description</string>
<string>enabled</string>
<string>gadget_url</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_text_content</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>Content of the web page</string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_text_content</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewWebFieldLibrary</string> </value>
</item>
<item>
<key> <string>gadget_url</string> </key>
<value> <string>slideeditor.gadget.html</string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <int>40</int> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Page Content</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>not:here/isExternalDocument</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
url_list = [
"renderjs.js",
"rsvp.js",
"slideeditor.gadget.html",
"slideeditor.gadget.js",
"slideeditor.gadget.css"
# XXX Depend on many erp5js fields
# normally embedded by the default precache script
]
return url_list
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>REQUEST=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>WebSection_getSlideEditorPrecacheManifestList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
div[data-gadget-url$="slideeditor.gadget.html"] > .document_table button:disabled {
color: #999999;
}
div[data-gadget-url$="slideeditor.gadget.html"] > .slide_list {
display: flex;
flex-wrap: wrap;
}
div[data-gadget-url$="slideeditor.gadget.html"] > .slide_list > section {
flex: 0 1 auto;
width: 9em;
height: 9em;
overflow: hidden;
background-color: #FFFFFF;
border: 1px solid #000000;
padding: 0.5em;
vertical-align: middle;
text-align: center;
margin-right: 2em;
margin-bottom: 2em;
display: flex;
flex-direction: column;
}
div[data-gadget-url$="slideeditor.gadget.html"] > .slide_list > section[data-slide-index] {
cursor: move;
}
div[data-gadget-url$="slideeditor.gadget.html"] > .slide_list > section.drag {
opacity: 0.4;
}
div[data-gadget-url$="slideeditor.gadget.html"] > .slide_list > section.over {
border-style: dashed;
}
div[data-gadget-url$="slideeditor.gadget.html"] > .slide_list > section button {
padding: 3pt;
border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: 0.325em;
background-color: #FFFFFF;
width: 2em;
overflow: hidden;
text-indent: -9999px;
white-space: nowrap;
}
div[data-gadget-url$="slideeditor.gadget.html"] > .slide_list > section button::before {
float: left;
text-indent: 0;
}
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Cacheable__manager_id</string> </key>
<value> <string>must_revalidate_http_cache</string> </value>
</item>
<item>
<key> <string>__name__</string> </key>
<value> <string>slideeditor.gadget.css</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/css</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html>
<head>
<!--
data-i18n=Slides
data-i18n=Edit
data-i18n=New slide
Please register or sign in to reply
data-i18n=Slide
data-i18n=Delete
data-i18n=Metadata
data-i18n=Text
data-i18n=Comments
data-i18n=Previous
data-i18n=List
data-i18n=Next
data-i18n=Chapter Title
data-i18n=Type of Slide
data-i18n=Slide Text
data-i18n=Chapter
data-i18n=Screenshot
data-i18n=Illustration
data-i18n=Code
data-i18n=Master
-->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Slide Editor Gadget</title>
<link rel="stylesheet" href="slideeditor.gadget.css">
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<script src="gadget_global.js"></script>
<script src="domsugar.js"></script>
<script src="slideeditor.gadget.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Cacheable__manager_id</string> </key>
<value> <string>must_revalidate_http_cache</string> </value>
</item>
<item>
<key> <string>__name__</string> </key>
<value> <string>slideeditor.gadget.html</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/*global window, document, rJS, console, RSVP, domsugar*/
/*jslint nomen: true, maxlen:80, indent:2*/
(function () {
"use strict";
var DISPLAY_LIST = 'display_list',
DISPLAY_SLIDE = 'display_slide',
DIALOG_SLIDE = 'dialog_slide',
DIALOG_COMMENT = 'dialog_comment',
DIALOG_METADATA = 'dialog_metadata',
FORMBOX_SCOPE = 'formbox',
TRANSLATABLE_WORD_LIST = [
'Slides',
'Edit',
'New slide',
'Slide',
'Delete',
'Metadata',
'Text',
'Comments',
'Previous',
'List',
'Next',
'Chapter Title',
'Type of Slide',
'Slide Text',
'Chapter',
'Screenshot',
'Illustration',
'Code',
'Master'
];
///////////////////////////////////////////////////
// translation
///////////////////////////////////////////////////
function getTranslationDict(gadget) {
return gadget.getTranslationList(TRANSLATABLE_WORD_LIST)
.push(function (word_list) {
var result_dict = {},
i;
for (i = 0; i < TRANSLATABLE_WORD_LIST.length; i += 1) {
result_dict[TRANSLATABLE_WORD_LIST[i]] = word_list[i];
}
return result_dict;
});
}
///////////////////////////////////////////////////
// Slide format handling
///////////////////////////////////////////////////
function getSlideElementList(presentation_html) {
// Convert to an Array so that array methods can be used to reorder slides
return Array.prototype.slice.call(domsugar('div', {
'class': 'slide_list',
html: presentation_html
}).querySelectorAll(':scope > section'));
}
function getSlideFromList(slide_list, slide_index) {
// Get the section corresponding to the slide
if (slide_list.length <= slide_index) {
throw new Error('No slide: ' + slide_index);
}
return slide_list[slide_index];
}
function getSlideDictFromSlideElement(slide) {
var h1,
details,
result = {
type: '',
title_html: '',
comment_html: '',
slide_html: ''
};
// Clone the slide,
// as we will remove the h1/details to calculate the content
slide = slide.cloneNode(true);
// XXX drop img handling for now
// As it seems it was a hack to allow image upload
// which is not working anymore in xhtml style
// Get the first h1 tag
h1 = slide.querySelector(':scope > h1');
if (h1 !== null) {
result.title_html = h1.innerHTML;
slide.removeChild(h1);
}
// Get the slide type
if (slide.classList.length !== 0) {
result.type = slide.classList[0];//.toUpperCase();
}
// XXX drop test management
// No idea what it is for now
// Get the comment
details = slide.querySelector(':scope > details');
if (details !== null) {
result.comment_html = details.innerHTML;
slide.removeChild(details);
}
// Finally, extract the slide
result.slide_html = slide.innerHTML;
return result;
}
function slideListAsHTML(slide_list) {
var i,
result = '';
for (i = 0; i < slide_list.length; i += 1) {
result += slide_list[i].outerHTML;
}
return result;
}
function updateSlideDict(presentation_html, value_dict, slide_index) {
var slide_list = getSlideElementList(presentation_html),
slide = getSlideFromList(slide_list, slide_index),
slide_dict = getSlideDictFromSlideElement(slide),
i,
class_string,
key;
// Hack: remove keys sent to erp5
delete value_dict['default_type:int'];
for (key in value_dict) {
if (value_dict.hasOwnProperty(key)) {
if (!slide_dict.hasOwnProperty(key)) {
throw new Error('Unknown slide property: ' + key);
}
slide_dict[key] = value_dict[key];
}
}
class_string = slide_dict.type;
for (i = 1; i < slide.classList.length; i += 1) {
class_string += ' ' + slide.classList[i];
}
slide.className = class_string;
slide.innerHTML = '<h1>' + slide_dict.title_html + '</h1>' +
'<details>' + slide_dict.comment_html + '</details>' +
slide_dict.slide_html;
return slideListAsHTML(slide_list);
}
///////////////////////////////////////////////////
// Page view handling
///////////////////////////////////////////////////
function buildPageTitle(gadget, title_translation) {
var element_list = [title_translation];
if (gadget.state.display_index !== null) {
element_list.push(
' ' + (gadget.state.display_index + 1)
// domsugar('label', {'class': 'page-number',
// text: gadget.state.display_index})
);
}
return domsugar('h1', element_list);
}
function buildSlideButtonList(slide_dialog, translation_dict,
disable_previous, disable_next) {
var button_list = [];
button_list.push(
domsugar('button', {
type: 'button',
'class': 'dialog-delete ui-icon-trash-o ui-btn-icon-left',
text: translation_dict.Delete
}),
domsugar('button', {
type: 'button',
disabled: (slide_dialog === DIALOG_METADATA),
'class': 'dialog-metadata ui-icon-info-circle ui-btn-icon-left',
text: translation_dict.Metadata
}),
domsugar('button', {
type: 'button',
disabled: (slide_dialog === DIALOG_SLIDE),
'class': 'dialog-slide ui-icon-file-image-o ui-btn-icon-left',
text: translation_dict.Text
}),
domsugar('button', {
type: 'button',
disabled: (slide_dialog === DIALOG_COMMENT),
'class': 'dialog-commenting ui-icon-comment ui-btn-icon-left',
text: translation_dict.Comments
}),
domsugar('button', {
type: 'button',
disabled: disable_previous,
'class': 'previous-btn ui-icon-backward ui-btn-icon-left',
text: translation_dict.Previous
}),
domsugar('button', {
type: 'button',
'class': 'list-btn ui-icon-th ui-btn-icon-left',
text: translation_dict.List
}),
domsugar('button', {
type: 'button',
disabled: disable_next,
'class': 'next-btn ui-icon-forward ui-btn-icon-left',
text: translation_dict.Next
})
);
return button_list;
}
///////////////////////////////////////////////////
// Page view handling
///////////////////////////////////////////////////
function getCKEditorJSON(translation_dict, key, value) {
return {
erp5_document: {
"_embedded": {
"_view": {
"your_slide_content": {
"title": translation_dict["Slide Text"],
"type": "GadgetField",
"url": "gadget_editor.html",
"renderjs_extra": JSON.stringify({
"portal_type": "Web Page",
"content_type": "text/html",
"editor": "fck_editor",
"maximize": true
}),
"editable": 1,
"key": key,
"default": value
}
}
},
"_links": {
"type": {
// form_list display portal_type in header
name: ""
}
}
},
form_definition: {
group_list: [
["bottom", [["your_slide_content"]]]
]
}
};
}
function getMetadataJSON(translation_dict, title_html, type) {
return {
erp5_document: {
"_embedded": {
"_view": {
"your_chapter_title": {
"title": translation_dict["Chapter Title"],
"type": "StringField",
"editable": 1,
"required": 1,
"key": "title_html",
"value": title_html
},
"your_slide_type": {
"title": translation_dict["Type of Slide"],
"type": "ListField",
"editable": 1,
"key": "type",
items: [["", ""],
[translation_dict.Chapter, "chapter"],
[translation_dict.Screenshot, "screenshot"],
[translation_dict.Illustration, "illustration"],
[translation_dict.Code, "code"],
[translation_dict.Master, "master"]
],
value: type
/*
},
"your_tested": {
"title": "XXX Does it Contain a Test?",
"type": "CheckBoxField",
"editable": 1,
"key": "field_your_tested",
"default": "eee",
"required": 0,
"hidden": 0
*/
}
}
},
"_links": {
"type": {
// form_list display portal_type in header
name: ""
}
}
},
form_definition: {
group_list: [
["left", [
["your_chapter_title"],
["your_slide_type"]
// ["your_tested"]
]]
]
}
};
}
function renderSlideDialog(gadget, translation_dict, slide_dialog,
is_updated) {
var formbox,
render_dict,
slide_list = getSlideElementList(gadget.state.value),
slide_dict = getSlideDictFromSlideElement(
getSlideFromList(slide_list, gadget.state.display_index)
),
queue;
if (slide_dialog === DIALOG_SLIDE) {
render_dict = getCKEditorJSON(
translation_dict,
"slide_html",
slide_dict.slide_html
);
} else if (slide_dialog === DIALOG_COMMENT) {
render_dict = getCKEditorJSON(
translation_dict,
"comment_html",
slide_dict.comment_html
);
} else if (slide_dialog === DIALOG_METADATA) {
render_dict = getMetadataJSON(translation_dict, slide_dict.title_html,
slide_dict.type);
} else {
// Ease developper work by raising for not handled cases
throw new Error('Unhandled dialog: ' + slide_dialog);
}
if (is_updated) {
queue = gadget.getDeclaredGadget(FORMBOX_SCOPE);
} else {
queue = gadget.declareGadget('gadget_erp5_form.html', {
scope: FORMBOX_SCOPE
});
}
return queue
.push(function (result) {
formbox = result;
return formbox.render(render_dict);
})
.push(function () {
// Clone listbox header structure to reuse the css
var header_element = domsugar('div', {'class': 'document_table'}, [
domsugar('div', {'class': 'ui-table-header'}, [
buildPageTitle(gadget, translation_dict.Slide),
domsugar('null',
buildSlideButtonList(
slide_dialog,
translation_dict,
gadget.state.display_index === 0,
gadget.state.display_index === slide_list.length - 1
))
])
]);
if (is_updated) {
gadget.element.firstChild.replaceWith(header_element);
} else {
domsugar(gadget.element, [
header_element,
formbox.element
]);
}
});
}
function renderSlideList(gadget, translation_dict) {
// Get the full HTML
var header_element,
section_list = getSlideElementList(gadget.state.value),
draggable_element_list = [],
i;
// Clone listbox header structure to reuse the css
header_element = domsugar('div', {'class': 'document_table'}, [
domsugar('div', {'class': 'ui-table-header'}, [
domsugar('h1', {text: section_list.length + ' ' +
translation_dict.Slides})
])
]);
for (i = 0; i < section_list.length; i += 1) {
draggable_element_list.push(domsugar('section', {
draggable: true,
'data-slide-index': i
}, [
domsugar('button', {type: 'button', text: translation_dict.Edit,
'class': 'display-slide ui-icon-pencil ui-btn-icon-left',
'data-slide-index': i}),
domsugar('h1', {
html: getSlideDictFromSlideElement(section_list[i]).title_html
})
]));
}
// Add the "Add slide" button
// div.appendChild(domsugar('section', {text: 'Add Slide'}));
draggable_element_list.push(domsugar('section', [
domsugar('button', {
type: 'button',
text: translation_dict['New slide'],
'class': 'display-new ui-icon-plus-circle ui-btn-icon-left'
}),
domsugar('h1', {
text: translation_dict['New slide']
})
]));
domsugar(gadget.element, [
header_element,
domsugar('div', {'class': 'slide_list'}, draggable_element_list)
]);
}
///////////////////////////////////////////////////
// Gadget
///////////////////////////////////////////////////
rJS(window)
.declareAcquiredMethod("notifyChange", "notifyChange")
.declareAcquiredMethod("getTranslationList", "getTranslationList")
.declareJob("deferNotifyChange", function () {
// Ensure error will be correctly handled
return this.notifyChange();
})
.setState({
display_step: DISPLAY_LIST,
display_index: null,
slide_dialog: null
})
.declareMethod('render', function (options) {
return this.changeState({
key: options.key,
value: options.value || "",
editable: options.editable === undefined ? true : options.editable
});
})
.declareMethod('getContent', function () {
var gadget = this,
display_step = gadget.state.display_step,
queue;
// First, check if the current display contains a dialog
// and modify the slide as expected
if (display_step === DISPLAY_SLIDE) {
// Save the slide modification
queue = gadget.getDeclaredGadget(FORMBOX_SCOPE)
.push(function (formbox_gadget) {
return formbox_gadget.getContent();
})
.push(function (formbox_content_dict) {
gadget.state.value = updateSlideDict(
gadget.state.value,
formbox_content_dict,
gadget.state.display_index
);
});
} else if ([DISPLAY_LIST].indexOf(display_step) !== -1) {
queue = new RSVP.Queue();
} else {
throw new Error('Display form not handled: ' + display_step);
}
return queue
.push(function () {
var result = {};
if (gadget.state.editable) {
result[gadget.state.key] = gadget.state.value;
}
return result;
});
}, {mutex: 'statechange'})
.onStateChange(function (modification_dict) {
var gadget = this,
display_step = gadget.state.display_step,
slide_dialog = gadget.state.slide_dialog,
queue = getTranslationDict(gadget);
if (display_step === DISPLAY_LIST) {
return queue
.push(function (translation_dict) {
return renderSlideList(gadget, translation_dict);
});
}
if (display_step === DISPLAY_SLIDE) {
return queue
.push(function (translation_dict) {
return renderSlideDialog(
gadget,
translation_dict,
slide_dialog,
!(modification_dict.hasOwnProperty('display_step') ||
modification_dict.hasOwnProperty('slide_dialog'))
);
});
}
// Ease developper work by raising for not handled cases
throw new Error('Unhandled display step: ' + display_step);
})
.onEvent("click", function (evt) {
// Only handle click on BUTTON and IMG element
var gadget = this,
tag_name = evt.target.tagName,
queue;
if (tag_name !== 'BUTTON') {
return;
}
// Always get content to ensure the possible displayed form
// is checked and content propagated to the gadget state value
queue = gadget.getContent();
// Actions from a slide dialog
if (evt.target.className.indexOf("next-btn") !== -1) {
return queue
.push(function () {
return gadget.changeState({
display_index: gadget.state.display_index + 1
});
});
}
if (evt.target.className.indexOf("previous-btn") !== -1) {
return queue
.push(function () {
return gadget.changeState({
display_index: gadget.state.display_index - 1
});
});
}
if (evt.target.className.indexOf("list-btn") !== -1) {
return queue
.push(function () {
return gadget.changeState({
display_step: DISPLAY_LIST
});
});
}
if (evt.target.className.indexOf("display-slide") !== -1) {
return queue
.push(function () {
return gadget.changeState({
display_step: DISPLAY_SLIDE,
display_index: parseInt(
evt.target.getAttribute('data-slide-index'),
10
),
slide_dialog: gadget.state.slide_dialog || DIALOG_SLIDE
});
});
}
if (evt.target.className.indexOf("dialog-metadata") !== -1) {
return queue
.push(function () {
return gadget.changeState({
slide_dialog: DIALOG_METADATA
});
});
}
if (evt.target.className.indexOf("dialog-comment") !== -1) {
return queue
.push(function () {
return gadget.changeState({
slide_dialog: DIALOG_COMMENT
});
});
}
if (evt.target.className.indexOf("dialog-slide") !== -1) {
return queue
.push(function () {
return gadget.changeState({
slide_dialog: DIALOG_SLIDE
});
});
}
if (evt.target.className.indexOf("dialog-delete") !== -1) {
return queue
.push(function () {
var slide_list = getSlideElementList(gadget.state.value);
slide_list.splice(gadget.state.display_index, 1);
return RSVP.all([
gadget.changeState({
value: slideListAsHTML(slide_list),
display_step: DISPLAY_LIST
}),
gadget.notifyChange()
]);
});
}
if (evt.target.className.indexOf("display-new") !== -1) {
return queue
.push(function () {
return gadget.changeState({
display_index: getSlideElementList(gadget.state.value).length,
display_step: DISPLAY_SLIDE,
slide_dialog: gadget.state.slide_dialog || DIALOG_SLIDE,
value: gadget.state.value + "<section></section>"
});
});
}
throw new Error('Unhandled button: ' + evt.target.textContent);
}, false, false)
///////////////////////////////////////////////////
// Drag / drop management
///////////////////////////////////////////////////
.onEvent("dragstart", function (evt) {
var tag_name = evt.target.tagName;
if (tag_name !== 'SECTION') {
return;
}
// Store index of the dragged slide
evt.target.classList.add('drag');
evt.dataTransfer.effectAllowed = 'move';
evt.dataTransfer.setData('application/x-dragged-slide',
evt.target.getAttribute('data-slide-index'));
}, false, false)
.onEvent("dragend", function (evt) {
var tag_name = evt.target.tagName;
if (tag_name !== 'SECTION') {
return;
}
evt.target.classList.remove('drag');
}, false, false)
.onEvent("dragover", function (evt) {
var tag_name = evt.target.tagName;
if (tag_name !== 'SECTION') {
return;
}
if (evt.preventDefault) {
evt.preventDefault(); // Necessary. Allows us to drop.
}
evt.dataTransfer.dropEffect = 'move';
}, false, false)
.onEvent("dragenter", function (evt) {
var tag_name = evt.target.tagName;
if (tag_name !== 'SECTION') {
return;
}
// Provide a visual feedback to the user
// Showing where the slide can be dropped
if (evt.target.getAttribute('data-slide-index')) {
evt.target.classList.add('over');
}
}, false, false)
.onEvent("dragleave", function (evt) {
var tag_name = evt.target.tagName;
if (tag_name !== 'SECTION') {
return;
}
evt.target.classList.remove('over');
}, false, false)
.onEvent("drop", function (evt) {
var gadget = this,
tag_name = evt.target.tagName,
slide_list,
source_index,
destination_index;
if (tag_name !== 'SECTION') {
return;
}
if (evt.preventDefault) {
evt.preventDefault(); // Necessary. Allows us to drop.
}
// Remove the over class
evt.target.classList.remove('over');
source_index = evt.dataTransfer.getData('application/x-dragged-slide');
if (source_index && evt.target.getAttribute('data-slide-index')) {
source_index = parseInt(
source_index,
10
);
destination_index = parseInt(
evt.target.getAttribute('data-slide-index'),
10
);
slide_list = getSlideElementList(gadget.state.value);
if (source_index !== destination_index) {
slide_list.splice(
destination_index,
0,
slide_list.splice(source_index, 1)[0]
);
return RSVP.all([
gadget.changeState({value: slideListAsHTML(slide_list)}),
gadget.notifyChange()
]);
}
}
}, false, false);
}());
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Cacheable__manager_id</string> </key>
<value> <string>must_revalidate_http_cache</string> </value>
</item>
<item>
<key> <string>__name__</string> </key>
<value> <string>slideeditor.gadget.js</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>application/javascript</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
div[data-gadget-url$="slideeditor.gadget.html"] {
// XXX Move to listbox css
& > .document_table {
button:disabled {
color: #999999;
}
}
& > .slide_list {
display: flex;
flex-wrap: wrap;
& > section {
flex: 0 1 auto;
&[data-slide-index] {
cursor: move;
}
width: 9em;
height: 9em;
overflow: hidden;
background-color:#FFFFFF;
border: 1px solid #000000;
padding: 0.5em;
vertical-align: middle;
text-align: center;
// color:#000000;
&.drag {
opacity: 0.4;
}
&.over {
// border: 2px solid #000000;
border-style: dashed;
}
// Spacing between every section
margin-right: 2em;
margin-bottom: 2em;
display: flex;
flex-direction: column;
button {
padding: 3pt;
border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: 0.325em;
background-color: #FFFFFF;
width: 2em;
overflow: hidden;
text-indent: -9999px;
white-space: nowrap;
&::before {
float: left;
text-indent: 0;
}
}
}
}
}
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>slideeditor.gadget.less</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/plain</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -22,6 +22,7 @@ Test Page | verify_content
Test Page | view
Test Page | view_editor
Test Page | view_slideshow_editor
Test Page | view_slideshow_editor_with_gadget
Test Page | view_test_report
Test Page | web_view
Web Page Module | page_fast_input
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Zuite" module="Products.Zelenium.zuite"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>renderjs_ui_run_my_doc_zuite</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>testSlideshowEditor</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test RenderJS UI</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">Test RenderJS UI</td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<!-- Initialize -->
<tr>
<td>open</td>
<td>${base_url}/web_site_module/renderjs_runner/#/test_page_module</td>
<td></td>
</tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_app_loaded" />
<tal:block tal:define="click_configuration python: {'text': 'Add'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/click_on_header_link" />
</tal:block>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/submit_dialog" />
<tal:block tal:define="notification_configuration python: {'class': 'success',
'text': 'Object created.'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
</tal:block>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
<!-- Go to test form -->
<tal:block tal:define="click_configuration python: {'text': 'Edit Slideshow'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/click_on_panel_link" />
</tal:block>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
<tr>
<td>assertTextPresent</td>
<td>0 Slides</td>
<td></td>
</tr>
<tr>
<td colspan="3"><b>Create first slide</b></td>
</tr>
<tr>
<td>click</td>
<td>//button[text()='New slide']</td>
<td></td>
</tr>
<tr>
<td>waitForTextPresent</td>
<td>Slide 1</td>
<td></td>
</tr>
<tr>
<td colspan="3"><b>Set text content</b></td>
</tr>
<tal:block tal:define="text_content python: 'slide 1 text content'">
<tal:block metal:use-macro="container/Zuite_CommonTemplateForRenderjsUi/macros/type_ckeditor_text_content"/>
</tal:block>
<tr>
<td colspan="3"><b>Go to the metadata form</b></td>
</tr>
<tr>
<td>click</td>
<td>//button[text()='Metadata']</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>title_html</td>
<td></td>
</tr>
<tr>
<td colspan="3"><b>Edit the metadata</b></td>
</tr>
<tr>
<td>type</td>
<td>title_html</td>
<td>slide 1 test title</td>
</tr>
<tr>
<td colspan="3"><b>Go back to the text form</b></td>
</tr>
<tr>
<td>click</td>
<td>//button[text()='Text']</td>
<td></td>
</tr>
<tr>
<td>waitForElementNotPresent</td>
<td>title_html</td>
<td></td>
</tr>
<tr>
<td colspan="3"><b>Check text content</b></td>
</tr>
<tal:block tal:define="text_content python: '<p>slide 1 text content</p>'">
<tal:block metal:use-macro="container/Zuite_CommonTemplateForRenderjsUi/macros/verify_ckeditor_text_content"/>
</tal:block>
<tr>
<td colspan="3"><b>Go back to the metadata form</b></td>
</tr>
<tr>
<td>click</td>
<td>//button[text()='Metadata']</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>title_html</td>
<td></td>
</tr>
<tr>
<td colspan="3"><b>Check the metadata</b></td>
</tr>
<tr>
<td>assertValue</td>
<td>title_html</td>
<td>slide 1 test title</td>
</tr>
<tr>
<td colspan="3"><b>Go back to the slide list</b></td>
</tr>
<tr>
<td>click</td>
<td>//button[text()='List']</td>
<td></td>
</tr>
<tr>
<td>waitForTextPresent</td>
<td>1 Slides</td>
<td></td>
</tr>
<tr>
<td colspan="3"><b>Go back to the slide 1</b></td>
</tr>
<tr>
<td>click</td>
<td>//button[text()='Edit']</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>title_html</td>
<td></td>
</tr>
<tr>
<td colspan="3"><b>Check the metadata</b></td>
</tr>
<tr>
<td>assertValue</td>
<td>title_html</td>
<td>slide 1 test title</td>
</tr>
<tal:block metal:use-macro="container/Zuite_CommonTemplateForRenderjsUi/macros/save"/>
<tr>
<td>assertValue</td>
<td>title_html</td>
<td>slide 1 test title</td>
</tr>
</tbody></table>
</body>
</html>
\ No newline at end of file
##############################################################################
#
# Copyright (c) 2011 Nexedi SARL and Contributors. All Rights Reserved.
# Kazuhiko <kazuhiko@nexedi.com>
# Rafael Monnerat <rafael@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import unittest
from Products.ERP5Type.tests.ERP5TypeFunctionalTestCase import ERP5TypeFunctionalTestCase
class TestRenderJSUIRunMyDocAction(ERP5TypeFunctionalTestCase):
foreground = 0
run_only = "renderjs_ui_run_my_doc_zuite"
def getBusinessTemplateList(self):
return (
'erp5_run_my_doc_renderjs_ui_test',
'erp5_run_my_doc',
'erp5_web_renderjs_ui',
'erp5_web_renderjs_ui_test',
'erp5_ui_test_core',
'erp5_test_result',
)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestRenderJSUIRunMyDocAction))
return suite
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testFunctionalRJSRunMyDocAction</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testFunctionalRJSRunMyDocAction</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
RenderJS UI tests for Run My Doc
\ No newline at end of file
Copyright (C) 2018 Nexedi SA
\ No newline at end of file
erp5_run_my_doc
erp5_web_renderjs_ui_test
\ No newline at end of file
portal_tests/renderjs_ui_run_my_doc_zuite
portal_tests/renderjs_ui_run_my_doc_zuite/**
\ No newline at end of file
test.erp5.testFunctionalRJSRunMyDocAction
\ No newline at end of file
erp5_full_text_mroonga_catalog
\ No newline at end of file
erp5_run_my_doc_renderjs_ui_test
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment