From d0f961f2ac5d81c78c05951114fbc4cc2a65ec18 Mon Sep 17 00:00:00 2001 From: oahzeved Date: Tue, 16 Jun 2020 14:42:07 +0800 Subject: [PATCH] feat: Nav support relative/absolute url --- @rbv | 2 +- .../configuration/portals/NavBuilder.java | 198 ++++++++++++++++++ .../configuration/portals/NavManager.java | 175 +--------------- src/main/webapp/_include/NavLeft.jsp | 8 +- src/main/webapp/assets/js/nav-settings.jsx | 2 +- src/main/webapp/commons/nav-settings.jsp | 3 +- src/main/webapp/commons/search-icon.jsp | 8 +- .../configuration/portals/NavManagerTest.java | 10 +- 8 files changed, 218 insertions(+), 188 deletions(-) create mode 100644 src/main/java/com/rebuild/server/configuration/portals/NavBuilder.java diff --git a/@rbv b/@rbv index 743aa9c6f..e75127ce0 160000 --- a/@rbv +++ b/@rbv @@ -1 +1 @@ -Subproject commit 743aa9c6fb21bbf3f88a9eec93b34c0ee8a4c4a6 +Subproject commit e75127ce05c1ad0b93ffff42d545bd21bc7971b7 diff --git a/src/main/java/com/rebuild/server/configuration/portals/NavBuilder.java b/src/main/java/com/rebuild/server/configuration/portals/NavBuilder.java new file mode 100644 index 000000000..eff25c4b0 --- /dev/null +++ b/src/main/java/com/rebuild/server/configuration/portals/NavBuilder.java @@ -0,0 +1,198 @@ +/* +Copyright (c) REBUILD and its owners. All rights reserved. + +rebuild is dual-licensed under commercial and open source licenses (GPLv3). +See LICENSE and COMMERCIAL in the project root for license information. +*/ + +package com.rebuild.server.configuration.portals; + +import cn.devezhao.commons.CodecUtils; +import cn.devezhao.persist4j.Entity; +import cn.devezhao.persist4j.engine.ID; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.rebuild.server.Application; +import com.rebuild.server.ServerListener; +import com.rebuild.server.configuration.ConfigEntry; +import com.rebuild.server.metadata.MetadataHelper; +import com.rebuild.utils.AppUtils; +import com.rebuild.utils.JSONUtils; +import org.apache.commons.lang.StringUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; + +import javax.servlet.http.HttpServletRequest; +import java.util.Iterator; + +/** + * 导航渲染 + * + * @author devezhao + * @since 2020/6/16 + */ +public class NavBuilder extends NavManager { + + public static final NavBuilder instance = new NavBuilder(); + private NavBuilder() { } + + /** + * 默认导航 + */ + private static final JSONArray NAVS_DEFAULT = JSONUtils.toJSONObjectArray( + new String[] { "icon", "text", "type", "value" }, + new Object[][] { + new Object[] { "chart-donut", "动态", "ENTITY", NAV_FEEDS }, + new Object[] { "folder", "文件", "ENTITY", NAV_FILEMRG } + }); + + /** + * @param request + * @return + */ + public JSONArray getNavPortal(HttpServletRequest request) { + return getNavPortal(AppUtils.getRequestUser(request)); + } + + /** + * @param user + * @return + */ + public JSONArray getNavPortal(ID user) { + ConfigEntry config = getLayoutOfNav(user); + if (config == null) { + return NAVS_DEFAULT; + } + + // 过滤 + JSONArray navs = (JSONArray) config.getJSON("config"); + for (Iterator iter = navs.iterator(); iter.hasNext(); ) { + JSONObject nav = (JSONObject) iter.next(); + JSONArray subNavs = nav.getJSONArray("sub"); + + if (subNavs != null && !subNavs.isEmpty()) { + for (Iterator subIter = subNavs.iterator(); subIter.hasNext(); ) { + JSONObject subNav = (JSONObject) subIter.next(); + if (isFilterNav(subNav, user)) { + subIter.remove(); + } + } + + // 无子级,移除主菜单 + if (subNavs.isEmpty()) { + iter.remove(); + } + } else if (isFilterNav(nav, user)) { + iter.remove(); + } + } + return navs; + } + + /** + * 是否需要过滤掉 + * + * @param nav + * @param user + * @return + */ + private boolean isFilterNav(JSONObject nav, ID user) { + String type = nav.getString("type"); + if ("ENTITY".equalsIgnoreCase(type)) { + String entity = nav.getString("value"); + if (NAV_PARENT.equals(entity)) { + return true; + } else if (NAV_FEEDS.equals(entity) || NAV_FILEMRG.equals(entity)) { + return false; + } else if (!MetadataHelper.containsEntity(entity)) { + LOG.warn("Unknow entity in nav : " + entity); + return true; + } + + Entity entityMeta = MetadataHelper.getEntity(entity); + return !Application.getSecurityManager().allowRead(user, entityMeta.getEntityCode()); + } + return false; + } + + /** + * 渲染导航菜單 + * + * @param item + * @param activeNav + * @return + */ + public String renderNavItem(JSONObject item, String activeNav) { + final boolean isUrlType = "URL".equals(item.getString("type")); + String navName = item.getString("value"); + String navUrl = item.getString("value"); + + boolean isOutUrl = isUrlType && navUrl.startsWith("http"); + if (isUrlType) { + navName = "nav_url-" + navName.hashCode(); + if (isOutUrl) { + navUrl = ServerListener.getContextPath() + "/commons/url-safe?url=" + CodecUtils.urlEncode(navUrl); + } else { + navUrl = ServerListener.getContextPath() + navUrl; + } + } else if (NAV_FEEDS.equals(navName)) { + navName = "nav_entity-Feeds"; + navUrl = ServerListener.getContextPath() + "/feeds/home"; + } else if (NAV_FILEMRG.equals(navName)) { + navName = "nav_entity-Attachment"; + navUrl = ServerListener.getContextPath() + "/files/home"; + } else { + navName = "nav_entity-" + navName; + navUrl = ServerListener.getContextPath() + "/app/" + navUrl + "/list"; + } + + String navIcon = StringUtils.defaultIfBlank(item.getString("icon"), "texture"); + String navText = item.getString("text"); + + JSONArray subNavs = null; + if (activeNav != null) { + subNavs = item.getJSONArray("sub"); + if (subNavs == null || subNavs.isEmpty()) { + subNavs = null; + } + } + + StringBuilder navHtml = new StringBuilder() + .append(String.format("
  • %s", + navName + (subNavs == null ? StringUtils.EMPTY : " parent"), + subNavs == null ? navUrl : "###", + isOutUrl ? "_blank" : "_self", + navIcon, + navText)); + if (subNavs != null) { + StringBuilder subHtml = new StringBuilder() + .append("
    • ") + .append(navText) + .append("
      • "); + + for (Object o : subNavs) { + JSONObject subNav = (JSONObject) o; + subHtml.append(renderNavItem(subNav, null)); + } + subHtml.append("
    "); + navHtml.append(subHtml); + } + navHtml.append("
  • "); + + if (activeNav != null) { + Document navBody = Jsoup.parseBodyFragment(navHtml.toString()); + for (Element nav : navBody.select("." + activeNav)) { + nav.addClass("active"); + if (activeNav.startsWith("nav_entity-")) { + Element navParent = nav.parent(); + if (navParent != null && navParent.hasClass("sub-menu-ul")) { + navParent.parent().parent().parent().parent().addClass("open active"); + } + } + } + return navBody.selectFirst("li").outerHtml(); + } + return navHtml.toString(); + } +} diff --git a/src/main/java/com/rebuild/server/configuration/portals/NavManager.java b/src/main/java/com/rebuild/server/configuration/portals/NavManager.java index 52b4013a1..0b87f8be1 100644 --- a/src/main/java/com/rebuild/server/configuration/portals/NavManager.java +++ b/src/main/java/com/rebuild/server/configuration/portals/NavManager.java @@ -7,26 +7,11 @@ See LICENSE and COMMERCIAL in the project root for license information. package com.rebuild.server.configuration.portals; -import cn.devezhao.commons.CodecUtils; -import cn.devezhao.persist4j.Entity; import cn.devezhao.persist4j.engine.ID; import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import com.rebuild.server.Application; -import com.rebuild.server.ServerListener; import com.rebuild.server.configuration.ConfigEntry; -import com.rebuild.server.metadata.MetadataHelper; -import com.rebuild.utils.AppUtils; -import com.rebuild.utils.JSONUtils; -import org.apache.commons.lang.StringUtils; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; /** @@ -43,9 +28,11 @@ public class NavManager extends BaseLayoutManager { public static final String NAV_FEEDS = "$FEEDS$"; // 文件 public static final String NAV_FILEMRG = "$FILEMRG$"; + // 项目看板 + public static final String NAV_KANBANMRG = "$KANBANMRG$"; public static final NavManager instance = new NavManager(); - private NavManager() { } + protected NavManager() { } /** * @param user @@ -79,160 +66,4 @@ public class NavManager extends BaseLayoutManager { } return array.toArray(new ID[0]); } - - // ---- - - /** - * 默认导航 - */ - private static final JSONArray NAVS_DEFAULT = JSONUtils.toJSONObjectArray( - new String[] { "icon", "text", "type", "value" }, - new Object[][] { - new Object[] { "chart-donut", "动态", "ENTITY", NAV_FEEDS }, - new Object[] { "folder", "文件", "ENTITY", NAV_FILEMRG } - }); - - /** - * @param request - * @return - */ - public JSONArray getNavForPortal(HttpServletRequest request) { - return getNavForPortal(AppUtils.getRequestUser(request)); - } - - /** - * @param user - * @return - */ - public JSONArray getNavForPortal(ID user) { - ConfigEntry config = getLayoutOfNav(user); - if (config == null) { - return NAVS_DEFAULT; - } - - // 过滤 - JSONArray navs = (JSONArray) config.getJSON("config"); - for (Iterator iter = navs.iterator(); iter.hasNext(); ) { - JSONObject nav = (JSONObject) iter.next(); - JSONArray subNavs = nav.getJSONArray("sub"); - - if (subNavs != null && !subNavs.isEmpty()) { - for (Iterator subIter = subNavs.iterator(); subIter.hasNext(); ) { - JSONObject subNav = (JSONObject) subIter.next(); - if (isFilterNav(subNav, user)) { - subIter.remove(); - } - } - - // 无子级,移除主菜单 - if (subNavs.isEmpty()) { - iter.remove(); - } - } else if (isFilterNav(nav, user)) { - iter.remove(); - } - } - return navs; - } - - /** - * 是否需要过滤掉 - * - * @param nav - * @param user - * @return - */ - private boolean isFilterNav(JSONObject nav, ID user) { - String type = nav.getString("type"); - if ("ENTITY".equalsIgnoreCase(type)) { - String entity = nav.getString("value"); - if (NAV_PARENT.equals(entity)) { - return true; - } else if (NAV_FEEDS.equals(entity) || NAV_FILEMRG.equals(entity)) { - return false; - } else if (!MetadataHelper.containsEntity(entity)) { - LOG.warn("Unknow entity in nav : " + entity); - return true; - } - - Entity entityMeta = MetadataHelper.getEntity(entity); - return !Application.getSecurityManager().allowRead(user, entityMeta.getEntityCode()); - } - return false; - } - - /** - * 渲染导航菜單 - * - * @param item - * @param activeNav - * @return - */ - public String renderNavItem(JSONObject item, String activeNav) { - final boolean isUrlType = "URL".equals(item.getString("type")); - String navName = item.getString("value"); - String navUrl = item.getString("value"); - - if (isUrlType) { - navName = "nav_url-" + navName.hashCode(); - navUrl = ServerListener.getContextPath() + "/commons/url-safe?url=" + CodecUtils.urlEncode(navUrl); - } else if (NAV_FEEDS.equals(navName)) { - navName = "nav_entity-Feeds"; - navUrl = ServerListener.getContextPath() + "/feeds/home"; - } else if (NAV_FILEMRG.equals(navName)) { - navName = "nav_entity-Attachment"; - navUrl = ServerListener.getContextPath() + "/files/home"; - } else { - navName = "nav_entity-" + navName; - navUrl = ServerListener.getContextPath() + "/app/" + navUrl + "/list"; - } - - String navIcon = StringUtils.defaultIfBlank(item.getString("icon"), "texture"); - String navText = item.getString("text"); - - JSONArray subNavs = null; - if (activeNav != null) { - subNavs = item.getJSONArray("sub"); - if (subNavs == null || subNavs.isEmpty()) { - subNavs = null; - } - } - - StringBuilder navHtml = new StringBuilder() - .append(String.format("
  • %s", - navName + (subNavs == null ? StringUtils.EMPTY : " parent"), - subNavs == null ? navUrl : "###", - isUrlType ? "_blank" : "_self", - navIcon, - navText)); - if (subNavs != null) { - StringBuilder subHtml = new StringBuilder() - .append("
    • ") - .append(navText) - .append("
      • "); - - for (Object o : subNavs) { - JSONObject subNav = (JSONObject) o; - subHtml.append(renderNavItem(subNav, null)); - } - subHtml.append("
    "); - navHtml.append(subHtml); - } - navHtml.append("
  • "); - - if (activeNav != null) { - Document navBody = Jsoup.parseBodyFragment(navHtml.toString()); - for (Element nav : navBody.select("." + activeNav)) { - nav.addClass("active"); - if (activeNav.startsWith("nav_entity-")) { - Element navParent = nav.parent(); - if (navParent != null && navParent.hasClass("sub-menu-ul")) { - navParent.parent().parent().parent().parent().addClass("open active"); - } - } - } - return navBody.selectFirst("li").outerHtml(); - } - return navHtml.toString(); - } } diff --git a/src/main/webapp/_include/NavLeft.jsp b/src/main/webapp/_include/NavLeft.jsp index 2e2419b8d..ab6e753fb 100644 --- a/src/main/webapp/_include/NavLeft.jsp +++ b/src/main/webapp/_include/NavLeft.jsp @@ -1,12 +1,12 @@ <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ page import="com.alibaba.fastjson.JSONObject"%> <%@ page import="com.alibaba.fastjson.JSONArray"%> -<%@ page import="com.rebuild.server.configuration.portals.NavManager"%> <%@ page import="com.rebuild.utils.AppUtils" %> <%@ page import="com.rebuild.server.service.bizz.privileges.ZeroEntry" %> +<%@ page import="com.rebuild.server.configuration.portals.NavBuilder" %> <% final String activeNav = request.getParameter("activeNav"); -final JSONArray navArray = NavManager.instance.getNavForPortal(request); +final JSONArray navArray = NavBuilder.instance.getNavPortal(request); %>
    <% if (AppUtils.allow(request, ZeroEntry.AllowCustomNav)) { %>
    - +
    <% } %> diff --git a/src/main/webapp/assets/js/nav-settings.jsx b/src/main/webapp/assets/js/nav-settings.jsx index 54737a6f6..21c7d03b0 100644 --- a/src/main/webapp/assets/js/nav-settings.jsx +++ b/src/main/webapp/assets/js/nav-settings.jsx @@ -45,7 +45,7 @@ $(document).ready(function () { value = $val('.J_menuUrl') if (!value) { RbHighbar.create('请输入 URL'); return - } else if (!!value && !$regex.isUrl(value)) { + } else if (!($regex.isUrl(value) || $regex.isUrl(`https://getrebuild.com${value}`))) { RbHighbar.create('请输入有效的 URL') return } diff --git a/src/main/webapp/commons/nav-settings.jsp b/src/main/webapp/commons/nav-settings.jsp index 6cce6d142..f830c416d 100644 --- a/src/main/webapp/commons/nav-settings.jsp +++ b/src/main/webapp/commons/nav-settings.jsp @@ -63,7 +63,7 @@ +
    支持外部地址或相对地址
    diff --git a/src/main/webapp/commons/search-icon.jsp b/src/main/webapp/commons/search-icon.jsp index 3ba2e78b9..5e207da8e 100644 --- a/src/main/webapp/commons/search-icon.jsp +++ b/src/main/webapp/commons/search-icon.jsp @@ -19,14 +19,14 @@ diff --git a/src/test/java/com/rebuild/server/configuration/portals/NavManagerTest.java b/src/test/java/com/rebuild/server/configuration/portals/NavManagerTest.java index e3cd3ed9a..0d9b1945d 100644 --- a/src/test/java/com/rebuild/server/configuration/portals/NavManagerTest.java +++ b/src/test/java/com/rebuild/server/configuration/portals/NavManagerTest.java @@ -46,18 +46,18 @@ public class NavManagerTest extends TestSupport { } @Test - public void testPortalNav() throws Exception { + public void testNavBuilder() throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders .get("/rebuild") .sessionAttr(WebUtils.CURRENT_USER, UserService.ADMIN_USER); HttpServletRequest request = builder.buildRequest(new MockServletContext()); - JSON navForPortal = NavManager.instance.getNavForPortal(request); + JSONArray navForPortal = NavBuilder.instance.getNavPortal(request); System.out.println("testPortalNav .......... \n" + navForPortal.toJSONString()); - if (!((JSONArray) navForPortal).isEmpty()) { - JSONObject firstNav = (JSONObject) ((JSONArray) navForPortal).get(0); - String navHtml = NavManager.instance.renderNavItem(firstNav, "home"); + if (!navForPortal.isEmpty()) { + JSONObject firstNav = (JSONObject) navForPortal.get(0); + String navHtml = NavBuilder.instance.renderNavItem(firstNav, "home"); System.out.println(navHtml); } }