diff --git a/pom.xml b/pom.xml
index bc2d198..3ea0936 100644
--- a/pom.xml
+++ b/pom.xml
@@ -156,9 +156,9 @@
provided
- org.gcube.dataaccess
+ org.gcube.data-catalogue
ckan-util-library
- 1.0.0-SNAPSHOT
+ [1.0.0-SNAPSHOT, 2.0.0-SNAPSHOT)
org.gcube.core
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherService.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherService.java
index bfa32a4..94d62d9 100644
--- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherService.java
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherService.java
@@ -2,6 +2,7 @@ package org.gcube.portlets.widgets.ckandatapublisherwidget.client;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.DatasetMetadataBean;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.LicensesBean;
+import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
@@ -31,8 +32,17 @@ public interface CKanPublisherService extends RemoteService {
/**
* Try to create such dataset starting from the information contained into the toCreate bean.
* @param toCreate
- * @return true on success, false otherwise
+ * @return the identifier of the created dataset or null on error
*/
- boolean createCKanDataset(DatasetMetadataBean toCreate);
+ String createCKanDataset(DatasetMetadataBean toCreate);
+
+ /**
+ * Add this resource to the dataset whose id is datasetId
+ * @param resource
+ * @param datasetId
+ * @param owner of the dataset
+ * @param callback
+ */
+ boolean addResourceToDataset(ResourceBean resource, String datasetId, String owner);
}
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherServiceAsync.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherServiceAsync.java
index ba890a7..a2eb722 100644
--- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherServiceAsync.java
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherServiceAsync.java
@@ -2,6 +2,7 @@ package org.gcube.portlets.widgets.ckandatapublisherwidget.client;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.DatasetMetadataBean;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.LicensesBean;
+import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -33,6 +34,16 @@ public interface CKanPublisherServiceAsync {
* @return true on success, false otherwise
*/
void createCKanDataset(DatasetMetadataBean toCreate,
+ AsyncCallback callback);
+
+ /**
+ * Add this resource to the dataset whose id is datasetId
+ * @param resource
+ * @param datasetId
+ * @param owner of the dataset
+ * @param callback
+ */
+ void addResourceToDataset(ResourceBean resource, String datasetId, String owner,
AsyncCallback callback);
}
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/AddResourceEvent.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/AddResourceEvent.java
new file mode 100644
index 0000000..05a5f4f
--- /dev/null
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/AddResourceEvent.java
@@ -0,0 +1,33 @@
+package org.gcube.portlets.widgets.ckandatapublisherwidget.client.events;
+
+import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+/**
+ * Added resource event
+ * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
+ */
+public class AddResourceEvent extends GwtEvent {
+ public static Type TYPE = new Type();
+
+ private ResourceBean resource;
+
+ public AddResourceEvent(ResourceBean resource) {
+ this.resource = resource;
+ }
+
+ public ResourceBean getResource() {
+ return resource;
+ }
+
+ @Override
+ public Type getAssociatedType() {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(AddResourceEventHandler handler) {
+ handler.onAddedResource(this);
+ }
+}
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/AddResourceEventHandler.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/AddResourceEventHandler.java
new file mode 100644
index 0000000..5448594
--- /dev/null
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/AddResourceEventHandler.java
@@ -0,0 +1,11 @@
+package org.gcube.portlets.widgets.ckandatapublisherwidget.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+/**
+ * Added resource handler interface
+ * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
+ */
+public interface AddResourceEventHandler extends EventHandler {
+ void onAddedResource(AddResourceEvent addResourceEvent);
+}
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEvent.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEvent.java
index 4374ac1..bf9287d 100644
--- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEvent.java
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEvent.java
@@ -3,7 +3,10 @@ import org.gcube.portlets.widgets.ckandatapublisherwidget.client.ui.CustomFieldE
import com.google.gwt.event.shared.GwtEvent;
-
+/**
+ * Delete custom field event.
+ * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
+ */
public class DeleteCustomFieldEvent extends GwtEvent {
public static Type TYPE = new Type();
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEventHandler.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEventHandler.java
index 6cfb050..6e5b2b4 100644
--- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEventHandler.java
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEventHandler.java
@@ -2,6 +2,10 @@ package org.gcube.portlets.widgets.ckandatapublisherwidget.client.events;
import com.google.gwt.event.shared.EventHandler;
+/**
+ * Handler associated to the DeleteCustomFieldEvent
+ * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
+ */
public interface DeleteCustomFieldEventHandler extends EventHandler {
void onRemoveEntry(DeleteCustomFieldEvent event);
}
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddResourceToDataset.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddResourceToDataset.java
new file mode 100644
index 0000000..453b12e
--- /dev/null
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddResourceToDataset.java
@@ -0,0 +1,142 @@
+package org.gcube.portlets.widgets.ckandatapublisherwidget.client.ui;
+
+import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherService;
+import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherServiceAsync;
+import org.gcube.portlets.widgets.ckandatapublisherwidget.client.events.AddResourceEvent;
+import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean;
+
+import com.github.gwtbootstrap.client.ui.AlertBlock;
+import com.github.gwtbootstrap.client.ui.Button;
+import com.github.gwtbootstrap.client.ui.TextArea;
+import com.github.gwtbootstrap.client.ui.TextBox;
+import com.github.gwtbootstrap.client.ui.constants.AlertType;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Form used to add resource(s) to a dataset
+ * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
+ */
+public class AddResourceToDataset extends Composite{
+
+ private static AddResourceToDatasetUiBinder uiBinder = GWT
+ .create(AddResourceToDatasetUiBinder.class);
+
+ interface AddResourceToDatasetUiBinder extends
+ UiBinder {
+ }
+
+ // bus to alert the dataset form about this new resource
+ private HandlerManager eventBus;
+
+ // the dataset id
+ private String datasetId;
+
+ // the owner
+ private String owner;
+
+ //Create a remote service proxy to talk to the server-side ckan service.
+ private final CKanPublisherServiceAsync ckanServices = GWT.create(CKanPublisherService.class);
+
+ @UiField TextBox resourceUrlTextBox;
+ @UiField TextBox resourceNameTextBox;
+ @UiField TextArea resourceDescriptionTextArea;
+ @UiField Button addResourceButton;
+ @UiField AlertBlock infoBlock;
+
+ public AddResourceToDataset(HandlerManager eventBus, String datasetId, String owner) {
+ initWidget(uiBinder.createAndBindUi(this));
+
+ // save bus
+ this.eventBus = eventBus;
+
+ // save dataset id (it is needed when we will add resources)
+ this.datasetId = datasetId;
+
+ // the owner of the dataset/files
+ this.owner = owner;
+ }
+
+ @UiHandler("addResourceButton")
+ void onAddButtonClick(ClickEvent e){
+
+ infoBlock.setVisible(false);
+
+ // validation
+ if(resourceUrlTextBox.getText().isEmpty()){
+
+ infoBlock.setType(AlertType.ERROR);
+ infoBlock.setText("URL cannot be empty");
+ infoBlock.setVisible(true);
+ return;
+
+ }
+
+ // remove html tags into description
+ //String description = convert(resourceDescriptionTextArea.getText()); TODO
+
+ // collect data and build up the bean
+ final ResourceBean resource = new ResourceBean(resourceUrlTextBox.getText(), resourceNameTextBox.getText(), resourceDescriptionTextArea.getText());
+
+ // disable add button
+ addResourceButton.setEnabled(false);
+
+ // try to create
+ ckanServices.addResourceToDataset(resource, datasetId, owner, new AsyncCallback() {
+
+ @Override
+ public void onSuccess(Boolean result) {
+
+ if(result){
+ showAlert("Resource created correctly", AlertType.SUCCESS);
+ eventBus.fireEvent(new AddResourceEvent(resource));
+ }
+ else
+ showAlert("Unable to add this resource. Check the url is correct", AlertType.ERROR);
+
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+
+ showAlert("Unable to add this resource, sorry", AlertType.ERROR);
+
+ }
+ });
+
+ }
+
+ /**
+ * Show error/success after resource creation attempt.
+ * @param text
+ * @param type
+ */
+ protected void showAlert(String text, AlertType type) {
+
+ infoBlock.setText(text);
+ infoBlock.setType(type);
+ infoBlock.setVisible(true);
+ addResourceButton.setEnabled(true);
+
+ // hide after some seconds
+ Timer t = new Timer() {
+
+ @Override
+ public void run() {
+
+ infoBlock.setVisible(false);
+
+ }
+ };
+
+ t.schedule(4000);
+ }
+}
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddResourceToDataset.ui.xml b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddResourceToDataset.ui.xml
new file mode 100644
index 0000000..15eb527
--- /dev/null
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddResourceToDataset.ui.xml
@@ -0,0 +1,124 @@
+
+
+
+ .form-main-style {
+ margin-left: 10px;
+ }
+
+ .fieldset-border-style {
+ border: 1px groove #444;
+ -webkit-box-shadow: 0px 0px 0px 0px #000;
+ box-shadow: 0px 0px 0px 0px #000;
+ padding: 10px;
+ }
+
+ .legend-style {
+ width: auto;
+ padding: 10px;
+ margin-bottom: 0px;
+ }
+
+ @external .form-horizontal .input-large;
+ .form-horizontal .input-large {
+ width: 95%;
+ }
+
+ .block-alert-style {
+ margin-top: 10px;
+ padding: 10px;
+ margin-bottom: 10px;
+ }
+
+ .tagsPanelStyle {
+ display: inline-block;
+ }
+
+ .add-resource-button {
+ float: right;
+ }
+
+ .info-markdown {
+ width: 95%;
+ background-color: #ebebeb;
+ border-bottom: 1px thin;
+ border-left: 1px thin;
+ border-right: 1px thin;
+ padding: 3px;
+ }
+
+
+
+
+
+
+ Add Resource
+
+ *
+ is required
+
+
+
+
+
+ *
+ URL:
+
+
+
+
+
+
+
+
+ Name:
+
+
+
+
+
+
+
+
+ Description:
+
+
+
+
+ You can use
+
+ Markdown formatting
+
+
+
+
+
+
+
+
+
+ Add
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddedResourcesSummary.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddedResourcesSummary.java
new file mode 100644
index 0000000..b338e85
--- /dev/null
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddedResourcesSummary.java
@@ -0,0 +1,134 @@
+package org.gcube.portlets.widgets.ckandatapublisherwidget.client.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.gcube.portlets.widgets.ckandatapublisherwidget.client.events.AddResourceEvent;
+import org.gcube.portlets.widgets.ckandatapublisherwidget.client.events.AddResourceEventHandler;
+import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean;
+
+import com.github.gwtbootstrap.client.ui.Button;
+import com.github.gwtbootstrap.client.ui.constants.ButtonType;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * A summary of the added resources by the user.
+ * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
+ */
+public class AddedResourcesSummary extends Composite{
+
+ private static AddedResourcesSummaryUiBinder uiBinder = GWT
+ .create(AddedResourcesSummaryUiBinder.class);
+
+ interface AddedResourcesSummaryUiBinder extends
+ UiBinder {
+ }
+
+ // Event bus
+ private HandlerManager eventBus;
+
+ // list of added resources
+ List addedResources;
+
+ @UiField VerticalPanel addResourcesPanel;
+
+ public AddedResourcesSummary(HandlerManager eventBus) {
+ initWidget(uiBinder.createAndBindUi(this));
+
+ // save bus
+ this.eventBus = eventBus;
+
+ // bind on add resource event
+ bind();
+
+ // init list
+ addedResources = new ArrayList();
+ }
+
+ private void bind() {
+
+ // when a new resource is added
+ eventBus.addHandler(AddResourceEvent.TYPE, new AddResourceEventHandler() {
+
+ @Override
+ public void onAddedResource(AddResourceEvent addResourceEvent) {
+
+ // get the resource
+ final ResourceBean justAddedResource = addResourceEvent.getResource();
+
+ // check if a resource with this id already exists
+ for (ResourceBean resource : addedResources){
+
+ if(resource.getId().equals(justAddedResource.getId())){
+
+ // clear list and rebuild
+ rebuildSummary();
+ return;
+ }
+ }
+
+ Button associatedButton = new Button();
+ associatedButton.setType(ButtonType.LINK);
+ associatedButton.setText("-" + justAddedResource.getName());
+
+ // add to the list
+ addedResources.add(justAddedResource);
+
+ // add to the panel
+ addResourcesPanel.add(associatedButton);
+
+ // add handler to swap tab on click
+ associatedButton.addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event) {
+
+ // TODO show information below this link and swap panel
+
+ }
+ });
+
+ }
+ });
+ }
+
+ /**
+ * Rebuild the summary list
+ */
+ protected void rebuildSummary() {
+
+ addResourcesPanel.clear();
+
+ for (final ResourceBean resource : addedResources){
+
+ Button associatedButton = new Button();
+ associatedButton.setType(ButtonType.LINK);
+ associatedButton.setText("-" + resource.getName());
+
+ // add to the list
+ addedResources.add(resource);
+
+ // add to the panel
+ addResourcesPanel.add(associatedButton);
+
+ // add handler to swap tab on click
+ associatedButton.addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event) {
+
+ // TODO show information below this link and swap panel
+
+ }
+ });
+ }
+ }
+}
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddedResourcesSummary.ui.xml b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddedResourcesSummary.ui.xml
new file mode 100644
index 0000000..79ee8b8
--- /dev/null
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddedResourcesSummary.ui.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+ Added Resources
+
+
+
\ No newline at end of file
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.java
index 438587f..dc7e066 100644
--- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.java
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.java
@@ -7,7 +7,6 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
-import org.apache.commons.lang.StringEscapeUtils;
import org.gcube.portlets.user.gcubewidgets.client.elements.Span;
import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherService;
import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherServiceAsync;
@@ -20,12 +19,14 @@ import com.github.gwtbootstrap.client.ui.AlertBlock;
import com.github.gwtbootstrap.client.ui.Button;
import com.github.gwtbootstrap.client.ui.CheckBox;
import com.github.gwtbootstrap.client.ui.ControlGroup;
-import com.github.gwtbootstrap.client.ui.HelpBlock;
import com.github.gwtbootstrap.client.ui.ListBox;
+import com.github.gwtbootstrap.client.ui.Tab;
+import com.github.gwtbootstrap.client.ui.TabPanel;
import com.github.gwtbootstrap.client.ui.TextArea;
import com.github.gwtbootstrap.client.ui.TextBox;
import com.github.gwtbootstrap.client.ui.base.ListItem;
import com.github.gwtbootstrap.client.ui.constants.AlertType;
+import com.github.gwtbootstrap.client.ui.resources.Bootstrap.Tabs;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
@@ -39,6 +40,7 @@ import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
/**
@@ -65,7 +67,6 @@ public class EditMetadataForm extends Composite{
@UiField FlowPanel tagsPanel;
@UiField ListBox licenseListbox;
@UiField ListBox visibilityListbox;
- @UiField ListBox searchableListbox;
@UiField ListBox organizationsListbox;
@UiField TextBox versionTextbox;
@UiField TextBox authorTextbox;
@@ -75,10 +76,19 @@ public class EditMetadataForm extends Composite{
@UiField ControlGroup customFields;
@UiField Button addCustomFieldButton;
@UiField CheckBox addResourcesCheckBox;
+ @UiField ControlGroup resourcesControlGroup;
@UiField Button createButton;
+ @UiField Button addResourcesButton;
@UiField Button resetButton;
@UiField AlertBlock infoBlock;
@UiField AlertBlock onCreateAlertBlock;
+ @UiField VerticalPanel resourcesPanel;
+
+ // tab panel
+ private TabPanel tabPanel;
+
+ // add resource form
+ AddResourceToDataset resourceForm;
// tags list
private List tagsList = new ArrayList();
@@ -95,14 +105,18 @@ public class EditMetadataForm extends Composite{
// dataset metadata bean
private DatasetMetadataBean receivedBean;
+ // the owner
+ private String owner;
/**
* Invoked in the most general case
* @param owner
*/
- public EditMetadataForm(String user) {
+ public EditMetadataForm(String owner) {
initWidget(uiBinder.createAndBindUi(this));
+ this.owner = owner;
+
// bind on events
bind();
@@ -114,7 +128,7 @@ public class EditMetadataForm extends Composite{
resetButton.setEnabled(false);
// get back the licenses and the metadata information
- ckanServices.getDatasetBean(null, user, new AsyncCallback() {
+ ckanServices.getDatasetBean(null, owner, new AsyncCallback() {
@Override
public void onSuccess(DatasetMetadataBean bean) {
@@ -187,6 +201,9 @@ public class EditMetadataForm extends Composite{
}
});
+ // hide the Add resources checkbox
+ resourcesControlGroup.setVisible(false);
+
}
/**
@@ -197,6 +214,8 @@ public class EditMetadataForm extends Composite{
public EditMetadataForm(String idFolderWorkspace, String owner) {
initWidget(uiBinder.createAndBindUi(this));
+ this.owner = owner;
+
// bind on events
bind();
@@ -355,7 +374,6 @@ public class EditMetadataForm extends Composite{
String description = descriptionTextarea.getText();
String selectedLicense = licenseListbox.getSelectedItemText();
String visibility = visibilityListbox.getSelectedItemText();
- String searchable = searchableListbox.getSelectedItemText();
long version = Long.valueOf(versionTextbox.getValue());
String author = authorTextbox.getValue();
String authorEmail = authorEmailTextbox.getValue();
@@ -374,7 +392,6 @@ public class EditMetadataForm extends Composite{
receivedBean.setVersion(version);
receivedBean.setVisibility(visibility.equals("Public"));
receivedBean.setTitle(title);
- receivedBean.setSearchable(searchable.equals("Yes"));
receivedBean.setTags(tagsList);
receivedBean.setSelectedOrganization(chosenOrganization);
receivedBean.setAddResources(addResources);
@@ -399,15 +416,62 @@ public class EditMetadataForm extends Composite{
onCreateAlertBlock.setText("Trying to create dataset, please wait");
onCreateAlertBlock.setVisible(true);
- ckanServices.createCKanDataset(receivedBean, new AsyncCallback() {
+ ckanServices.createCKanDataset(receivedBean, new AsyncCallback() {
@Override
- public void onSuccess(Boolean result) {
+ public void onSuccess(final String datasetId) {
- if(result){
+ if(datasetId != null){
alertOnCreate("Dataset correctly created!", AlertType.SUCCESS);
+ // disable dataset fields
+ disableDatasetFields();
+
+ // if we are in the "general case" we need to show a form for adding resources
+ if(!resourcesControlGroup.isVisible()){
+
+ createButton.setVisible(false);
+ resetButton.setVisible(false);
+
+ // show the add resources button
+ addResourcesButton.setVisible(true);
+ addResourcesButton.addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event) {
+
+ // hide the button
+ addResourcesButton.setVisible(false);
+
+ // TabPanel
+ tabPanel = new TabPanel(Tabs.ABOVE);
+ tabPanel.setWidth("100%");
+
+ // add the form
+ resourceForm = new AddResourceToDataset(eventBus, datasetId, owner);
+
+ // tab for the form
+ Tab formContainer = new Tab();
+ formContainer.add(resourceForm);
+ formContainer.setHeading("Add New Resource");
+ tabPanel.add(formContainer);
+
+ // tab for the added resources
+ Tab addedResources = new Tab();
+ addedResources.add(new AddedResourcesSummary(eventBus));
+ addedResources.setHeading("Added Resource");
+ tabPanel.add(addedResources);
+
+ // add tabs to resources panel
+ tabPanel.selectTab(0);
+ resourcesPanel.add(tabPanel);
+ resourcesPanel.setVisible(true);
+
+ }
+ });
+ }
+
}else{
alertOnCreate("Unable to create this dataset, please retry later", AlertType.ERROR);
@@ -457,8 +521,8 @@ public class EditMetadataForm extends Composite{
* @return true on success
*/
private boolean validateData() {
-
+ // TODO
return true;
}
@@ -483,6 +547,24 @@ public class EditMetadataForm extends Composite{
}
+ /**
+ * Disable dataset editable fields
+ */
+ protected void disableDatasetFields() {
+ titleTextBox.setEnabled(false);
+ descriptionTextarea.setEnabled(false);
+ versionTextbox.setEnabled(false);
+ authorTextbox.setEnabled(false);
+ authorEmailTextbox.setEnabled(false);
+ maintainerTextbox.setEnabled(false);
+ maintainerEmailTextbox.setEnabled(false);
+ visibilityListbox.setEnabled(false);
+ tagsEnterTextBox.setEnabled(false);
+ licenseListbox.setEnabled(false);
+ organizationsListbox.setEnabled(false);
+ addCustomFieldButton.setEnabled(false);
+ }
+
/**
* change alert block behavior.
* @param textToShow
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.ui.xml b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.ui.xml
index d6e1895..66d0cb7 100644
--- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.ui.xml
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.ui.xml
@@ -22,7 +22,6 @@
@external .form-horizontal .input-large;
.form-horizontal .input-large {
width: 95%;
- margin-bottom: 15px;
}
.block-alert-style {
@@ -34,6 +33,15 @@
.tagsPanelStyle {
display: inline-block;
}
+
+ .info-markdown {
+ width: 95%;
+ background-color: #ebebeb;
+ border-bottom: 1px thin;
+ border-left: 1px thin;
+ border-right: 1px thin;
+ padding: 3px;
+ }
+
+ You can use
+
+ Markdown formatting
+
+
@@ -135,23 +162,6 @@
-
- Searchable:
-
-
- Yes
- No
-
-
-
- Searchable datasets can be searched by anyone, while
- not-searchable datasets can only be accessed by entering directly
- its URL.
-
-
-
-
Publish in:
@@ -233,7 +243,7 @@
-
+
+
+ Add Resources
+
+
Create
diff --git a/src/main/webapp/CKanMetadataPublisher.css b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/public/CKanMetadataPublisher.css
similarity index 100%
rename from src/main/webapp/CKanMetadataPublisher.css
rename to src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/public/CKanMetadataPublisher.css
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/server/CKANPublisherServicesImpl.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/server/CKANPublisherServicesImpl.java
index d5fc0b5..a41f983 100644
--- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/server/CKANPublisherServicesImpl.java
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/server/CKANPublisherServicesImpl.java
@@ -1,5 +1,7 @@
package org.gcube.portlets.widgets.ckandatapublisherwidget.server;
+import java.net.HttpURLConnection;
+import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
@@ -15,10 +17,11 @@ import org.gcube.common.homelibrary.home.workspace.WorkspaceItem;
import org.gcube.common.homelibrary.home.workspace.folder.FolderItem;
import org.gcube.common.homelibrary.home.workspace.folder.items.GCubeItem;
import org.gcube.common.scope.api.ScopeProvider;
-import org.gcube.dataaccess.ckanutillibrary.CKanUtilsFactory;
+import org.gcube.datacatalogue.ckanutillibrary.CKanUtilsFactory;
import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherService;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.DatasetMetadataBean;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.LicensesBean;
+import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean;
import org.gcube.vomanagement.usermanagement.UserManager;
import org.gcube.vomanagement.usermanagement.impl.LiferayUserManager;
import org.gcube.vomanagement.usermanagement.model.GCubeUser;
@@ -53,7 +56,7 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
if(isWithinPortal())
return ScopeProvider.instance.get();
else
- return "/gcube";
+ return "/gcube"; // Development
}
@@ -67,7 +70,7 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
try{
String currentScope = getCurrentScope();
- return "https://" + CKanUtilsFactory.getInstance().getCkanUtilsForScope(currentScope).getCatalogueUrl();
+ return CKanUtilsFactory.getInstance().getCkanUtilsForScope(currentScope).getCatalogueUrl();
}catch(Exception e){
logger.error("Failed to retrieve catalogue url information", e);
}
@@ -92,7 +95,6 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
}
return null;
-
}
/**
@@ -300,7 +302,7 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
}
@Override
- public boolean createCKanDataset(DatasetMetadataBean toCreate) {
+ public String createCKanDataset(DatasetMetadataBean toCreate) {
// retrieve ckan's catalog url
String ckanPortalUrl = getCatalogueUrl();
@@ -334,10 +336,6 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
dataset.setMaintainerEmail(toCreate.getMaintainerEmail());
dataset.setVersion(String.valueOf(toCreate.getVersion()));
dataset.setNotes(toCreate.getDescription());
-
- logger.info("Searchable is " + toCreate.isSearchable() + " and visible is " + toCreate.getVisibility());
-
- dataset.setPriv(false);
dataset.setOpen(toCreate.getVisibility());
// iterate over the licenses to find the id of the chosen one
@@ -431,10 +429,10 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
logger.debug("Dataset created/updated " + res.getId());
else{
logger.error("Dataset described by " + toCreate + " not created!");
- return false;
+ return null;
}
- return true;
+ return res.getId();
}
/**
@@ -450,4 +448,66 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
return convertedName;
}
+
+ /**
+ * Utility methods
+ * @param URLName
+ * @return
+ */
+ private static boolean exists(String URLName){
+ try {
+ HttpURLConnection.setFollowRedirects(true);
+ HttpURLConnection con = (HttpURLConnection) new URL(URLName).openConnection();
+ con.setRequestMethod("HEAD");
+ logger.info("Return code is " + con.getResponseCode());
+ return (con.getResponseCode() == HttpURLConnection.HTTP_OK);
+ }
+ catch (Exception e) {
+ logger.error("Exception while checking url", e);
+ return false;
+ }
+ }
+
+ @Override
+ public boolean addResourceToDataset(ResourceBean resourceBean, String datasetId, String owner) {
+
+ logger.info("Incoming request for creating new resource for dataset with id " + datasetId);
+ logger.info("Owner is " + owner + " and resource is " + resourceBean);
+
+ // of course, if it exists
+ if(exists(resourceBean.getUrl())){
+
+ try{
+ // retrieve ckan's catalog url
+ String ckanPortalUrl = getCatalogueUrl();
+
+ // retrieve the api key for this user
+ String apiKey = getCKANApikeyFromUser(owner);
+
+ CkanResource resource = new CkanResource(ckanPortalUrl, datasetId);
+ resource.setName(resourceBean.getName());
+ resource.setDescription(resourceBean.getDescription());
+ resource.setUrl(resourceBean.getUrl());
+ resource.setOwner(owner);
+
+ // Checked client
+ CheckedCkanClient client = new CheckedCkanClient(ckanPortalUrl, apiKey);
+ CkanResource createdRes = client.createResource(resource);
+
+ if(createdRes != null){
+
+ logger.info("Resource " + createdRes.getName() + " is now available");
+ return true;
+
+ }
+
+ }catch(Exception e){
+ logger.error("Unable to create new resource", e);
+ }
+ }
+
+ logger.info("No resource created");
+
+ return false;
+ }
}
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/shared/DatasetMetadataBean.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/shared/DatasetMetadataBean.java
index f8373d0..66084fb 100644
--- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/shared/DatasetMetadataBean.java
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/shared/DatasetMetadataBean.java
@@ -13,7 +13,6 @@ import java.util.Map;
* Description -> folders' description
* tags -> folder's custom fields keys' names
* visibility -> as chosen by the creator (visible = true, not visible = false)
- * searchable -> as chosen by the creator
* source -> url of the folder within the workspace
* version -> during creation it is going to be 1.0
* author, maintainer -> folder's owner
@@ -34,7 +33,6 @@ public class DatasetMetadataBean implements Serializable {
List tags; // on retrieve, they are the keys of the custom fields
private String license; // chosen by the user
private boolean visibility; // Private (false) or Public(true)
- private boolean searchable; // true or false
private String source; // url of the folder in the workspace
private long version; // version 1, 2 ...
private String author; // folder's owner fullname
@@ -46,7 +44,7 @@ public class DatasetMetadataBean implements Serializable {
private String selectedOrganization;
private List resourcesIds; // in case of workspace, this is the list of children's ids
private boolean addResources; // if true, add these files as resources to the dataset
-
+
public DatasetMetadataBean(){
super();
}
@@ -59,7 +57,6 @@ public class DatasetMetadataBean implements Serializable {
* @param tags
* @param license
* @param visibility
- * @param searchable
* @param source
* @param version
* @param author
@@ -72,7 +69,7 @@ public class DatasetMetadataBean implements Serializable {
*/
public DatasetMetadataBean(String id, String title, String description,
Map customFields, List tags,
- String license, boolean visibility, boolean searchable,
+ String license, boolean visibility,
String source, long version, String author, String authorEmail,
String maintainer, String maintainerEmail,
String ownerFolderInWorkspace, List organizationList,
@@ -85,7 +82,6 @@ public class DatasetMetadataBean implements Serializable {
this.tags = tags;
this.license = license;
this.visibility = visibility;
- this.searchable = searchable;
this.source = source;
this.version = version;
this.author = author;
@@ -161,14 +157,6 @@ public class DatasetMetadataBean implements Serializable {
this.visibility = visibility;
}
- public boolean isSearchable() {
- return searchable;
- }
-
- public void setSearchable(boolean searchable) {
- this.searchable = searchable;
- }
-
public String getSource() {
return source;
}
@@ -254,7 +242,7 @@ public class DatasetMetadataBean implements Serializable {
return "DatasetMetadataBean [id=" + id + ", title=" + title
+ ", description=" + description + ", customFields="
+ customFields + ", tags=" + tags + ", license=" + license
- + ", visibility=" + visibility + ", searchable=" + searchable
+ + ", visibility=" + visibility
+ ", source=" + source + ", version=" + version + ", author="
+ author + ", authorEmail=" + authorEmail + ", maintainer="
+ maintainer + ", maintainerEmail=" + maintainerEmail
diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/shared/ResourceBean.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/shared/ResourceBean.java
new file mode 100644
index 0000000..6ce3c1e
--- /dev/null
+++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/shared/ResourceBean.java
@@ -0,0 +1,108 @@
+package org.gcube.portlets.widgets.ckandatapublisherwidget.shared;
+
+import java.io.Serializable;
+
+/**
+ * A dataset's resource bean.
+ * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
+ */
+public class ResourceBean implements Serializable{
+
+ private static final long serialVersionUID = -6542455246456049712L;
+ private String url;
+ private String name;
+ private String description;
+ private String id;
+
+ public ResourceBean(){
+ super();
+ }
+
+ /**
+ * @param url
+ * @param name
+ * @param description
+ */
+ public ResourceBean(String url, String name, String description) {
+ super();
+ this.url = url;
+ this.name = name;
+ this.description = description;
+ }
+
+ /**
+ * @param url
+ * @param name
+ * @param description
+ * @param id
+ */
+ public ResourceBean(String url, String name, String description, String id) {
+ super();
+ this.url = url;
+ this.name = name;
+ this.description = description;
+ this.id = id;
+ }
+
+ /**
+ * @return the url
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ /**
+ * @param url the url to set
+ */
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param name the name to set
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return the description
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * @param description the description to set
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ /**
+ * @return the id
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return "ResourceBean [url=" + url + ", name=" + name + ", description="
+ + description + ", id=" + id + "]";
+ }
+}
diff --git a/src/main/resources/org/gcube/portlets/widgets/ckandatapublisherwidget/CKanMetadataPublisher.gwt.xml b/src/main/resources/org/gcube/portlets/widgets/ckandatapublisherwidget/CKanMetadataPublisher.gwt.xml
index 2c54a6b..fa37e4f 100644
--- a/src/main/resources/org/gcube/portlets/widgets/ckandatapublisherwidget/CKanMetadataPublisher.gwt.xml
+++ b/src/main/resources/org/gcube/portlets/widgets/ckandatapublisherwidget/CKanMetadataPublisher.gwt.xml
@@ -19,4 +19,7 @@
+
+
+
diff --git a/src/main/webapp/CKanMetadataPublisher.html b/src/main/webapp/CKanMetadataPublisher.html
index f0b17e1..7fa2fb4 100644
--- a/src/main/webapp/CKanMetadataPublisher.html
+++ b/src/main/webapp/CKanMetadataPublisher.html
@@ -5,6 +5,8 @@
+
diff --git a/src/main/webapp/js/showdown.js b/src/main/webapp/js/showdown.js
new file mode 100644
index 0000000..7f8d073
--- /dev/null
+++ b/src/main/webapp/js/showdown.js
@@ -0,0 +1,325 @@
+/**
+ * Created by Tivie on 06-01-2015.
+ */
+
+// Private properties
+var showdown = {},
+ parsers = {},
+ extensions = {},
+ globalOptions = getDefaultOpts(true),
+ flavor = {
+ github: {
+ omitExtraWLInCodeBlocks: true,
+ prefixHeaderId: 'user-content-',
+ simplifiedAutoLink: true,
+ literalMidWordUnderscores: true,
+ strikethrough: true,
+ tables: true,
+ tablesHeaderId: true,
+ ghCodeBlocks: true,
+ tasklists: true
+ },
+ vanilla: getDefaultOpts(true)
+ };
+
+/**
+ * helper namespace
+ * @type {{}}
+ */
+showdown.helper = {};
+
+/**
+ * TODO LEGACY SUPPORT CODE
+ * @type {{}}
+ */
+showdown.extensions = {};
+
+/**
+ * Set a global option
+ * @static
+ * @param {string} key
+ * @param {*} value
+ * @returns {showdown}
+ */
+showdown.setOption = function (key, value) {
+ 'use strict';
+ globalOptions[key] = value;
+ return this;
+};
+
+/**
+ * Get a global option
+ * @static
+ * @param {string} key
+ * @returns {*}
+ */
+showdown.getOption = function (key) {
+ 'use strict';
+ return globalOptions[key];
+};
+
+/**
+ * Get the global options
+ * @static
+ * @returns {{}}
+ */
+showdown.getOptions = function () {
+ 'use strict';
+ return globalOptions;
+};
+
+/**
+ * Reset global options to the default values
+ * @static
+ */
+showdown.resetOptions = function () {
+ 'use strict';
+ globalOptions = getDefaultOpts(true);
+};
+
+/**
+ * Set the flavor showdown should use as default
+ * @param {string} name
+ */
+showdown.setFlavor = function (name) {
+ 'use strict';
+ if (flavor.hasOwnProperty(name)) {
+ var preset = flavor[name];
+ for (var option in preset) {
+ if (preset.hasOwnProperty(option)) {
+ globalOptions[option] = preset[option];
+ }
+ }
+ }
+};
+
+/**
+ * Get the default options
+ * @static
+ * @param {boolean} [simple=true]
+ * @returns {{}}
+ */
+showdown.getDefaultOptions = function (simple) {
+ 'use strict';
+ return getDefaultOpts(simple);
+};
+
+/**
+ * Get or set a subParser
+ *
+ * subParser(name) - Get a registered subParser
+ * subParser(name, func) - Register a subParser
+ * @static
+ * @param {string} name
+ * @param {function} [func]
+ * @returns {*}
+ */
+showdown.subParser = function (name, func) {
+ 'use strict';
+ if (showdown.helper.isString(name)) {
+ if (typeof func !== 'undefined') {
+ parsers[name] = func;
+ } else {
+ if (parsers.hasOwnProperty(name)) {
+ return parsers[name];
+ } else {
+ throw Error('SubParser named ' + name + ' not registered!');
+ }
+ }
+ }
+};
+
+/**
+ * Gets or registers an extension
+ * @static
+ * @param {string} name
+ * @param {object|function=} ext
+ * @returns {*}
+ */
+showdown.extension = function (name, ext) {
+ 'use strict';
+
+ if (!showdown.helper.isString(name)) {
+ throw Error('Extension \'name\' must be a string');
+ }
+
+ name = showdown.helper.stdExtName(name);
+
+ // Getter
+ if (showdown.helper.isUndefined(ext)) {
+ if (!extensions.hasOwnProperty(name)) {
+ throw Error('Extension named ' + name + ' is not registered!');
+ }
+ return extensions[name];
+
+ // Setter
+ } else {
+ // Expand extension if it's wrapped in a function
+ if (typeof ext === 'function') {
+ ext = ext();
+ }
+
+ // Ensure extension is an array
+ if (!showdown.helper.isArray(ext)) {
+ ext = [ext];
+ }
+
+ var validExtension = validate(ext, name);
+
+ if (validExtension.valid) {
+ extensions[name] = ext;
+ } else {
+ throw Error(validExtension.error);
+ }
+ }
+};
+
+/**
+ * Gets all extensions registered
+ * @returns {{}}
+ */
+showdown.getAllExtensions = function () {
+ 'use strict';
+ return extensions;
+};
+
+/**
+ * Remove an extension
+ * @param {string} name
+ */
+showdown.removeExtension = function (name) {
+ 'use strict';
+ delete extensions[name];
+};
+
+/**
+ * Removes all extensions
+ */
+showdown.resetExtensions = function () {
+ 'use strict';
+ extensions = {};
+};
+
+/**
+ * Validate extension
+ * @param {array} extension
+ * @param {string} name
+ * @returns {{valid: boolean, error: string}}
+ */
+function validate(extension, name) {
+ 'use strict';
+
+ var errMsg = (name) ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension',
+ ret = {
+ valid: true,
+ error: ''
+ };
+
+ if (!showdown.helper.isArray(extension)) {
+ extension = [extension];
+ }
+
+ for (var i = 0; i < extension.length; ++i) {
+ var baseMsg = errMsg + ' sub-extension ' + i + ': ',
+ ext = extension[i];
+ if (typeof ext !== 'object') {
+ ret.valid = false;
+ ret.error = baseMsg + 'must be an object, but ' + typeof ext + ' given';
+ return ret;
+ }
+
+ if (!showdown.helper.isString(ext.type)) {
+ ret.valid = false;
+ ret.error = baseMsg + 'property "type" must be a string, but ' + typeof ext.type + ' given';
+ return ret;
+ }
+
+ var type = ext.type = ext.type.toLowerCase();
+
+ // normalize extension type
+ if (type === 'language') {
+ type = ext.type = 'lang';
+ }
+
+ if (type === 'html') {
+ type = ext.type = 'output';
+ }
+
+ if (type !== 'lang' && type !== 'output' && type !== 'listener') {
+ ret.valid = false;
+ ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang/language", "output/html" or "listener"';
+ return ret;
+ }
+
+ if (type === 'listener') {
+ if (showdown.helper.isUndefined(ext.listeners)) {
+ ret.valid = false;
+ ret.error = baseMsg + '. Extensions of type "listener" must have a property called "listeners"';
+ return ret;
+ }
+ } else {
+ if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
+ ret.valid = false;
+ ret.error = baseMsg + type + ' extensions must define either a "regex" property or a "filter" method';
+ return ret;
+ }
+ }
+
+ if (ext.listeners) {
+ if (typeof ext.listeners !== 'object') {
+ ret.valid = false;
+ ret.error = baseMsg + '"listeners" property must be an object but ' + typeof ext.listeners + ' given';
+ return ret;
+ }
+ for (var ln in ext.listeners) {
+ if (ext.listeners.hasOwnProperty(ln)) {
+ if (typeof ext.listeners[ln] !== 'function') {
+ ret.valid = false;
+ ret.error = baseMsg + '"listeners" property must be an hash of [event name]: [callback]. listeners.' + ln +
+ ' must be a function but ' + typeof ext.listeners[ln] + ' given';
+ return ret;
+ }
+ }
+ }
+ }
+
+ if (ext.filter) {
+ if (typeof ext.filter !== 'function') {
+ ret.valid = false;
+ ret.error = baseMsg + '"filter" must be a function, but ' + typeof ext.filter + ' given';
+ return ret;
+ }
+ } else if (ext.regex) {
+ if (showdown.helper.isString(ext.regex)) {
+ ext.regex = new RegExp(ext.regex, 'g');
+ }
+ if (!ext.regex instanceof RegExp) {
+ ret.valid = false;
+ ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + typeof ext.regex + ' given';
+ return ret;
+ }
+ if (showdown.helper.isUndefined(ext.replace)) {
+ ret.valid = false;
+ ret.error = baseMsg + '"regex" extensions must implement a replace string or function';
+ return ret;
+ }
+ }
+ }
+ return ret;
+}
+
+/**
+ * Validate extension
+ * @param {object} ext
+ * @returns {boolean}
+ */
+showdown.validateExtension = function (ext) {
+ 'use strict';
+
+ var validateExtension = validate(ext, null);
+ if (!validateExtension.valid) {
+ console.warn(validateExtension.error);
+ return false;
+ }
+ return true;
+};