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
......@@ -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>
</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>
</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
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