You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@roller.apache.org by sn...@apache.org on 2018/08/26 14:46:50 UTC

roller git commit: Convert stylesheet edit page to Struts 2 Bootstrap and rework UI logic

Repository: roller
Updated Branches:
  refs/heads/bootstrap-ui 96a006503 -> 48e970895


Convert stylesheet edit page to Struts 2 Bootstrap and rework UI logic


Project: http://git-wip-us.apache.org/repos/asf/roller/repo
Commit: http://git-wip-us.apache.org/repos/asf/roller/commit/48e97089
Tree: http://git-wip-us.apache.org/repos/asf/roller/tree/48e97089
Diff: http://git-wip-us.apache.org/repos/asf/roller/diff/48e97089

Branch: refs/heads/bootstrap-ui
Commit: 48e970895e06008ff6ef981adf59ea6ea916c576
Parents: 96a0065
Author: Dave Johnson <sn...@gmail.com>
Authored: Sun Aug 26 10:46:47 2018 -0400
Committer: Dave Johnson <sn...@gmail.com>
Committed: Sun Aug 26 10:46:47 2018 -0400

----------------------------------------------------------------------
 .../ui/struts2/editor/StylesheetEdit.java       | 326 +++++++++----------
 .../resources/ApplicationResources.properties   |  65 ++--
 app/src/main/resources/struts.xml               |   1 +
 .../WEB-INF/jsps/editor/StylesheetEdit.jsp      | 143 ++++----
 .../WEB-INF/jsps/tiles/tiles-tabbedpage.jsp     |  17 +-
 app/src/main/webapp/WEB-INF/tiles.xml           |  13 +-
 app/src/main/webapp/roller-ui/styles/roller.css |   2 +-
 .../business/CustomTemplateRenditionTest.java   |   4 +-
 .../weblogger/business/ThemeManagerTest.java    |  64 ++++
 9 files changed, 357 insertions(+), 278 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/roller/blob/48e97089/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/StylesheetEdit.java
----------------------------------------------------------------------
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/StylesheetEdit.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/StylesheetEdit.java
index a206b05..861a152 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/StylesheetEdit.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/StylesheetEdit.java
@@ -18,31 +18,27 @@
 
 package org.apache.roller.weblogger.ui.struts2.editor;
 
-import java.util.Date;
-
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.roller.weblogger.WebloggerException;
 import org.apache.roller.weblogger.business.WeblogManager;
+import org.apache.roller.weblogger.business.Weblogger;
 import org.apache.roller.weblogger.business.WebloggerFactory;
+import org.apache.roller.weblogger.business.themes.SharedTheme;
 import org.apache.roller.weblogger.business.themes.ThemeManager;
-import org.apache.roller.weblogger.pojos.CustomTemplateRendition;
+import org.apache.roller.weblogger.pojos.*;
 import org.apache.roller.weblogger.pojos.TemplateRendition.RenditionType;
-import org.apache.roller.weblogger.pojos.TemplateRendition;
-import org.apache.roller.weblogger.pojos.Theme;
-import org.apache.roller.weblogger.pojos.ThemeTemplate;
 import org.apache.roller.weblogger.pojos.ThemeTemplate.ComponentType;
-import org.apache.roller.weblogger.pojos.Weblog;
-import org.apache.roller.weblogger.pojos.WeblogTemplate;
-import org.apache.roller.weblogger.pojos.WeblogTheme;
 import org.apache.roller.weblogger.ui.struts2.util.UIAction;
 import org.apache.roller.weblogger.util.cache.CacheManager;
 import org.apache.struts2.convention.annotation.AllowedMethods;
 
+import java.util.Date;
+
 /**
  * Action which handles editing for a weblog stylesheet override template.
  */
-// TODO: make this work @AllowedMethods({"execute","move","delete","revert"})
+// TODO: make this work @AllowedMethods({"execute","copyStylesheet","delete","revert"})
 public class StylesheetEdit extends UIAction {
 
     private static final long serialVersionUID = 4657591015852311907L;
@@ -56,13 +52,8 @@ public class StylesheetEdit extends UIAction {
     private String contentsStandard = null;
     private String contentsMobile = null;
 
-    private boolean sharedTheme;
-
-    // read by JSP to determine if user just deleted his shared theme customized stylesheet
-    private boolean sharedStylesheetDeleted;
-
-    // Do we have a custom stylesheet already for a shared theme
-    private boolean sharedThemeCustomStylesheet = false;
+    // if shared theme, is a stylesheet supported?
+    private boolean sharedThemeStylesheet = false;
 
     public StylesheetEdit() {
         this.actionName = "stylesheetEdit";
@@ -72,91 +63,26 @@ public class StylesheetEdit extends UIAction {
 
     @Override
     public void myPrepare() {
-        sharedTheme = !WeblogTheme.CUSTOM.equals(getActionWeblog().getEditorTheme());
-        sharedStylesheetDeleted = false;
 
-        ThemeTemplate stylesheet = null;
-        try {
-            stylesheet = getActionWeblog().getTheme().getStylesheet();
-        } catch (WebloggerException ex) {
-            log.error("Error looking up stylesheet on weblog - "
-                    + getActionWeblog().getHandle(), ex);
-        }
+        sharedThemeStylesheet = false;
+
+        WeblogManager weblogManager = WebloggerFactory.getWeblogger().getWeblogManager();
+        ThemeManager themeManager = WebloggerFactory.getWeblogger().getThemeManager();
 
-        if (stylesheet != null) {
-            log.debug("custom stylesheet path is - " + stylesheet.getLink());
+        if ( isSharedTheme() ) {
             try {
-                setTemplate(WebloggerFactory.getWeblogger().getWeblogManager()
-                        .getTemplateByLink(getActionWeblog(), stylesheet.getLink()));
-
-                if (getTemplate() == null) {
-                    log.debug("custom stylesheet not found, creating it");
-
-                    // template doesn't exist yet, so create it
-                    WeblogTemplate stylesheetTmpl = new WeblogTemplate();
-                    stylesheetTmpl.setWeblog(getActionWeblog());
-                    stylesheetTmpl.setAction(ThemeTemplate.ComponentType.STYLESHEET);
-                    stylesheetTmpl.setName(stylesheet.getName());
-                    stylesheetTmpl.setDescription(stylesheet.getDescription());
-                    stylesheetTmpl.setLink(stylesheet.getLink());
-                    stylesheetTmpl.setHidden(false);
-                    stylesheetTmpl.setNavbar(false);
-                    stylesheetTmpl.setLastModified(new Date());
-
-                    // create renditions for available rendition types
-                    TemplateRendition sCode = stylesheet.getTemplateRendition(RenditionType.STANDARD);
-                    if (sCode != null) {
-                        CustomTemplateRendition standardRendition = new CustomTemplateRendition(
-                                stylesheetTmpl, RenditionType.STANDARD);
-                        standardRendition.setTemplate(sCode.getTemplate());
-                        standardRendition.setTemplateLanguage(sCode.getTemplateLanguage());
-                        WebloggerFactory.getWeblogger().getWeblogManager()
-                                .saveTemplateRendition(standardRendition);
-                    }
-
-                    TemplateRendition mCode = stylesheet.getTemplateRendition(RenditionType.MOBILE);
-                    if (mCode != null) {
-                        CustomTemplateRendition mobileRendition = new CustomTemplateRendition(
-                                stylesheetTmpl, RenditionType.MOBILE);
-                        mobileRendition.setTemplate(mCode.getTemplate());
-                        mobileRendition.setTemplateLanguage(mCode
-                                .getTemplateLanguage());
-                        WebloggerFactory.getWeblogger().getWeblogManager()
-                                .saveTemplateRendition(mobileRendition);
-                    }
-
-                    WebloggerFactory.getWeblogger().getWeblogManager()
-                            .saveTemplate(stylesheetTmpl);
-                    setTemplate(stylesheetTmpl);
-                    WebloggerFactory.getWeblogger().flush();
-
-
-                    // success message
-                    addMessage("stylesheetEdit.create.success");
-                }
+                SharedTheme themeName = themeManager.getTheme(getActionWeblog().getEditorTheme());
+                sharedThemeStylesheet = themeName.getStylesheet() != null;
 
-                // See if we're using a shared theme with a custom stylesheet
-                if (!WeblogTheme.CUSTOM.equals(getActionWeblog()
-                        .getEditorTheme())
-                        && getActionWeblog().getTheme().getStylesheet() != null) {
-
-                    ThemeTemplate override = WebloggerFactory
-                            .getWeblogger()
-                            .getWeblogManager()
-                            .getTemplateByLink(
-                                    getActionWeblog(),
-                                    getActionWeblog().getTheme()
-                                            .getStylesheet().getLink());
-
-                    if (override != null) {
-                        sharedThemeCustomStylesheet = true;
-                    }
-                }
+                ThemeTemplate themeStylesheet  = themeName.getStylesheet();
+
+                WeblogTemplate weblogStylesheet =
+                    weblogManager.getTemplateByLink(getActionWeblog(), themeStylesheet.getLink());
+
+                setTemplate( weblogStylesheet );
 
             } catch (WebloggerException ex) {
-                log.error(
-                        "Error finding/adding stylesheet template from weblog - "
-                                + getActionWeblog().getHandle(), ex);
+                log.error("Error looking up shared theme name on weblog - " + getActionWeblog().getHandle(), ex);
             }
         }
     }
@@ -165,17 +91,16 @@ public class StylesheetEdit extends UIAction {
      * Show stylesheet edit page.
      */
     public String execute() {
+
         if (template != null) {
             try {
                 if (getTemplate().getTemplateRendition(RenditionType.STANDARD) != null) {
-                    setContentsStandard(getTemplate().getTemplateRendition(
-                            RenditionType.STANDARD).getTemplate());
+                    setContentsStandard(getTemplate().getTemplateRendition( RenditionType.STANDARD).getTemplate());
                 } else {
                     setContentsStandard("");
                 }
                 if (getTemplate().getTemplateRendition(RenditionType.MOBILE) != null) {
-                    setContentsMobile(getTemplate().getTemplateRendition(
-                            RenditionType.MOBILE).getTemplate());
+                    setContentsMobile(getTemplate().getTemplateRendition( RenditionType.MOBILE).getTemplate());
                 }
                 if (log.isDebugEnabled()) {
                     log.debug("Standard: " + getContentsStandard() + " Mobile: "
@@ -188,10 +113,83 @@ public class StylesheetEdit extends UIAction {
         return INPUT;
     }
 
+    public String copyStylesheet() {
+
+        WeblogManager weblogManager = WebloggerFactory.getWeblogger().getWeblogManager();
+        ThemeManager themeManager = WebloggerFactory.getWeblogger().getThemeManager();
+
+        ThemeTemplate stylesheet = null;
+
+        try {
+            SharedTheme themeName = themeManager.getTheme(getActionWeblog().getEditorTheme());
+            stylesheet = themeName.getStylesheet();
+
+        } catch (WebloggerException ex) {
+
+        }
+
+        log.debug("custom stylesheet path is - " + stylesheet.getLink());
+        try {
+            setTemplate( weblogManager.getTemplateByLink(getActionWeblog(), stylesheet.getLink()));
+
+            if (getTemplate() == null) {
+                log.debug("custom stylesheet not found, creating it");
+
+                WeblogTemplate stylesheetTmpl = new WeblogTemplate();
+                stylesheetTmpl.setWeblog(getActionWeblog());
+                stylesheetTmpl.setAction(ComponentType.STYLESHEET);
+                stylesheetTmpl.setName(stylesheet.getName());
+                stylesheetTmpl.setDescription(stylesheet.getDescription());
+                stylesheetTmpl.setLink(stylesheet.getLink());
+                stylesheetTmpl.setHidden(false);
+                stylesheetTmpl.setNavbar(false);
+                stylesheetTmpl.setLastModified(new Date());
+
+                // create renditions for available rendition types: standard and mobile
+
+                TemplateRendition sCode = stylesheet.getTemplateRendition(RenditionType.STANDARD);
+                if (sCode != null) {
+                    CustomTemplateRendition standardRendition = new CustomTemplateRendition(
+                        stylesheetTmpl, RenditionType.STANDARD);
+                    standardRendition.setTemplate(sCode.getTemplate());
+                    standardRendition.setTemplateLanguage(sCode.getTemplateLanguage());
+                    weblogManager.saveTemplateRendition(standardRendition);
+                }
+
+                TemplateRendition mCode = stylesheet.getTemplateRendition(RenditionType.MOBILE);
+                if (mCode != null) {
+                    CustomTemplateRendition mobileRendition =
+                        new CustomTemplateRendition(stylesheetTmpl, RenditionType.MOBILE);
+                    mobileRendition.setTemplate(mCode.getTemplate());
+                    mobileRendition.setTemplateLanguage(mCode.getTemplateLanguage());
+                    weblogManager.saveTemplateRendition(mobileRendition);
+                }
+
+                weblogManager.saveTemplate(stylesheetTmpl);
+                setTemplate(stylesheetTmpl);
+
+                WebloggerFactory.getWeblogger().flush();
+
+                // success message
+                addMessage("stylesheetEdit.create.success");
+            }
+
+        } catch (WebloggerException ex) {
+            log.error("Error finding/adding stylesheet template from weblog - "
+                + getActionWeblog().getHandle(), ex);
+            addError("generic.error.check.logs");
+        }
+
+        return revert();
+    }
+
     /**
      * Save an existing stylesheet.
      */
     public String save() {
+
+        WeblogManager weblogManager = WebloggerFactory.getWeblogger().getWeblogManager();
+
         if (!hasActionErrors()) {
             try {
 
@@ -202,26 +200,21 @@ public class StylesheetEdit extends UIAction {
 
                 if (stylesheet.getTemplateRendition(RenditionType.STANDARD) != null) {
                     // if we have a template, then set it
-                    CustomTemplateRendition tc = stylesheet
-                            .getTemplateRendition(RenditionType.STANDARD);
+                    CustomTemplateRendition tc = stylesheet.getTemplateRendition(RenditionType.STANDARD);
                     tc.setTemplate(getContentsStandard());
-                    WebloggerFactory.getWeblogger().getWeblogManager()
-                            .saveTemplateRendition(tc);
+                    weblogManager.saveTemplateRendition(tc);
+
                 } else {
                     // otherwise create it, then set it
-                    CustomTemplateRendition tc = new CustomTemplateRendition(
-                            stylesheet, RenditionType.STANDARD);
+                    CustomTemplateRendition tc = new CustomTemplateRendition( stylesheet, RenditionType.STANDARD);
                     tc.setTemplate("");
-                    WebloggerFactory.getWeblogger().getWeblogManager()
-                            .saveTemplateRendition(tc);
+                    weblogManager.saveTemplateRendition(tc);
                 }
 
                 if (stylesheet.getTemplateRendition(RenditionType.MOBILE) != null) {
-                    CustomTemplateRendition tc = stylesheet
-                            .getTemplateRendition(RenditionType.MOBILE);
+                    CustomTemplateRendition tc = stylesheet.getTemplateRendition(RenditionType.MOBILE);
                     tc.setTemplate(getContentsMobile());
-                    WebloggerFactory.getWeblogger().getWeblogManager()
-                            .saveTemplateRendition(tc);
+                    weblogManager.saveTemplateRendition(tc);
                 }
 
                 // save template and flush
@@ -248,53 +241,54 @@ public class StylesheetEdit extends UIAction {
      * Revert the stylesheet to its original state.  UI provides this only for shared themes.
      */
     public String revert() {
-        if (sharedTheme && !hasActionErrors()) {
+
+        WeblogManager weblogManager = WebloggerFactory.getWeblogger().getWeblogManager();
+
+        if (isSharedTheme() && !hasActionErrors()) {
             try {
 
                 WeblogTemplate stylesheet = getTemplate();
 
                 // lookup the theme used by this weblog
-                ThemeManager tmgr = WebloggerFactory.getWeblogger()
-                        .getThemeManager();
+                ThemeManager tmgr = WebloggerFactory.getWeblogger() .getThemeManager();
                 Theme theme = tmgr.getTheme(getActionWeblog().getEditorTheme());
 
                 stylesheet.setLastModified(new Date());
 
                 if (stylesheet.getTemplateRendition(RenditionType.STANDARD) != null) {
-                    TemplateRendition templateCode = theme.getStylesheet()
-                            .getTemplateRendition(RenditionType.STANDARD);
+
+                    TemplateRendition templateCode =
+                        theme.getStylesheet().getTemplateRendition(RenditionType.STANDARD);
+
                     // if we have a template, then set it
-                    CustomTemplateRendition existingTemplateCode = stylesheet
-                            .getTemplateRendition(RenditionType.STANDARD);
-                    existingTemplateCode
-                            .setTemplate(templateCode.getTemplate());
-                    WebloggerFactory.getWeblogger().getWeblogManager()
-                            .saveTemplateRendition(existingTemplateCode);
+                    CustomTemplateRendition existingTemplateCode =
+                        stylesheet.getTemplateRendition(RenditionType.STANDARD);
+
+                    existingTemplateCode.setTemplate(templateCode.getTemplate());
+                    weblogManager.saveTemplateRendition(existingTemplateCode);
                 }
                 if (stylesheet.getTemplateRendition(RenditionType.MOBILE) != null) {
-                    TemplateRendition templateCode = theme.getStylesheet()
-                            .getTemplateRendition(RenditionType.MOBILE);
-                    CustomTemplateRendition existingTemplateCode = stylesheet
-                            .getTemplateRendition(RenditionType.MOBILE);
-                    existingTemplateCode
-                            .setTemplate(templateCode.getTemplate());
+
+                    TemplateRendition templateCode =
+                        theme.getStylesheet().getTemplateRendition(RenditionType.MOBILE);
+                    CustomTemplateRendition existingTemplateCode =
+                        stylesheet.getTemplateRendition(RenditionType.MOBILE);
+
+                    existingTemplateCode.setTemplate(templateCode.getTemplate());
                 }
 
                 // save template and flush
-                WebloggerFactory.getWeblogger().getWeblogManager()
-                        .saveTemplate(stylesheet);
+                weblogManager.saveTemplate(stylesheet);
                 WebloggerFactory.getWeblogger().flush();
 
                 // notify caches
                 CacheManager.invalidate(stylesheet);
 
                 // success message
-                addMessage("stylesheetEdit.revert.success",
-                        stylesheet.getName());
+                addMessage("stylesheetEdit.revert.success", stylesheet.getName());
 
             } catch (WebloggerException ex) {
-                log.error("Error updating stylesheet template for weblog - "
-                        + getActionWeblog().getHandle(), ex);
+                log.error("Error updating stylesheet template for weblog - " + getActionWeblog().getHandle(), ex);
                 addError("generic.error.check.logs");
             }
         }
@@ -305,19 +299,18 @@ public class StylesheetEdit extends UIAction {
      * set theme to default stylesheet, ie delete it.
      */
     public String delete() {
-        if (template != null && sharedTheme && !hasActionErrors()) {
+        if (template != null && isSharedTheme() && !hasActionErrors()) {
             try {
                 // Delete template and flush
-                WeblogManager mgr = WebloggerFactory.getWeblogger()
-                        .getWeblogManager();
+                WeblogManager weblogManager = WebloggerFactory.getWeblogger().getWeblogManager();
 
                 // Remove template and page codes
-                mgr.removeTemplate(template);
+                weblogManager.removeTemplate(template);
 
                 Weblog weblog = getActionWeblog();
 
                 // save updated weblog and flush
-                mgr.saveWeblog(weblog);
+                weblogManager.saveWeblog(weblog);
 
                 // notify caches
                 CacheManager.invalidate(template);
@@ -326,15 +319,12 @@ public class StylesheetEdit extends UIAction {
                 WebloggerFactory.getWeblogger().flush();
 
                 // success message
-                addMessage("stylesheetEdit.default.success",
-                        template.getName());
+                addMessage("stylesheetEdit.default.success", template.getName());
 
                 template = null;
-                sharedStylesheetDeleted = true;
 
             } catch (Exception e) {
-                log.error("Error deleting stylesheet template for weblog - "
-                        + getActionWeblog().getHandle(), e);
+                log.error("Error deleting stylesheet template for weblog - " + getActionWeblog().getHandle(), e);
                 addError("generic.error.check.logs");
             }
         }
@@ -343,7 +333,6 @@ public class StylesheetEdit extends UIAction {
 
     /**
      * Checks if is custom theme.
-     * 
      * @return true, if is custom theme
      */
     public boolean isCustomTheme() {
@@ -351,8 +340,19 @@ public class StylesheetEdit extends UIAction {
     }
 
     /**
+     * Checks if is shared theme.
+     * @return true, if is shared theme
+     */
+    public boolean isSharedTheme() {
+        return !isCustomTheme();
+    }
+
+    public boolean isSharedThemeStylesheet() {
+        return sharedThemeStylesheet;
+    }
+
+    /**
      * Gets the template.
-     * 
      * @return the template
      */
     public WeblogTemplate getTemplate() {
@@ -361,9 +361,7 @@ public class StylesheetEdit extends UIAction {
 
     /**
      * Sets the template.
-     * 
-     * @param template
-     *            the new template
+     * @param template the new template
      */
     public void setTemplate(WeblogTemplate template) {
         this.template = template;
@@ -371,7 +369,6 @@ public class StylesheetEdit extends UIAction {
 
     /**
      * Gets the contents standard.
-     * 
      * @return the contents standard
      */
     public String getContentsStandard() {
@@ -380,9 +377,7 @@ public class StylesheetEdit extends UIAction {
 
     /**
      * Sets the contents standard.
-     * 
-     * @param contents
-     *            the new contents standard
+     * @param contents the new contents standard
      */
     public void setContentsStandard(String contents) {
         this.contentsStandard = contents;
@@ -390,7 +385,6 @@ public class StylesheetEdit extends UIAction {
 
     /**
      * Gets the contents mobile.
-     * 
      * @return the contents mobile
      */
     public String getContentsMobile() {
@@ -399,29 +393,9 @@ public class StylesheetEdit extends UIAction {
 
     /**
      * Sets the contents mobile.
-     * 
-     * @param contents
-     *            the new contents mobile
+     * @param contents the new contents mobile
      */
     public void setContentsMobile(String contents) {
         this.contentsMobile = contents;
     }
-
-    /**
-     * Checks if using a shared theme with a custom stylesheet.
-     * 
-     * @return true, if checks if shared theme and custom stylesheet
-     */
-    public boolean isSharedThemeCustomStylesheet() {
-        return sharedThemeCustomStylesheet;
-    }
-
-    /**
-     * Checks if user just deleted his custom shared stylesheet
-     *
-     * @return true, if custom shared stylesheet was deleted.
-     */
-    public boolean isSharedStylesheetDeleted() {
-        return sharedStylesheetDeleted;
-    }
 }

http://git-wip-us.apache.org/repos/asf/roller/blob/48e97089/app/src/main/resources/ApplicationResources.properties
----------------------------------------------------------------------
diff --git a/app/src/main/resources/ApplicationResources.properties b/app/src/main/resources/ApplicationResources.properties
index 09747fd..da0e07b 100644
--- a/app/src/main/resources/ApplicationResources.properties
+++ b/app/src/main/resources/ApplicationResources.properties
@@ -1216,30 +1216,53 @@ statCount.weblogDayHits=Today''s hit count
 # ------------------------------------------------------------------ Stylesheet Editor
 
 stylesheetEdit.title=Stylesheet
+
 stylesheetEdit.subtitle=Edit weblog custom stylesheet
-stylesheetEdit.tip=This form allows you to edit the stylesheet for your theme.
-stylesheetEdit.revertTip=If you run into any problems you don''t know how to \
-fix you can start over at any time by clicking the restore button.  Please <b>backup</b> your stylesheet!
-stylesheetEdit.revertTip1=<font color="red">If you have not customized your stylesheet here, \
-please use the <em>Shared Theme</em> default by clicking the delete stylesheet button. \
-This will ensure you are using the latest version and any changes will be picked up.</font> 
 
-stylesheetEdit.revert=Restore Stylesheet
-stylesheetEdit.delete=Delete Stylesheet
+stylesheetEdit.copyStylesheet=Copy Stylesheet
+stylesheetEdit.copyStylesheetTip=Copy Shared Theme's stylesheet into your weblog so you can edit it.
+
+stylesheetEdit.standard=Standard
+
+stylesheetEdit.mobile=Mobile
+
+stylesheetEdit.tip=This form allows you to edit the customizable CSS Stylesheet for your theme.
+
+stylesheetEdit.youCanCustomize=\
+  <p><b>You can customize!</b> \
+  The theme you are using provides a customizable stylesheet. If you make changes to it \
+  here you will see those changes reflected in the theme pages that include the stylesheet.</p> \
+  <p>You can also choose to <b>Revert</b> your copy of the stylesheet so that it matches the default one from \
+  the theme. Or you can <b>Delete</b> your copy so that this weblog will use the Shared Theme's stylesheet instead.</p>
+
+stylesheetEdit.sharedThemeWithStylesheet=\
+ <p>The <b>Shared Theme</b> you are using provides a customizable stylesheet.</p> \
+ <p>Would you like to copy that stylesheet into your weblog so you can edit it?</p>
+
+stylesheetEdit.sharedThemeNoStylesheetSupport=\
+  <p>The <b>Shared Theme</b> you are using does not provide a customizable stylesheet.</p> \
+  <p>Choose another theme if you want to be able to customize your CSS.</p>
+
+stylesheetEdit.customThemeNoStylesheet=\
+  The <b>Custom Theme</b> you are using does not provide a customizable stylesheet, but since \
+  you are using a Custom Theme, you can add any stylesheets and new templates that you wish.
+
+stylesheetEdit.revert=Revert
+stylesheetEdit.revertTip=Reverts this weblog's copy to match the Shared Theme's default stylesheet.
+
+stylesheetEdit.delete=Delete
+stylesheetEdit.deleteTip=Delete's this weblog's copy setup weblog to use the Shared Theme's default stylesheet.
 
 stylesheetEdit.create.success=Custom stylesheet created successfully.
+
 stylesheetEdit.save.success=Stylesheet updated successfully.
+
 stylesheetEdit.revert.success=Stylesheet reverted successfully.
-stylesheetEdit.default.success=Stylesheet deleted successfully.  Your theme is now using the Shared Theme default.
 
-stylesheetEdit.noOverridableStylesheetAvailable=\
-The theme you''re using does not define a customizable stylesheet in its descriptor.  You can choose another shared theme, or for \
-  custom themes, create the stylesheet on the templates tab instead.
+stylesheetEdit.default.success=Stylesheet deleted successfully. Your theme is now using the Shared Theme default.
 
-stylesheetEdit.canRecreateStylesheetOverride=Note: clicking the stylesheet tab again will \
-recreate a custom override stylesheet from the theme default.
+stylesheetEdit.confirmDelete=Are you sure you want to delete your stylesheet and use the themes default instead?
 
-stylesheetEdit.confirmDelete=Are you sure you want to delete your stylesheet?
 stylesheetEdit.confirmRevert=Are you sure you want to revert your stylesheet to the theme default?
 
 # ------------------------------------------------------------------ Tabbed Menu
@@ -1321,12 +1344,16 @@ themeEditor.previewDescription=Before you update your theme you can use <b>Previ
 
 themeEditor.importWarning=WARNING: Updating your custom theme <b>will overwrite</b> \
   some of your existing templates.
+
 themeEditor.importRequired=Since this is the first time using a  custom theme, \
   Roller will copy the templates from your existing Shared Theme so you can edit them.
-themeEditor.importAndOverwriteTemplates=Import the Shared Theme selected above and overwrite existing custom theme.
-themeEditor.existingTemplatesWarning=<b>This blog already has a custom theme defined so you need to make a choice</b>. \
-  Do you want to switch back to using that theme, or do you want to over write that theme with the \
-  Shared Theme that is selected above?
+
+themeEditor.importAndOverwriteTemplates=Import the Shared Theme selected above and overwrite \
+  existing custom theme.
+
+themeEditor.existingTemplatesWarning=<b>This blog already has a custom theme defined so you need to \
+  make a choice</b>. Do you want to switch back to using that theme, or do you want to over write that \
+  theme with the Shared Theme currently in use?
 themeEditor.setTheme.success=Theme set to {0}
 themeEditor.setCustomTheme.success=Shared theme {0} imported into custom templates
 themeEditor.setCustomTheme.instructions=Use the Stylesheet and Templates tabs above to edit your custom theme.

http://git-wip-us.apache.org/repos/asf/roller/blob/48e97089/app/src/main/resources/struts.xml
----------------------------------------------------------------------
diff --git a/app/src/main/resources/struts.xml b/app/src/main/resources/struts.xml
index 00258b2..b96672d 100644
--- a/app/src/main/resources/struts.xml
+++ b/app/src/main/resources/struts.xml
@@ -282,6 +282,7 @@
         <global-allowed-methods>
             add,
             cancel,
+            copyStylesheet,
             createNewDirectory,
             execute,
             delete,

http://git-wip-us.apache.org/repos/asf/roller/blob/48e97089/app/src/main/webapp/WEB-INF/jsps/editor/StylesheetEdit.jsp
----------------------------------------------------------------------
diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/StylesheetEdit.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/StylesheetEdit.jsp
index 7196d51..aa1b4b8 100644
--- a/app/src/main/webapp/WEB-INF/jsps/editor/StylesheetEdit.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/editor/StylesheetEdit.jsp
@@ -19,92 +19,107 @@
 
 <p class="subtitle"><s:text name="stylesheetEdit.subtitle" /></p>
 
-<s:if test="template == null">
-    <div class="notification">
-        <s:if test="sharedStylesheetDeleted">
-            <%-- clicking the stylesheet tab will recreate your custom override stylesheet ... --%>
-            <s:text name="stylesheetEdit.canRecreateStylesheetOverride" />
-        </s:if>
-        <s:else>
-            <%-- shared theme does not offer a stylesheet ... --%>
-            <s:text name="stylesheetEdit.noOverridableStylesheetAvailable" />
-        </s:else>
-    </div>
-</s:if>
-<s:else>
-    <p class="pagetip">
-        <s:text name="stylesheetEdit.tip" />
-        <s:if test="!customTheme">
-            <s:text name="stylesheetEdit.revertTip" />
-            <s:if test="sharedThemeCustomStylesheet">
-                <br /><br /><s:text name="stylesheetEdit.revertTip1" />
-            </s:if>
-        </s:if>
-    </p>
 
-    <s:form action="stylesheetEdit!save">
+<s:if test="template != null">
+
+    <s:text name="stylesheetEdit.youCanCustomize" />
+
+    <s:form action="stylesheetEdit!save" theme="bootstrap" cssClass="form-vertical">
         <s:hidden name="salt" />
         <s:hidden name="weblog" />
 
-        <%-- ================================================================== --%>
         <%-- Tabs for each of the two content areas: Standard and Mobile --%>
+        <ul id="template-code-tabs" class="nav nav-tabs" role="tablist" style="margin-bottom: 1em">
+
+            <li role="presentation" class="active">
+                <a href="#tabStandard" aria-controls="home" role="tab" data-toggle="tab">
+                    <em><s:text name="stylesheetEdit.standard"/></em>
+                </a>
+            </li>
 
-        <div id="template-code-tabs">
-        <ul>
-            <li class="selected"><a href="#tabStandard"><em>Standard</em></a></li>
             <s:if test="contentsMobile != null">
-                <li><a href="#tabMobile"><em>Mobile</em></a></li>
+                <li role="presentation">
+                    <a href="#tabMobile" aria-controls="home" role="tab" data-toggle="tab">
+                        <em><s:text name="stylesheetEdit.mobile"/></em>
+                    </a>
+                </li>
             </s:if>
+
         </ul>
-        <div>
-            <div id="tabStandard">
+
+        <%-- Tab content for each of the two content areas: Standard and Mobile --%>
+        <div class="tab-content">
+
+            <div role="tabpanel" class="tab-pane active" id="tabStandard">
                 <s:textarea name="contentsStandard" cols="80" rows="30" cssStyle="width:100%" />
             </div>
+
             <s:if test="contentsMobile != null">
-                <div id="tabMobile">
+                <div role="tabpanel" class="tab-pane" id="tabMobile">
                     <s:textarea name="contentsMobile" cols="80" rows="30" cssStyle="width:100%" />
                 </div>
             </s:if>
-        </div>
+
         </div>
 
-        <%-- ================================================================== --%>
         <%-- Save, Close and Resize text area buttons--%>
+        <s:submit value="%{getText('generic.save')}" cssClass="btn btn-success" />
 
-        <table style="width:100%">
-            <tr>
-                <td>
-                    <s:submit value="%{getText('generic.save')}" />&nbsp;&nbsp;
-                    <s:if test="!customTheme">
-                        <s:submit value="%{getText('stylesheetEdit.revert')}" onclick="revertStylesheet();return false;" />
-                    </s:if>
-                    <%-- Only delete if we have no custom templates ie website.customStylesheetPath=null --%>
-                    <s:if test="sharedThemeCustomStylesheet">
-                        <s:submit value="%{getText('stylesheetEdit.delete')}" onclick="deleteStylesheet();return false;" />
-                    </s:if>
-                </td>
-            </tr>
-        </table>
+        <s:if test="!customTheme">
+            <s:submit value="%{getText('stylesheetEdit.revert')}" cssClass="btn"
+                onclick="revertStylesheet();return false;"
+                      tooltip="%{getText('stylesheetEdit.revertTip')}" />
+        </s:if>
+
+        <%-- Only delete if we have no custom templates ie website.customStylesheetPath=null --%>
+        <s:if test="sharedThemeStylesheet">
+            <s:submit value="%{getText('stylesheetEdit.delete')}"  cssClass="btn btn-danger"
+                onclick="deleteStylesheet();return false;"
+                      tooltip="%{getText('stylesheetEdit.deleteTip')}" />
+        </s:if>
 
     </s:form>
 
-    <script>
-        function revertStylesheet() {
-            if (window.confirm('<s:text name="stylesheetEdit.confirmRevert"/>')) {
-                document.stylesheetEdit.action = "<s:url action='stylesheetEdit!revert' />";
+</s:if>
+<s:elseif test="sharedTheme">
+
+    <s:if test="sharedThemeStylesheet">
+
+        <s:text name="stylesheetEdit.sharedThemeWithStylesheet" />
+
+        <s:form action="stylesheetEdit!copyStylesheet" theme="bootstrap" cssClass="form-vertical">
+            <s:hidden name="salt" />
+            <s:hidden name="weblog" />
+            <s:submit value="%{getText('stylesheetEdit.copyStylesheet')}" cssClass="btn btn-success"
+                tooltip="%{getText('stylesheetEdit.createStylesheetTip')}" />
+        </s:form>
+
+    </s:if>
+    <s:else>
+        <p><s:text name="stylesheetEdit.sharedThemeNoStylesheetSupport" /></p>
+    </s:else>
+
+</s:elseif>
+<s:else>
+    <s:text name="stylesheetEdit.customThemeNoStylesheet" />
+</s:else>
+
+<script type="text/javascript">
+
+    function revertStylesheet() {
+        if (window.confirm('<s:text name="stylesheetEdit.confirmRevert"/>')) {
+            document.stylesheetEdit.action = "<s:url action='stylesheetEdit!revert' />";
+            document.stylesheetEdit.submit();
+        }
+    };
+    <s:if test="%{sharedThemeStylesheet}">
+        function deleteStylesheet() {
+            if (window.confirm('<s:text name="stylesheetEdit.confirmDelete"/>')) {
+                document.stylesheetEdit.action = "<s:url action='stylesheetEdit!delete' />";
                 document.stylesheetEdit.submit();
             }
         };
-        <s:if test="%{sharedThemeCustomStylesheet}">
-            function deleteStylesheet() {
-                if (window.confirm('<s:text name="stylesheetEdit.confirmDelete"/>')) {
-                    document.stylesheetEdit.action = "<s:url action='stylesheetEdit!delete' />";
-                    document.stylesheetEdit.submit();
-                }
-            };
-        </s:if>
-        $(function() {
-            $( "#template-code-tabs" ).tabs();
-        });
-    </script>
-</s:else>
+    </s:if>
+
+</script>
+

http://git-wip-us.apache.org/repos/asf/roller/blob/48e97089/app/src/main/webapp/WEB-INF/jsps/tiles/tiles-tabbedpage.jsp
----------------------------------------------------------------------
diff --git a/app/src/main/webapp/WEB-INF/jsps/tiles/tiles-tabbedpage.jsp b/app/src/main/webapp/WEB-INF/jsps/tiles/tiles-tabbedpage.jsp
index 8073292..9d8d957 100644
--- a/app/src/main/webapp/WEB-INF/jsps/tiles/tiles-tabbedpage.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/tiles/tiles-tabbedpage.jsp
@@ -30,7 +30,10 @@
 
 <tiles:insertAttribute name="bannerStatus"/>
 
+<tilesx:useAttribute name="sidebar">
+
 <div class="row">
+
     <div class="col-md-8 roller-column-left">
         <div class="panel panel-default">
             <div class="panel-body">
@@ -40,6 +43,7 @@
             </div>
         </div>
     </div>
+
     <div class="col-md-4 roller-column-right">
         <div class="panel panel-default">
             <div class="panel-body">
@@ -48,12 +52,17 @@
                 </s:if>
             </div>
         </div>
-        <div class="panel panel-default">
-            <div class="panel-body">
-                <tiles:insertAttribute name="sidebar"/>
+
+        <c:if test="${sidebar == '/WEB-INF/jsps/tiles/empty.jsp'}">
+            <div class="panel panel-default">
+                <div class="panel-body">
+                    <tiles:insertAttribute name="sidebar"/>
+                </div>
             </div>
-        </div>
+        </c:if>
+
     </div>
+
 </div>
 
 <footer class="footer">

http://git-wip-us.apache.org/repos/asf/roller/blob/48e97089/app/src/main/webapp/WEB-INF/tiles.xml
----------------------------------------------------------------------
diff --git a/app/src/main/webapp/WEB-INF/tiles.xml b/app/src/main/webapp/WEB-INF/tiles.xml
index eef1052..429954f 100644
--- a/app/src/main/webapp/WEB-INF/tiles.xml
+++ b/app/src/main/webapp/WEB-INF/tiles.xml
@@ -44,17 +44,6 @@
         <put-attribute name="footer"       value="/WEB-INF/jsps/tiles/footer.jsp" />
     </definition>
 
-    <definition name=".tiles-simple-tabbedpage" template="/WEB-INF/jsps/tiles/tiles-simple-tabbedpage.jsp">
-        <put-attribute name="bannerStatus" value="/WEB-INF/jsps/tiles/bannerStatus.jsp" />
-        <put-attribute name="userStatus"   value="/WEB-INF/jsps/tiles/userStatus.jsp" />
-        <put-attribute name="head"         value="/WEB-INF/jsps/tiles/head.jsp" />
-        <put-attribute name="styles"       value="/WEB-INF/jsps/tiles/empty.jsp" />
-        <put-attribute name="menu"         value="/WEB-INF/jsps/tiles/menu.jsp" />
-        <put-attribute name="messages"     value="/WEB-INF/jsps/tiles/messages.jsp" />
-        <put-attribute name="content"      value="${content}" />
-        <put-attribute name="footer"       value="/WEB-INF/jsps/tiles/footer.jsp" />
-    </definition>
-
     <definition name=".tiles-simplepage" template="/WEB-INF/jsps/tiles/tiles-simplepage.jsp">
         <put-attribute name="banner"       value="/WEB-INF/jsps/tiles/empty.jsp" />
         <put-attribute name="bannerStatus" value="/WEB-INF/jsps/tiles/bannerStatus.jsp" />
@@ -358,7 +347,7 @@
         <put-attribute name="styles" value="/WEB-INF/jsps/tiles/empty.jsp" />
     </definition>
     
-    <definition name=".ThemeEdit" extends=".tiles-simple-tabbedpage" >
+    <definition name=".ThemeEdit" extends=".tiles-tabbedpage" >
         <put-attribute name="head" value="/WEB-INF/jsps/tiles/head.jsp" />
         <put-attribute name="content" value="/WEB-INF/jsps/editor/ThemeEdit.jsp" />
     </definition>

http://git-wip-us.apache.org/repos/asf/roller/blob/48e97089/app/src/main/webapp/roller-ui/styles/roller.css
----------------------------------------------------------------------
diff --git a/app/src/main/webapp/roller-ui/styles/roller.css b/app/src/main/webapp/roller-ui/styles/roller.css
index 319a59a..0fced51 100644
--- a/app/src/main/webapp/roller-ui/styles/roller.css
+++ b/app/src/main/webapp/roller-ui/styles/roller.css
@@ -306,6 +306,6 @@ div.image-controls input {
 }
 
 #sharedChooser, #customChooser {
-    height: 10em;
+    height: 13em;
 }
 

http://git-wip-us.apache.org/repos/asf/roller/blob/48e97089/app/src/test/java/org/apache/roller/weblogger/business/CustomTemplateRenditionTest.java
----------------------------------------------------------------------
diff --git a/app/src/test/java/org/apache/roller/weblogger/business/CustomTemplateRenditionTest.java b/app/src/test/java/org/apache/roller/weblogger/business/CustomTemplateRenditionTest.java
index fe76dff..ba45b24 100644
--- a/app/src/test/java/org/apache/roller/weblogger/business/CustomTemplateRenditionTest.java
+++ b/app/src/test/java/org/apache/roller/weblogger/business/CustomTemplateRenditionTest.java
@@ -32,8 +32,8 @@ import org.apache.roller.weblogger.pojos.User;
 import org.apache.roller.weblogger.pojos.Weblog;
 import org.apache.roller.weblogger.pojos.WeblogTemplate;
 
-public class CustomTemplateRenditionTest extends TestCase{
-    public static Log log = LogFactory.getLog(WeblogPageTest.class);
+public class CustomTemplateRenditionTest extends TestCase {
+    public static Log log = LogFactory.getLog(CustomTemplateRenditionTest.class);
 
        User testUser = null;
        Weblog testWeblog = null;

http://git-wip-us.apache.org/repos/asf/roller/blob/48e97089/app/src/test/java/org/apache/roller/weblogger/business/ThemeManagerTest.java
----------------------------------------------------------------------
diff --git a/app/src/test/java/org/apache/roller/weblogger/business/ThemeManagerTest.java b/app/src/test/java/org/apache/roller/weblogger/business/ThemeManagerTest.java
new file mode 100644
index 0000000..e7bb9c8
--- /dev/null
+++ b/app/src/test/java/org/apache/roller/weblogger/business/ThemeManagerTest.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  The ASF licenses this file to You
+ * under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.  For additional information regarding
+ * copyright in this work, please see the NOTICE file in the top level
+ * directory of this distribution.
+ */
+
+package org.apache.roller.weblogger.business;
+
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.weblogger.TestUtils;
+import org.apache.roller.weblogger.business.themes.ThemeManager;
+import org.apache.roller.weblogger.pojos.*;
+
+public class ThemeManagerTest extends TestCase {
+    public static Log log = LogFactory.getLog(ThemeManagerTest.class);
+
+    public ThemeManagerTest(String name) {
+        super(name);
+    }
+
+    public static Test suite() {
+        return new TestSuite(CustomTemplateRenditionTest.class);
+    }
+
+
+    public void setUp() throws Exception {
+        TestUtils.setupWeblogger();
+    }
+
+    public void tearDown() throws Exception {
+    }
+
+    public void testThemeAssumptions() throws Exception {
+
+        ThemeManager themeManager = WebloggerFactory.getWeblogger().getThemeManager();
+        themeManager.initialize();
+
+        assertNotNull( themeManager.getTheme("basic") );
+        assertNotNull( themeManager.getTheme("basic").getStylesheet() );
+        assertNull( themeManager.getTheme("frontpage").getStylesheet() );
+    }
+        
+}
+
+
+
+