diff --git a/bookMarkService/business/bookmark/src/main/java/com/fanxb/bookmark/business/bookmark/constant/FileConstant.java b/bookMarkService/business/bookmark/src/main/java/com/fanxb/bookmark/business/bookmark/constant/FileConstant.java new file mode 100644 index 0000000..0e9f78a --- /dev/null +++ b/bookMarkService/business/bookmark/src/main/java/com/fanxb/bookmark/business/bookmark/constant/FileConstant.java @@ -0,0 +1,16 @@ +package com.fanxb.bookmark.business.bookmark.constant; + +import java.nio.file.Paths; + +/** + * TODO + * + * @author fanxb + */ +public class FileConstant { + + /** + * 网站icon存储路径 + */ + public static final String FAVICON_PATH = Paths.get("files", "public", "favicon").toString(); +} diff --git a/bookMarkService/business/bookmark/src/main/java/com/fanxb/bookmark/business/bookmark/dao/HostIconDao.java b/bookMarkService/business/bookmark/src/main/java/com/fanxb/bookmark/business/bookmark/dao/HostIconDao.java new file mode 100644 index 0000000..915100b --- /dev/null +++ b/bookMarkService/business/bookmark/src/main/java/com/fanxb/bookmark/business/bookmark/dao/HostIconDao.java @@ -0,0 +1,33 @@ +package com.fanxb.bookmark.business.bookmark.dao; + +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +/** + * @author fanxb + */ +@Mapper +public interface HostIconDao { + + /** + * 插入一条数据 + * + * @param host host + * @param iconPath path + * @author fanxb + */ + @Insert("insert into host_icon(host,iconPath) value(#{host},#{iconPath})") + void insert(@Param("host") String host, @Param("iconPath") String iconPath); + + /** + * 根据host获取iconPath + * + * @param host host + * @return {@link String} + * @author fanxb + */ + @Select("select iconPath from host_icon where host=#{host}") + String selectByHost(String host); +} diff --git a/bookMarkService/business/bookmark/src/main/java/com/fanxb/bookmark/business/bookmark/service/impl/BookmarkServiceImpl.java b/bookMarkService/business/bookmark/src/main/java/com/fanxb/bookmark/business/bookmark/service/impl/BookmarkServiceImpl.java index d5651ee..c3f82e9 100644 --- a/bookMarkService/business/bookmark/src/main/java/com/fanxb/bookmark/business/bookmark/service/impl/BookmarkServiceImpl.java +++ b/bookMarkService/business/bookmark/src/main/java/com/fanxb/bookmark/business/bookmark/service/impl/BookmarkServiceImpl.java @@ -1,21 +1,29 @@ package com.fanxb.bookmark.business.bookmark.service.impl; +import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.fanxb.bookmark.business.api.UserApi; +import com.fanxb.bookmark.business.bookmark.constant.FileConstant; import com.fanxb.bookmark.business.bookmark.dao.BookmarkDao; +import com.fanxb.bookmark.business.bookmark.dao.HostIconDao; import com.fanxb.bookmark.business.bookmark.entity.BookmarkEs; import com.fanxb.bookmark.business.bookmark.entity.MoveNodeBody; import com.fanxb.bookmark.business.bookmark.entity.redis.BookmarkDeleteMessage; import com.fanxb.bookmark.business.bookmark.entity.redis.VisitNumPlus; import com.fanxb.bookmark.business.bookmark.service.BookmarkService; import com.fanxb.bookmark.business.bookmark.service.PinYinService; +import com.fanxb.bookmark.common.constant.CommonConstant; import com.fanxb.bookmark.common.constant.EsConstant; import com.fanxb.bookmark.common.constant.RedisConstant; import com.fanxb.bookmark.common.entity.po.Bookmark; +import com.fanxb.bookmark.common.exception.CustomException; import com.fanxb.bookmark.common.util.*; import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; +import okhttp3.Response; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.builder.SearchSourceBuilder; @@ -28,9 +36,15 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.File; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; @@ -51,13 +65,15 @@ public class BookmarkServiceImpl implements BookmarkService { private final PinYinService pinYinService; private final UserApi userApi; private final EsUtil esUtil; + private final HostIconDao hostIconDao; @Autowired - public BookmarkServiceImpl(BookmarkDao bookmarkDao, PinYinService pinYinService, UserApi userApi, EsUtil esUtil) { + public BookmarkServiceImpl(BookmarkDao bookmarkDao, PinYinService pinYinService, UserApi userApi, EsUtil esUtil, HostIconDao hostIconDao) { this.bookmarkDao = bookmarkDao; this.pinYinService = pinYinService; this.userApi = userApi; this.esUtil = esUtil; + this.hostIconDao = hostIconDao; } @Override @@ -201,7 +217,7 @@ public class BookmarkServiceImpl implements BookmarkService { bookmark.setUserId(userId); bookmark.setCreateTime(System.currentTimeMillis()); bookmark.setAddTime(bookmark.getCreateTime()); - bookmark.setIcon(getIconBase64(bookmark.getUrl())); + bookmark.setIcon(getIconPath(bookmark.getUrl())); //文件夹和书签都建立搜索key pinYinService.changeBookmark(bookmark); bookmarkDao.insertOne(bookmark); @@ -215,7 +231,7 @@ public class BookmarkServiceImpl implements BookmarkService { bookmark.setUserId(userId); if (bookmark.getType() == 0) { pinYinService.changeBookmark(bookmark); - bookmark.setIcon(getIconBase64(bookmark.getUrl())); + bookmark.setIcon(getIconPath(bookmark.getUrl())); } bookmarkDao.editBookmark(bookmark); userApi.versionPlus(userId); @@ -274,7 +290,7 @@ public class BookmarkServiceImpl implements BookmarkService { while ((deal = bookmarkDao.selectUserNoIcon(userId, start, size)).size() > 0) { start += size; deal.forEach(item -> { - String icon = getIconBase64(item.getUrl()); + String icon = getIconPath(item.getUrl()); if (StrUtil.isNotEmpty(icon)) { bookmarkDao.updateIcon(item.getBookmarkId(), icon); } @@ -305,21 +321,56 @@ public class BookmarkServiceImpl implements BookmarkService { return resPath; } - private String getIconBase64(String url) { + /** + * 获取icon + * + * @param url url + * @return {@link String} + * @author fanxb + */ + private String getIconPath(String url) { if (StrUtil.isEmpty(url)) { return ""; } + String host; try { URL urlObj = new URL(url); - byte[] data = HttpUtil.download(urlIconAddress + "/icon?url=" + urlObj.getHost() + "&size=8..16..64", false); - String base64 = new String(Base64.getEncoder().encode(data)); - if (StrUtil.isNotEmpty(base64)) { - return "data:image/png;base64," + base64; - } else { - log.warn("url无法获取icon:{}", url); - } - } catch (MalformedURLException e) { + host = urlObj.getHost(); + } catch (Exception e) { log.warn("url无法解析出domain:{}", url); + return ""; + } + String iconPath = hostIconDao.selectByHost(host); + if (iconPath != null) { + return iconPath; + } + iconPath = saveFile(host, urlIconAddress + "/icon?url=" + host + "&size=16..64..256"); + if (StrUtil.isNotEmpty(iconPath)) { + hostIconDao.insert(host, iconPath); + } + return iconPath; + } + + private String saveFile(String host, String url) { + try { + try (Response res = HttpUtil.getClient(false).newCall(new Request.Builder().url(url) + .header("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36 Edg/100.0.1185.36") + .get().build()).execute()) { + assert res.body() != null; + if (!HttpUtil.checkIsOk(res.code())) { + throw new CustomException("请求错误:" + res.code()); + } + byte[] data = res.body().byteStream().readAllBytes(); + if (data.length > 0) { + String iconUrl = res.request().url().toString(); + String fileName = URLEncoder.encode(host, StandardCharsets.UTF_8) + iconUrl.substring(iconUrl.lastIndexOf(".")); + String filePath = Paths.get(FileConstant.FAVICON_PATH, host.substring(0, 2), fileName).toString(); + FileUtil.writeBytes(data, Paths.get(CommonConstant.fileSavePath, filePath).toString()); + return File.separator + filePath; + } else { + log.info("未获取到icon:{}", url); + } + } } catch (Exception e) { log.error("url获取icon故障:{}", url, e); } diff --git a/bookMarkService/business/user/src/main/java/com/fanxb/bookmark/business/user/constant/FileConstant.java b/bookMarkService/business/user/src/main/java/com/fanxb/bookmark/business/user/constant/FileConstant.java index eccc028..9f0ac6f 100644 --- a/bookMarkService/business/user/src/main/java/com/fanxb/bookmark/business/user/constant/FileConstant.java +++ b/bookMarkService/business/user/src/main/java/com/fanxb/bookmark/business/user/constant/FileConstant.java @@ -1,5 +1,6 @@ package com.fanxb.bookmark.business.user.constant; +import com.fanxb.bookmark.common.constant.CommonConstant; import org.springframework.stereotype.Component; import java.nio.file.Paths; @@ -17,6 +18,6 @@ public class FileConstant { /** * 用户头像目录 */ - public static String iconPath = Paths.get("files", "public", "icon").toString(); + public static String iconPath = Paths.get(CommonConstant.fileSavePath, "files", "public", "icon").toString(); } diff --git a/bookMarkService/common/src/main/java/com/fanxb/bookmark/common/util/HttpUtil.java b/bookMarkService/common/src/main/java/com/fanxb/bookmark/common/util/HttpUtil.java index ffc946f..17665d4 100644 --- a/bookMarkService/common/src/main/java/com/fanxb/bookmark/common/util/HttpUtil.java +++ b/bookMarkService/common/src/main/java/com/fanxb/bookmark/common/util/HttpUtil.java @@ -12,8 +12,6 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; -import java.io.InputStream; -import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Proxy; import java.util.Map; @@ -51,42 +49,33 @@ public class HttpUtil { /** * 无代理环境 */ - private static final OkHttpClient CLIENT = new OkHttpClient.Builder().connectTimeout(2, TimeUnit.SECONDS) + private static final OkHttpClient CLIENT = new OkHttpClient.Builder().connectTimeout(1, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .build(); + + /** + * 获取客户端 + * + * @param proxy 是否代理 + * @return {@link OkHttpClient} + * @author fanxb + */ + public static OkHttpClient getClient(boolean proxy) { + return proxy ? PROXY_CLIENT : CLIENT; + } + public static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); @PostConstruct public void init() { - OkHttpClient.Builder builder = new OkHttpClient.Builder(); + OkHttpClient.Builder builder = new OkHttpClient.Builder().connectTimeout(1, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS); log.info("代理配置,ip:{},port:{}", proxyIp, proxyPort); if (StrUtil.isNotBlank(proxyIp) && StrUtil.isNotBlank(proxyPort)) { builder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyIp, Integer.parseInt(proxyPort)))); proxyExist = true; - } - PROXY_CLIENT = builder.connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(60, TimeUnit.SECONDS) - .build(); - } - - /*** - * 下载文件 - * @author fanxb - * @param url 下载链接 - * @param proxy 是否使用代理 - * @return java.io.InputStream - * @date 2021/3/12 - **/ - public static byte[] download(String url, boolean proxy) { - try (Response res = (proxy ? PROXY_CLIENT : CLIENT).newCall(new Request.Builder().url(url).build()).execute()) { - assert res.body() != null; - if (checkIsOk(res.code())) { - return res.body().byteStream().readAllBytes(); - } else { - throw new CustomException("下载出现问题:" + res.body().string()); - } - } catch (Exception e) { - throw new CustomException(e); + PROXY_CLIENT = builder.build(); + } else { + PROXY_CLIENT = CLIENT; } } @@ -270,6 +259,8 @@ public class HttpUtil { } return ipAddress; } + + } diff --git a/bookMarkService/files/public/icon/1.1614853232994.html b/bookMarkService/files/public/icon/1.1614853232994.html deleted file mode 100644 index 909688a..0000000 --- a/bookMarkService/files/public/icon/1.1614853232994.html +++ /dev/null @@ -1,17 +0,0 @@ - - - -Bookmarks -

Bookmarks Menu

- -

-

1 -
2 -

f1

-

-

f11 -
f12 -

-

diff --git a/bookMarkService/files/public/icon/1.1614853330412.html b/bookMarkService/files/public/icon/1.1614853330412.html deleted file mode 100644 index 909688a..0000000 --- a/bookMarkService/files/public/icon/1.1614853330412.html +++ /dev/null @@ -1,17 +0,0 @@ - - - -Bookmarks -

Bookmarks Menu

- -

-

1 -
2 -

f1

-

-

f11 -
f12 -

-

diff --git a/bookMarkService/web/src/main/resources/db/migration/V21__新增域名path表.sql b/bookMarkService/web/src/main/resources/db/migration/V21__新增域名path表.sql new file mode 100644 index 0000000..3f3fdd0 --- /dev/null +++ b/bookMarkService/web/src/main/resources/db/migration/V21__新增域名path表.sql @@ -0,0 +1,10 @@ +CREATE TABLE bookmark.host_icon ( + id INT UNSIGNED auto_increment NOT NULL, + host varchar(300) NOT NULL COMMENT 'host', + iconPath varchar(330) NOT NULL, + CONSTRAINT host_icon_pk PRIMARY KEY (id) +) +ENGINE=InnoDB +DEFAULT CHARSET=utf8mb4 +COLLATE=utf8mb4_0900_ai_ci; +CREATE INDEX host_icon_host_IDX USING BTREE ON bookmark.host_icon (host(20)); diff --git a/bookmark_front/.gitignore b/bookmark_front/.gitignore index c6c57be..c8016d8 100644 --- a/bookmark_front/.gitignore +++ b/bookmark_front/.gitignore @@ -3,6 +3,7 @@ node_modules /dist package-lock.json yarn.lock +public/files # local env files .env.local diff --git a/bookmark_front/public/static/bookmarkBrowserPlugin.7z b/bookmark_front/public/static/bookmarkBrowserPlugin.7z new file mode 100644 index 0000000..b52ae61 Binary files /dev/null and b/bookmark_front/public/static/bookmarkBrowserPlugin.7z differ diff --git a/bookmark_front/src/App.vue b/bookmark_front/src/App.vue index ddbbc49..be76731 100644 --- a/bookmark_front/src/App.vue +++ b/bookmark_front/src/App.vue @@ -6,7 +6,7 @@ diff --git a/bookmark_front/src/router/index.js b/bookmark_front/src/router/index.js index 85d3876..3487034 100644 --- a/bookmark_front/src/router/index.js +++ b/bookmark_front/src/router/index.js @@ -1,65 +1,69 @@ import Vue from "vue"; import VueRouter from "vue-router"; import * as vuex from "../store/index.js"; -import { GLOBAL_CONFIG, SUPPORT_NO_LOGIN, TOKEN } from "@/store/modules/globalConfig"; +import { GLOBAL_CONFIG, SUPPORT_NO_LOGIN, TOKEN, setToken } from "@/store/modules/globalConfig"; import { checkJwtValid } from "@/util/UserUtil"; Vue.use(VueRouter); const routes = [ - { path: "/", component: () => import("@/views/home/index") }, - { - path: "/manage", - component: () => import("@/views/manage/index"), - children: [ - { path: "", redirect: "/manage/bookmarkTree" }, - { path: "bookmarkTree", component: () => import("@/views/manage/bookmarkTree/index") }, - { path: "personSpace/userInfo", component: () => import("@/views/manage/personSpace/index") }, - ] - }, - { - path: "/public", - component: () => import("@/views/public/index"), - children: [ - { path: "login", component: () => import("@/views/public/login/index") }, - { path: "register", component: () => import("@/views/public/register/index") }, - { path: "resetPassword", component: () => import("@/views/public/passwordReset/index") }, - { path: "oauth/github", component: () => import("@/views/public/oauth/github/index") }, - { path: "about", component: () => import("@/views/public/about/index") }, - { path: "404", component: () => import("@/views/public/notFound/index") }, - ] - }, - { path: "*", redirect: "/public/404" } + { path: "/", component: () => import("@/views/home/index") }, + { path: "/noHead/addBookmark", component: () => import("@/views/noHead/addBookmark/index") }, + { + path: "/manage", + component: () => import("@/views/manage/index"), + children: [ + { path: "", redirect: "/manage/bookmarkTree" }, + { path: "bookmarkTree", component: () => import("@/views/manage/bookmarkTree/index") }, + { path: "personSpace/userInfo", component: () => import("@/views/manage/personSpace/index") }, + { path: "sso/auth", component: () => import("@/views/manage/sso/auth/index") } + ] + }, + { + path: "/public", + component: () => import("@/views/public/index"), + children: [ + { path: "login", component: () => import("@/views/public/login/index") }, + { path: "register", component: () => import("@/views/public/register/index") }, + { path: "resetPassword", component: () => import("@/views/public/passwordReset/index") }, + { path: "oauth/github", component: () => import("@/views/public/oauth/github/index") }, + { path: "about", component: () => import("@/views/public/about/index") }, + { path: "404", component: () => import("@/views/public/notFound/index") } + ] + }, + { path: "*", redirect: "/public/404" } ]; const router = new VueRouter({ - mode: "history", - routes + mode: "history", + routes }); /** * 在此进行登录信息判断,以及重定向到登录页面 */ router.beforeEach(async (to, from, next) => { - //进入主页面/管理页面时,确认已经进行初始化操作 - if (to.path === '/' || to.path.startsWith("/manage")) { - await vuex.loginInit(); - } - let supportNoLogin = to.path === '/' || to.path.startsWith("/public"); - vuex.default.commit(GLOBAL_CONFIG + "/" + SUPPORT_NO_LOGIN, supportNoLogin); - if (!supportNoLogin && !checkJwtValid(vuex.default.state[GLOBAL_CONFIG][TOKEN])) { - //如不支持未登录进入,切jwt已过期,直接跳转到登录页面,并清理缓存 - await vuex.default.dispatch("treeData/clear"); - await vuex.default.dispatch("globalConfig/clear"); - next({ - path: "/public/login?to=" + btoa(location.href), - replace: true - }); - } else { - next(); - } -}) - - + if (to.query.token && checkJwtValid(to.query.token)) { + console.log("获取到页面token", to.query.token); + await vuex.default.dispatch(GLOBAL_CONFIG + "/" + setToken, to.query.token); + } + //进入除/public以外的路由,确认已经进行初始化操作 + if (!to.path.startsWith("/public")) { + await vuex.loginInit(); + } + let supportNoLogin = to.path === "/" || to.path.startsWith("/public"); + vuex.default.commit(GLOBAL_CONFIG + "/" + SUPPORT_NO_LOGIN, supportNoLogin); + if (!supportNoLogin && !checkJwtValid(vuex.default.state[GLOBAL_CONFIG][TOKEN])) { + //如不支持未登录进入,切jwt已过期,直接跳转到登录页面,并清理缓存 + await vuex.default.dispatch("treeData/clear"); + await vuex.default.dispatch("globalConfig/clear"); + next({ + path: "/public/login?to=" + btoa(location.href), + replace: true + }); + } else { + next(); + } +}); export default router; diff --git a/bookmark_front/src/store/modules/globalConfig.js b/bookmark_front/src/store/modules/globalConfig.js index 27b2d23..b949ce4 100644 --- a/bookmark_front/src/store/modules/globalConfig.js +++ b/bookmark_front/src/store/modules/globalConfig.js @@ -11,95 +11,100 @@ export const IS_PHONE = "isPhone"; export const noLoginInit = "noLoginInit"; export const loginInit = "loginInit"; +/** + * 登出清除数据 + */ export const clear = "clear"; +/** + * 设置token + */ +export const setToken = "setToken"; /** * 存储全局配置 */ const state = { - /** - * 用户信息 - */ - [USER_INFO]: null, - /** - * token,null说明未获取登录凭证 - */ - [TOKEN]: null, - /** - * 是否已经初始化完成,避免多次重复初始化 - */ - [IS_INIT]: false, - /** - * 是否移动端 - */ - [IS_PHONE]: /Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent), - /** - * 是否支持未登录进入页面 - */ - [SUPPORT_NO_LOGIN]: false, - /** - * 服务端全局配置 - */ - [SERVER_CONFIG]: {} + /** + * 用户信息 + */ + [USER_INFO]: null, + /** + * token,null说明未获取登录凭证 + */ + [TOKEN]: null, + /** + * 是否已经初始化完成,避免多次重复初始化 + */ + [IS_INIT]: false, + /** + * 是否移动端 + */ + [IS_PHONE]: /Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent), + /** + * 是否支持未登录进入页面 + */ + [SUPPORT_NO_LOGIN]: false, + /** + * 服务端全局配置 + */ + [SERVER_CONFIG]: {} }; const getters = {}; const actions = { - //未登录需要进行的初始化 - async [noLoginInit] ({ commit }) { - commit(SERVER_CONFIG, await HttpUtil.get("/common/config/global")); - let token = await localforage.getItem(TOKEN); - if (token) { - commit(TOKEN, token); - window.jwtToken = token; - } - }, - //登陆后的,初始化数据 - async [loginInit] (context) { - if (context.state.isInit) { - return; - } - let userInfo = await HttpUtil.get("/user/currentUserInfo"); - context.commit(USER_INFO, userInfo); - context.commit(IS_INIT, true); - }, - async setToken ({ commit }, token) { - await localforage.setItem(TOKEN, token); - window.jwtToken = token; - commit(TOKEN, token); - }, - //登出清除数据 - async [clear] (context) { - await localforage.removeItem(TOKEN); - context.commit(USER_INFO, null); - context.commit(TOKEN, null); - context.commit(IS_INIT, false); - }, + //未登录需要进行的初始化 + async [noLoginInit]({ commit }) { + commit(SERVER_CONFIG, await HttpUtil.get("/common/config/global")); + let token = await localforage.getItem(TOKEN); + if (token) { + commit(TOKEN, token); + window.jwtToken = token; + } + }, + //登陆后的,初始化数据 + async [loginInit](context) { + if (context.state.isInit) { + return; + } + let userInfo = await HttpUtil.get("/user/currentUserInfo"); + context.commit(USER_INFO, userInfo); + context.commit(IS_INIT, true); + }, + async [setToken]({ commit }, token) { + await localforage.setItem(TOKEN, token); + window.jwtToken = token; + commit(TOKEN, token); + }, + async [clear](context) { + await localforage.removeItem(TOKEN); + context.commit(USER_INFO, null); + context.commit(TOKEN, null); + context.commit(IS_INIT, false); + } }; const mutations = { - [USER_INFO] (state, userInfo) { - state[USER_INFO] = userInfo; - }, - [TOKEN] (state, token) { - state[TOKEN] = token; - }, - [IS_INIT] (state, isInit) { - state[IS_INIT] = isInit; - }, - [SERVER_CONFIG] (state, serverConfig) { - state[SERVER_CONFIG] = serverConfig; - }, - [SUPPORT_NO_LOGIN] (state, val) { - state[SUPPORT_NO_LOGIN] = val; - } + [USER_INFO](state, userInfo) { + state[USER_INFO] = userInfo; + }, + [TOKEN](state, token) { + state[TOKEN] = token; + }, + [IS_INIT](state, isInit) { + state[IS_INIT] = isInit; + }, + [SERVER_CONFIG](state, serverConfig) { + state[SERVER_CONFIG] = serverConfig; + }, + [SUPPORT_NO_LOGIN](state, val) { + state[SUPPORT_NO_LOGIN] = val; + } }; - export const store = { - namespaced: true, - state, - getters, - actions, - mutations + namespaced: true, + state, + getters, + actions, + mutations }; diff --git a/bookmark_front/src/store/modules/treeData.js b/bookmark_front/src/store/modules/treeData.js index 5f91116..9ee7947 100644 --- a/bookmark_front/src/store/modules/treeData.js +++ b/bookmark_front/src/store/modules/treeData.js @@ -30,6 +30,10 @@ export const clear = "clear"; * 删除书签数据 */ export const deleteData = "deleteData"; +/** + * 新增节点 + */ +export const addNode = "addNode"; /** * 版本检查定时调度 @@ -44,321 +48,316 @@ let toastShow = false; * 书签树相关配置 */ const state = { - //全部书签数据 - [TOTAL_TREE_DATA]: {}, - //版本 - [VERSION]: null, - //是否已经初始化书签数据 - [IS_INIT]: false, - // 是否正在加载数据 - [IS_INITING]: false, - [SHOW_REFRESH_TOAST]: false, - [HOME_PIN_LIST]: [], - [HOME_PIN_BOOKMARK_ID_MAP]: {} + //全部书签数据 + [TOTAL_TREE_DATA]: {}, + //版本 + [VERSION]: null, + //是否已经初始化书签数据 + [IS_INIT]: false, + // 是否正在加载数据 + [IS_INITING]: false, + [SHOW_REFRESH_TOAST]: false, + [HOME_PIN_LIST]: [], + [HOME_PIN_BOOKMARK_ID_MAP]: {} }; const getters = { - [getById]: state => id => { - let arr = Object.values(state[TOTAL_TREE_DATA]); - for (let i in arr) { - for (let j in arr[i]) { - if (arr[i][j].bookmarkId === id) { - return arr[i][j]; - } - } - } - return null; - } + [getById]: state => id => { + let arr = Object.values(state[TOTAL_TREE_DATA]); + for (let i in arr) { + for (let j in arr[i]) { + if (arr[i][j].bookmarkId === id) { + return arr[i][j]; + } + } + } + return null; + } }; const actions = { - async [noLoginInit] () { - - }, - async [loginInit] (context) { - if (context.state.isInit || context.state.isIniting) { - return; - } - await context.dispatch(refreshHomePinList); - context.commit(IS_INITING, true); - context.commit(TOTAL_TREE_DATA, await localforage.getItem(TOTAL_TREE_DATA)); - context.commit(VERSION, await localforage.getItem(VERSION)); - await treeDataCheck(context, true); - context.commit(IS_INIT, true); - context.commit(IS_INITING, false); - timer = setInterval(() => treeDataCheck(context, false), CHECK_INTERVAL); - }, - /** - * 确保数据加载完毕 - */ - ensureDataOk (context) { - return new Promise((resolve, reject) => { - let timer = setInterval(() => { - try { - if (context.state[IS_INIT] && context.state[IS_INITING] == false) { - clearInterval(timer); - resolve(); - } - } catch (err) { - reject(err); - } - }, 50); - }); - }, - //刷新缓存数据 - async [refresh] (context) { - let treeData = await HttpUtil.get("/bookmark/currentUser"); - if (!treeData[""]) { - treeData[""] = []; - } - Object.values(treeData).forEach(item => - item.forEach(item1 => { - item1.isLeaf = item1.type === 0; - item1.class = "treeNodeItem"; - item1.scopedSlots = { title: "nodeTitle" }; - }) - ); - let version = await HttpUtil.get("/user/version"); - await context.dispatch("updateVersion", version); - await context.dispatch(refreshHomePinList); - context.commit(TOTAL_TREE_DATA, treeData); - await localforage.setItem(TOTAL_TREE_DATA, treeData); - }, - //清除缓存数据 - async [clear] (context) { - context.commit(TOTAL_TREE_DATA, null); - context.commit(VERSION, null); - context.commit(SHOW_REFRESH_TOAST, false); - context.commit(IS_INIT, false); - context.commit(IS_INITING, false); - context.commit(HOME_PIN_LIST, []); - if (timer != null) { - clearInterval(timer); - } - await localforage.removeItem(TOTAL_TREE_DATA); - await localforage.removeItem(VERSION); - }, - /** - * 移动节点 - */ - async moveNode (context, info) { - let data = context.state[TOTAL_TREE_DATA]; - const target = info.node.dataRef; - const current = info.dragNode.dataRef; - //从原来位置中删除当前节点 - let currentList = data[current.path]; - currentList.splice( - currentList.findIndex(item => item.bookmarkId === current.bookmarkId), - 1 - ); - //请求体 - const body = { - bookmarkId: current.bookmarkId, - sourcePath: current.path, - targetPath: "", - //-1 表示排在最后 - sort: -1 - }; - if (info.dropToGap) { - body.targetPath = target.path; - //移动到目标节点的上面或者下面 - let targetList = data[target.path]; - //目标节点index - let index = targetList.indexOf(target); - //移动节点相对于目标节点位置的增量 - let addIndex = info.dropPosition > index ? 1 : 0; - body.sort = target.sort + addIndex; - targetList.splice(index + addIndex, 0, current); - for (let i = index + 1; i < targetList.length; i++) { - targetList[i].sort += 1; - } - } else { - //移动到一个文件夹下面 - body.targetPath = target.path + "." + target.bookmarkId; - let targetList = data[body.targetPath]; - if (!targetList) { - targetList = []; - data[body.targetPath] = targetList; - } - body.sort = targetList.length > 0 ? targetList[targetList.length - 1].sort + 1 : 1; - targetList.push(current); - } - //更新节点的path和对应子节点path - current.path = body.targetPath; - current.sort = body.sort; - //如果为文件夹还要更新所有子书签的path - if (body.sourcePath !== body.targetPath) { - let keys = Object.keys(data); - //旧路径 - let oldPath = body.sourcePath + "." + current.bookmarkId; - //新路径 - let newPath = body.targetPath + "." + current.bookmarkId; - keys.forEach(item => { - if (!item.startsWith(oldPath)) { - return; - } - let newPathStr = item.replace(oldPath, newPath); - let list = data[item]; - delete data[item]; - data[newPathStr] = list; - list.forEach(item1 => (item1.path = newPathStr)); - }); - } - context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]); - await context.dispatch("updateVersion", null); - await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]); - return body; - }, - async [refreshHomePinList] ({ commit }) { - let list = await HttpUtil.get("/home/pin"); - commit(HOME_PIN_LIST, list); - let map = {}; - list.filter(item => item.id).forEach(item => map[item.bookmarkId] = true); - commit(HOME_PIN_BOOKMARK_ID_MAP, map); - - }, - /** - * 更新版本数据 - */ - async updateVersion ({ commit, state }, version) { - commit(VERSION, version == null ? state[VERSION] + 1 : version); - await localforage.setItem(VERSION, state[VERSION]); - }, - /** - * 新增书签、文件夹 - */ - async addNode (context, { sourceNode, targetNode }) { - if (sourceNode === null) { - if (context.state[TOTAL_TREE_DATA][""] === undefined) { - context.state[TOTAL_TREE_DATA][""] = []; - } - context.state[TOTAL_TREE_DATA][""].push(targetNode); - } else { - if (sourceNode.children === undefined) { - sourceNode.children = []; - } - sourceNode.children.push(targetNode); - } - if (targetNode.type === 0) { - context.state[TOTAL_TREE_DATA][targetNode.path + "." + targetNode.bookmarkId] = []; - } - targetNode.isLeaf = targetNode.type === 0; - targetNode.class = "treeNodeItem"; - targetNode.scopedSlots = { title: "nodeTitle" }; - context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]); - await context.dispatch("updateVersion", null); - await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]); - }, - /** - * 删除节点数据 - */ - async [deleteData] (context, { pathList, bookmarkIdList }) { - //待删除的书签 - let bookmarkIdSet = new Set(); - bookmarkIdList.forEach(item => bookmarkIdSet.add(item)); - //删除子节点 - pathList.forEach(item => { - delete state[TOTAL_TREE_DATA][item]; - Object.keys(context.state[TOTAL_TREE_DATA]) - .filter(key => key.startsWith(item + ".")) - .forEach(key => delete state[TOTAL_TREE_DATA][key]); - bookmarkIdSet.add(parseInt(item.split(".").reverse())); - }); - //删除直接选中的节点 - Object.keys(context.state[TOTAL_TREE_DATA]).forEach(item => { - let list = context.state[TOTAL_TREE_DATA][item]; - for (let i = list.length - 1; i >= 0; i--) { - if (bookmarkIdSet.has(list[i].bookmarkId)) { - list.splice(i, 1); - } - } - }); - context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]); - await context.dispatch("updateVersion", null); - await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]); - }, - /** - * 编辑书签节点 - */ - async editNode ({ dispatch, state, commit }, { node, newName, newUrl, newIcon }) { - node.name = newName; - node.url = newUrl; - node.icon = newIcon; - commit(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]); - await dispatch("updateVersion", null); - await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]); - } + async [noLoginInit]() {}, + async [loginInit](context) { + if (context.state.isInit || context.state.isIniting) { + return; + } + await context.dispatch(refreshHomePinList); + context.commit(IS_INITING, true); + context.commit(TOTAL_TREE_DATA, await localforage.getItem(TOTAL_TREE_DATA)); + context.commit(VERSION, await localforage.getItem(VERSION)); + await treeDataCheck(context, true); + context.commit(IS_INIT, true); + context.commit(IS_INITING, false); + timer = setInterval(() => treeDataCheck(context, false), CHECK_INTERVAL); + }, + /** + * 确保数据加载完毕 + */ + ensureDataOk(context) { + return new Promise((resolve, reject) => { + let timer = setInterval(() => { + try { + if (context.state[IS_INIT] && context.state[IS_INITING] == false) { + clearInterval(timer); + resolve(); + } + } catch (err) { + reject(err); + } + }, 50); + }); + }, + //刷新缓存数据 + async [refresh](context) { + let treeData = await HttpUtil.get("/bookmark/currentUser"); + if (!treeData[""]) { + treeData[""] = []; + } + Object.values(treeData).forEach(item => + item.forEach(item1 => { + item1.isLeaf = item1.type === 0; + item1.class = "treeNodeItem"; + item1.scopedSlots = { title: "nodeTitle" }; + }) + ); + let version = await HttpUtil.get("/user/version"); + await context.dispatch("updateVersion", version); + await context.dispatch(refreshHomePinList); + context.commit(TOTAL_TREE_DATA, treeData); + await localforage.setItem(TOTAL_TREE_DATA, treeData); + }, + //清除缓存数据 + async [clear](context) { + context.commit(TOTAL_TREE_DATA, null); + context.commit(VERSION, null); + context.commit(SHOW_REFRESH_TOAST, false); + context.commit(IS_INIT, false); + context.commit(IS_INITING, false); + context.commit(HOME_PIN_LIST, []); + if (timer != null) { + clearInterval(timer); + } + await localforage.removeItem(TOTAL_TREE_DATA); + await localforage.removeItem(VERSION); + }, + /** + * 移动节点 + */ + async moveNode(context, info) { + let data = context.state[TOTAL_TREE_DATA]; + const target = info.node.dataRef; + const current = info.dragNode.dataRef; + //从原来位置中删除当前节点 + let currentList = data[current.path]; + currentList.splice( + currentList.findIndex(item => item.bookmarkId === current.bookmarkId), + 1 + ); + //请求体 + const body = { + bookmarkId: current.bookmarkId, + sourcePath: current.path, + targetPath: "", + //-1 表示排在最后 + sort: -1 + }; + if (info.dropToGap) { + body.targetPath = target.path; + //移动到目标节点的上面或者下面 + let targetList = data[target.path]; + //目标节点index + let index = targetList.indexOf(target); + //移动节点相对于目标节点位置的增量 + let addIndex = info.dropPosition > index ? 1 : 0; + body.sort = target.sort + addIndex; + targetList.splice(index + addIndex, 0, current); + for (let i = index + 1; i < targetList.length; i++) { + targetList[i].sort += 1; + } + } else { + //移动到一个文件夹下面 + body.targetPath = target.path + "." + target.bookmarkId; + let targetList = data[body.targetPath]; + if (!targetList) { + targetList = []; + data[body.targetPath] = targetList; + } + body.sort = targetList.length > 0 ? targetList[targetList.length - 1].sort + 1 : 1; + targetList.push(current); + } + //更新节点的path和对应子节点path + current.path = body.targetPath; + current.sort = body.sort; + //如果为文件夹还要更新所有子书签的path + if (body.sourcePath !== body.targetPath) { + let keys = Object.keys(data); + //旧路径 + let oldPath = body.sourcePath + "." + current.bookmarkId; + //新路径 + let newPath = body.targetPath + "." + current.bookmarkId; + keys.forEach(item => { + if (!item.startsWith(oldPath)) { + return; + } + let newPathStr = item.replace(oldPath, newPath); + let list = data[item]; + delete data[item]; + data[newPathStr] = list; + list.forEach(item1 => (item1.path = newPathStr)); + }); + } + context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]); + await context.dispatch("updateVersion", null); + await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]); + return body; + }, + async [refreshHomePinList]({ commit }) { + let list = await HttpUtil.get("/home/pin"); + commit(HOME_PIN_LIST, list); + let map = {}; + list.filter(item => item.id).forEach(item => (map[item.bookmarkId] = true)); + commit(HOME_PIN_BOOKMARK_ID_MAP, map); + }, + /** + * 更新版本数据 + */ + async updateVersion({ commit, state }, version) { + commit(VERSION, version == null ? state[VERSION] + 1 : version); + await localforage.setItem(VERSION, state[VERSION]); + }, + /** + * 新增书签、文件夹 + */ + async [addNode](context, { sourceNode, targetNode }) { + if (sourceNode === null) { + if (context.state[TOTAL_TREE_DATA][""] === undefined) { + context.state[TOTAL_TREE_DATA][""] = []; + } + context.state[TOTAL_TREE_DATA][""].push(targetNode); + } else { + if (sourceNode.children === undefined) { + sourceNode.children = []; + } + sourceNode.children.push(targetNode); + } + if (targetNode.type === 0) { + context.state[TOTAL_TREE_DATA][targetNode.path + "." + targetNode.bookmarkId] = []; + } + targetNode.isLeaf = targetNode.type === 0; + targetNode.class = "treeNodeItem"; + targetNode.scopedSlots = { title: "nodeTitle" }; + context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]); + await context.dispatch("updateVersion", null); + await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]); + }, + /** + * 删除节点数据 + */ + async [deleteData](context, { pathList, bookmarkIdList }) { + //待删除的书签 + let bookmarkIdSet = new Set(); + bookmarkIdList.forEach(item => bookmarkIdSet.add(item)); + //删除子节点 + pathList.forEach(item => { + delete state[TOTAL_TREE_DATA][item]; + Object.keys(context.state[TOTAL_TREE_DATA]) + .filter(key => key.startsWith(item + ".")) + .forEach(key => delete state[TOTAL_TREE_DATA][key]); + bookmarkIdSet.add(parseInt(item.split(".").reverse())); + }); + //删除直接选中的节点 + Object.keys(context.state[TOTAL_TREE_DATA]).forEach(item => { + let list = context.state[TOTAL_TREE_DATA][item]; + for (let i = list.length - 1; i >= 0; i--) { + if (bookmarkIdSet.has(list[i].bookmarkId)) { + list.splice(i, 1); + } + } + }); + context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]); + await context.dispatch("updateVersion", null); + await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]); + }, + /** + * 编辑书签节点 + */ + async editNode({ dispatch, state, commit }, { node, newName, newUrl, newIcon }) { + node.name = newName; + node.url = newUrl; + node.icon = newIcon; + commit(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]); + await dispatch("updateVersion", null); + await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]); + } }; const mutations = { - [TOTAL_TREE_DATA]: (state, totalTreeData) => { - state.totalTreeData = totalTreeData; - }, - [IS_INIT] (state, isInit) { - state.isInit = isInit; - }, - [IS_INITING] (state, isIniting) { - state.isIniting = isIniting; - }, - [VERSION]: (state, version) => { - state[VERSION] = version; - }, - [SHOW_REFRESH_TOAST]: (state, val) => { - state[SHOW_REFRESH_TOAST] = val; - }, - [HOME_PIN_LIST]: (state, val) => { - state[HOME_PIN_LIST] = val; - }, - [HOME_PIN_BOOKMARK_ID_MAP]: (state, val) => { - state[HOME_PIN_BOOKMARK_ID_MAP] = val; - } + [TOTAL_TREE_DATA]: (state, totalTreeData) => { + state.totalTreeData = totalTreeData; + }, + [IS_INIT](state, isInit) { + state.isInit = isInit; + }, + [IS_INITING](state, isIniting) { + state.isIniting = isIniting; + }, + [VERSION]: (state, version) => { + state[VERSION] = version; + }, + [SHOW_REFRESH_TOAST]: (state, val) => { + state[SHOW_REFRESH_TOAST] = val; + }, + [HOME_PIN_LIST]: (state, val) => { + state[HOME_PIN_LIST] = val; + }, + [HOME_PIN_BOOKMARK_ID_MAP]: (state, val) => { + state[HOME_PIN_BOOKMARK_ID_MAP] = val; + } }; - /** * 检查书签缓存是否最新 - * - * @param {*} context - * @param {*} isFirst - * @returns + * + * @param {*} context + * @param {*} isFirst + * @returns */ -async function treeDataCheck (context, isFirst) { - if (toastShow || !checkJwtValid(context.rootState.globalConfig.token)) { - return; - } - let realVersion = await HttpUtil.get("/user/version"); - if (realVersion !== context.state[VERSION]) { - if (context.state[SHOW_REFRESH_TOAST] && !isFirst) { - //如果在书签管理页面需要弹窗提示 - window.vueInstance.$confirm({ - title: "书签数据有更新,是否立即刷新?", - cancelText: "稍后提醒", - closable: false, - keyboard: false, - maskClosable: false, - onOk () { - toastShow = false; - return new Promise(async (resolve) => { - await context.dispatch(refresh); - resolve(); - }); - }, - onCancel () { - toastShow = false; - } - }); - toastShow = true; - } else { - await context.dispatch(refresh); - } - } +async function treeDataCheck(context, isFirst) { + if (toastShow || !checkJwtValid(context.rootState.globalConfig.token)) { + return; + } + let realVersion = await HttpUtil.get("/user/version"); + if (realVersion !== context.state[VERSION]) { + if (context.state[SHOW_REFRESH_TOAST] && !isFirst) { + //如果在书签管理页面需要弹窗提示 + window.vueInstance.$confirm({ + title: "书签数据有更新,是否立即刷新?", + cancelText: "稍后提醒", + closable: false, + keyboard: false, + maskClosable: false, + onOk() { + toastShow = false; + return new Promise(async resolve => { + await context.dispatch(refresh); + resolve(); + }); + }, + onCancel() { + toastShow = false; + } + }); + toastShow = true; + } else { + await context.dispatch(refresh); + } + } } export const store = { - namespaced: true, - state, - getters, - actions, - mutations + namespaced: true, + state, + getters, + actions, + mutations }; - diff --git a/bookmark_front/src/views/manage/bookmarkTree/index.vue b/bookmark_front/src/views/manage/bookmarkTree/index.vue index 892b890..1e4442e 100644 --- a/bookmark_front/src/views/manage/bookmarkTree/index.vue +++ b/bookmark_front/src/views/manage/bookmarkTree/index.vue @@ -15,7 +15,7 @@ @@ -77,7 +77,7 @@ 新增 复制URL - {{ homePinList.filter((item) => item.id && item.bookmarkId == rec.dataRef.bookmarkId).length > 0 ? "从首页移除" : "固定到首页" }} + {{ homePinList.filter(item => item.id && item.bookmarkId == rec.dataRef.bookmarkId).length > 0 ? "从首页移除" : "固定到首页" }} 编辑 删除 @@ -114,7 +114,7 @@ export default { loadedKeys: [], // 已加载数据 replaceFields: { title: "name", - key: "bookmarkId", + key: "bookmarkId" }, mulSelect: false, // 多选框是否显示 currentSelect: null, // 当前树的选择项 @@ -126,19 +126,19 @@ export default { // 新增、修改目标数据,null说明向根节点增加数据 targetNode: null, // 是否为新增动作 - isAdd: false, + isAdd: false }, - copyBoard: null, //剪贴板对象 + copyBoard: null //剪贴板对象 }; }, computed: { ...mapState("treeData", ["totalTreeData", HOME_PIN_LIST]), - ...mapState("globalConfig", ["isPhone"]), + ...mapState("globalConfig", ["isPhone"]) }, watch: { totalTreeData(newVal, oldVal) { this.resetData(); - }, + } }, async mounted() { this.$store.commit(TREE_DATA + "/" + SHOW_REFRESH_TOAST, true); @@ -147,14 +147,21 @@ export default { this.loading = false; //初始化clipboard this.copyBoard = new ClipboardJS(".copy-to-board", { - text: function (trigger) { + text: function(trigger) { return trigger.attributes.data.nodeValue; - }, + } }); - this.copyBoard.on("success", (e) => { + this.copyBoard.on("success", e => { this.$message.success("复制成功"); e.clearSelection(); }); + + window.onblur = e => { + console.log("窗口非激活"); + }; + window.onfocus = e => { + console.log("窗口激活"); + }; }, beforeDestroy() { this.$store.commit(TREE_DATA + "/" + SHOW_REFRESH_TOAST, false); @@ -168,7 +175,7 @@ export default { */ loadData(treeNode) { console.log("加载数据", treeNode); - return new Promise((resolve) => { + return new Promise(resolve => { const data = typeof treeNode === "number" ? this.$store.getters["treeData/getById"](treeNode) : treeNode.dataRef; let newPath = data.path + "." + data.bookmarkId; if (!this.totalTreeData[newPath]) { @@ -212,9 +219,9 @@ export default { this.expandedKeys = [ ...item.path .split(".") - .filter((item) => item.length > 0) - .map((item) => parseInt(item)), - item.bookmarkId, + .filter(item => item.length > 0) + .map(item => parseInt(item)), + item.bookmarkId ]; } else { this.expandedKeys.pop(); @@ -228,7 +235,7 @@ export default { } else { this.checkedKeys.splice(this.checkedKeys.indexOf(item.bookmarkId), 1); this.checkedNodes.splice( - this.checkedNodes.findIndex((item1) => item1.bookmarkId === item.bookmarkId), + this.checkedNodes.findIndex(item1 => item1.bookmarkId === item.bookmarkId), 1 ); } @@ -248,9 +255,9 @@ export default { this.expandedKeys = [ ...item.path .split(".") - .filter((item) => item.length > 0) - .map((item) => parseInt(item)), - item.bookmarkId, + .filter(item => item.length > 0) + .map(item => parseInt(item)), + item.bookmarkId ]; } } else { @@ -273,7 +280,7 @@ export default { const bookmarkIdList = []; const pathList = []; if (this.checkedNodes) { - this.checkedNodes.forEach((item) => + this.checkedNodes.forEach(item => item.type === 1 ? pathList.push(item.path + "." + item.bookmarkId) : bookmarkIdList.push(item.bookmarkId) ); } @@ -290,7 +297,7 @@ export default { await HttpUtil.post("/bookmark/batchDelete", null, { pathList, bookmarkIdList }); await this.$store.dispatch(TREE_DATA + "/" + deleteData, { pathList, bookmarkIdList }); //删除已经被删除的数据 - pathList.forEach((item) => { + pathList.forEach(item => { const id = parseInt(item.split(".").reverse()[0]); let index = this.loadedKeys.indexOf(id); if (index > -1) { @@ -322,13 +329,13 @@ export default { this.refresh(false); this.expandedKeys = item.path .split(".") - .filter((one) => one.length > 0) - .map((one) => parseInt(one)); + .filter(one => one.length > 0) + .map(one => parseInt(one)); this.loadedKeys = item.path .split(".") - .filter((one) => one.length > 0) - .map((one) => parseInt(one)); - this.expandedKeys.forEach(async (one) => await this.loadData(one)); + .filter(one => one.length > 0) + .map(one => parseInt(one)); + this.expandedKeys.forEach(async one => await this.loadData(one)); this.currentSelect = item; }, /** @@ -344,7 +351,7 @@ export default { this.addModal = { show: false, targetNode: null, - isAdd: false, + isAdd: false }; }, async onDrop(info) { @@ -388,12 +395,12 @@ export default { await this.deleteBookmarks(); resolve(); }); - }, + } }); } else if (key === "edit") { this.editData(); } else if (key === "pin") { - let pin = this.homePinList.filter((one) => one.id && one.bookmarkId == item.bookmarkId); + let pin = this.homePinList.filter(one => one.id && one.bookmarkId == item.bookmarkId); if (pin.length > 0) { await HttpUtil.delete("/home/pin", { id: pin[0].id }); } else { @@ -411,8 +418,8 @@ export default { dealList(root, map[""], map); let content = exportFileHead + root.outerHTML; downloadFile(moment().format("YYYY-MM-DD") + "导出书签.html", content); - }, - }, + } + } }; diff --git a/bookmark_front/src/views/manage/sso/auth/index.vue b/bookmark_front/src/views/manage/sso/auth/index.vue new file mode 100644 index 0000000..4f63b01 --- /dev/null +++ b/bookmark_front/src/views/manage/sso/auth/index.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/bookmark_front/src/views/noHead/addBookmark/index.vue b/bookmark_front/src/views/noHead/addBookmark/index.vue new file mode 100644 index 0000000..c7596dd --- /dev/null +++ b/bookmark_front/src/views/noHead/addBookmark/index.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/bookmark_front/src/views/public/about/index.vue b/bookmark_front/src/views/public/about/index.vue index 1288485..f8dfc62 100644 --- a/bookmark_front/src/views/public/about/index.vue +++ b/bookmark_front/src/views/public/about/index.vue @@ -9,6 +9,11 @@ 使用教程: 点击跳转 +
+ 浏览器插件: + 点击下载 + ,使用详情请参考使用教程 +
交流反馈qq群:150056494,邮箱:fleyx20@outlook.com
统计: @@ -38,7 +43,7 @@ export default { script.defer = true; script.src = "https://qiezi.fleyx.com/qiezijs/1.0/qiezi_statistic.min.js"; document.getElementsByTagName("head")[0].appendChild(script); - }, + } }; diff --git a/浏览器插件/bookmarkBrowserPlugin/background.js b/浏览器插件/bookmarkBrowserPlugin/background.js new file mode 100644 index 0000000..d42af06 --- /dev/null +++ b/浏览器插件/bookmarkBrowserPlugin/background.js @@ -0,0 +1,100 @@ +chrome.runtime.onInstalled.addListener(() => { + chrome.contextMenus.create( + { + title: '添加到书签', + id: "addBookmark", + }, + () => console.log("创建右键菜单成功") + ); +}); + + + +chrome.contextMenus.onClicked.addListener(async function (info, tab) { + console.log(info, tab); + let body = { + name: tab.title, + url: tab.url, + }; + sendToContent(tab.id, { code: "addBookmark", data: body, token: await getVal("token") }); +}); + + +// 接收content/popup发送的消息 +chrome.runtime.onMessage.addListener(async (data, sender, sendResponse) => { + if (!data.code || !data.receiver == 'background') { + return; + } + sendResponse("ok"); + console.log("收到消息:", data, sender); + if (data.code == 'setToken') { + await setVal("token", data.data); + // sendToContent + await sendToContent(sender.tab.id, { code: "setTokenOk" }); + } else if (data.code == 'getToken') { + let token = await getVal("token"); + sendToPopup({ code: "setToken", data: await getVal("token") }); + } else if (data.code == "clearToken") { + await clearVal("token"); + } +}) + +/** + * 向content发送消息 + * @param {*} tabId + * @param {*} data + */ +function sendToContent (tabId, data) { + console.log(tabId, data); + data.receiver = "content"; + chrome.tabs.sendMessage(tabId, data, res => { + console.log("接受响应", res); + }) +} + +/** + * 向popup发送消息 + * @param {*} data + */ +function sendToPopup (data) { + data.receiver = "popup"; + chrome.runtime.sendMessage(data, res => console.log(res)); +} + +/** + * 设置值 + * @param {*} key + * @param {*} val + * @returns + */ +function setVal (key, val) { + return new Promise((resolve, reject) => { + chrome.storage.local.set({ [key]: val }, function () { + console.log("设置值成功:", key, val) + resolve(); + }) + }) +} + +/** + * 获取值 + * @param {*} key + * @returns + */ +function getVal (key) { + return new Promise((resolve, reject) => { + chrome.storage.local.get([key], function (res) { + console.log("取值成功", res); + resolve(res[key]); + }) + }) +} + +function clearVal (key) { + return new Promise((resolve, reject) => { + chrome.storage.local.remove(key, function () { + console.log("remove成功", key); + resolve(); + }) + }) +} \ No newline at end of file diff --git a/浏览器插件/bookmarkBrowserPlugin/manifest.json b/浏览器插件/bookmarkBrowserPlugin/manifest.json new file mode 100644 index 0000000..1e99669 --- /dev/null +++ b/浏览器插件/bookmarkBrowserPlugin/manifest.json @@ -0,0 +1,26 @@ +{ + "name": "签签世界", + "description": "云书签管理平台", + "version": "1.0", + "manifest_version": 3, + "permissions": ["contextMenus", "storage"], + "action": { + "default_popup": "popup/index.html" + }, + "icons": { + "48": "static/icons/favicon.png", + "128": "static/icons/favicon.png" + }, + "background": { + "service_worker": "background.js" + }, + "options_ui": { + "page": "options/index.html" + }, + "content_scripts": [ + { + "matches": ["*://*/*"], + "js": ["static/js/axios.min.js", "static/js/config.js", "static/js/content.js"] + } + ] +} diff --git a/浏览器插件/bookmarkBrowserPlugin/options/index.html b/浏览器插件/bookmarkBrowserPlugin/options/index.html new file mode 100644 index 0000000..2570f4f --- /dev/null +++ b/浏览器插件/bookmarkBrowserPlugin/options/index.html @@ -0,0 +1,16 @@ + + + + + + Document + + + +

option.html

+ + diff --git a/浏览器插件/bookmarkBrowserPlugin/popup/index.html b/浏览器插件/bookmarkBrowserPlugin/popup/index.html new file mode 100644 index 0000000..d82fbaa --- /dev/null +++ b/浏览器插件/bookmarkBrowserPlugin/popup/index.html @@ -0,0 +1,31 @@ + + + + + + Document + + + + 点击登录 + +

+
插件版本:
+ + + + + diff --git a/浏览器插件/bookmarkBrowserPlugin/popup/index.js b/浏览器插件/bookmarkBrowserPlugin/popup/index.js new file mode 100644 index 0000000..04e6582 --- /dev/null +++ b/浏览器插件/bookmarkBrowserPlugin/popup/index.js @@ -0,0 +1,50 @@ +console.log("asdf"); +console.log(bookmarkHost); + +var token; +var login = document.getElementById("login"); +var action = document.getElementById("action"); + +(async () => { + //初始化 + login.href = bookmarkHost + "/manage/sso/auth"; + document.getElementById("version").innerText = version; + sendToBg("getToken", null); +})(); + +/** + * 退出登陆 + */ +document.getElementById("logout").addEventListener("click", () => { + console.log("click"); + sendToBg("clearToken", null); + action.style.display = "none"; + login.style.display = "block"; +}); + +/** + * 发送消息到后台 + * @param {*} data + */ +function sendToBg (code, data) { + chrome.runtime.sendMessage({ code, data, receiver: "background" }, res => console.log(res)); +} + + +// 接收content/background发送的消息 +chrome.runtime.onMessage.addListener(async (data, sender, sendResponse) => { + if (!data.code || !data.receiver == 'popup') { + return; + } + sendResponse("ok"); + console.log("popup收到消息:", data); + if (data.code == 'setToken') { + token = data.data; + if (token) { + action.style.display = "block"; + login.style.display = "none"; + } else { + login.style.display = "block"; + } + } +}) \ No newline at end of file diff --git a/浏览器插件/bookmarkBrowserPlugin/static/icons/favicon.ico b/浏览器插件/bookmarkBrowserPlugin/static/icons/favicon.ico new file mode 100644 index 0000000..f354250 Binary files /dev/null and b/浏览器插件/bookmarkBrowserPlugin/static/icons/favicon.ico differ diff --git a/浏览器插件/bookmarkBrowserPlugin/static/icons/favicon.png b/浏览器插件/bookmarkBrowserPlugin/static/icons/favicon.png new file mode 100644 index 0000000..a3dff88 Binary files /dev/null and b/浏览器插件/bookmarkBrowserPlugin/static/icons/favicon.png differ diff --git a/浏览器插件/bookmarkBrowserPlugin/static/js/axios.min.js b/浏览器插件/bookmarkBrowserPlugin/static/js/axios.min.js new file mode 100644 index 0000000..9b44143 --- /dev/null +++ b/浏览器插件/bookmarkBrowserPlugin/static/js/axios.min.js @@ -0,0 +1,2 @@ +!function (t, e) { "object" == typeof exports && "object" == typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define([], e) : "object" == typeof exports ? exports.axios = e() : t.axios = e() }(this, (function () { return function (t) { var e = {}; function r (n) { if (e[n]) return e[n].exports; var o = e[n] = { i: n, l: !1, exports: {} }; return t[n].call(o.exports, o, o.exports, r), o.l = !0, o.exports } return r.m = t, r.c = e, r.d = function (t, e, n) { r.o(t, e) || Object.defineProperty(t, e, { enumerable: !0, get: n }) }, r.r = function (t) { "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(t, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(t, "__esModule", { value: !0 }) }, r.t = function (t, e) { if (1 & e && (t = r(t)), 8 & e) return t; if (4 & e && "object" == typeof t && t && t.__esModule) return t; var n = Object.create(null); if (r.r(n), Object.defineProperty(n, "default", { enumerable: !0, value: t }), 2 & e && "string" != typeof t) for (var o in t) r.d(n, o, function (e) { return t[e] }.bind(null, o)); return n }, r.n = function (t) { var e = t && t.__esModule ? function () { return t.default } : function () { return t }; return r.d(e, "a", e), e }, r.o = function (t, e) { return Object.prototype.hasOwnProperty.call(t, e) }, r.p = "", r(r.s = 12) }([function (t, e, r) { "use strict"; var n = r(4), o = Object.prototype.toString; function s (t) { return Array.isArray(t) } function i (t) { return void 0 === t } function a (t) { return "[object ArrayBuffer]" === o.call(t) } function u (t) { return null !== t && "object" == typeof t } function c (t) { if ("[object Object]" !== o.call(t)) return !1; var e = Object.getPrototypeOf(t); return null === e || e === Object.prototype } function h (t) { return "[object Function]" === o.call(t) } function f (t, e) { if (null != t) if ("object" != typeof t && (t = [t]), s(t)) for (var r = 0, n = t.length; r < n; r++)e.call(null, t[r], r, t); else for (var o in t) Object.prototype.hasOwnProperty.call(t, o) && e.call(null, t[o], o, t) } t.exports = { supportedProtocols: ["http:", "https:", "file:"], getProtocol: function (t) { return t || "http:" }, isArray: s, isArrayBuffer: a, isBuffer: function (t) { return null !== t && !i(t) && null !== t.constructor && !i(t.constructor) && "function" == typeof t.constructor.isBuffer && t.constructor.isBuffer(t) }, isFormData: function (t) { return "[object FormData]" === o.call(t) }, isArrayBufferView: function (t) { return "undefined" != typeof ArrayBuffer && ArrayBuffer.isView ? ArrayBuffer.isView(t) : t && t.buffer && a(t.buffer) }, isString: function (t) { return "string" == typeof t }, isNumber: function (t) { return "number" == typeof t }, isObject: u, isPlainObject: c, isUndefined: i, isDate: function (t) { return "[object Date]" === o.call(t) }, isFile: function (t) { return "[object File]" === o.call(t) }, isBlob: function (t) { return "[object Blob]" === o.call(t) }, isFunction: h, isStream: function (t) { return u(t) && h(t.pipe) }, isURLSearchParams: function (t) { return "[object URLSearchParams]" === o.call(t) }, isStandardBrowserEnv: function () { return ("undefined" == typeof navigator || "ReactNative" !== navigator.product && "NativeScript" !== navigator.product && "NS" !== navigator.product) && ("undefined" != typeof window && "undefined" != typeof document) }, forEach: f, merge: function t () { var e = {}; function r (r, n) { c(e[n]) && c(r) ? e[n] = t(e[n], r) : c(r) ? e[n] = t({}, r) : s(r) ? e[n] = r.slice() : e[n] = r } for (var n = 0, o = arguments.length; n < o; n++)f(arguments[n], r); return e }, extend: function (t, e, r) { return f(e, (function (e, o) { t[o] = r && "function" == typeof e ? n(e, r) : e })), t }, trim: function (t) { return t.trim ? t.trim() : t.replace(/^\s+|\s+$/g, "") }, stripBOM: function (t) { return 65279 === t.charCodeAt(0) && (t = t.slice(1)), t }, inherits: function (t, e, r, n) { t.prototype = Object.create(e.prototype, n), t.prototype.constructor = t, r && Object.assign(t.prototype, r) }, toFlatObject: function (t, e, r) { var n, o, s, i = {}; e = e || {}; do { for (o = (n = Object.getOwnPropertyNames(t)).length; o-- > 0;)i[s = n[o]] || (e[s] = t[s], i[s] = !0); t = Object.getPrototypeOf(t) } while (t && (!r || r(t, e)) && t !== Object.prototype); return e } } }, function (t, e, r) { "use strict"; var n = r(0); function o (t, e, r, n, o) { Error.call(this), this.message = t, this.name = "AxiosError", e && (this.code = e), r && (this.config = r), n && (this.request = n), o && (this.response = o) } n.inherits(o, Error, { toJSON: function () { return { message: this.message, name: this.name, description: this.description, number: this.number, fileName: this.fileName, lineNumber: this.lineNumber, columnNumber: this.columnNumber, stack: this.stack, config: this.config, code: this.code, status: this.response && this.response.status ? this.response.status : null } } }); var s = o.prototype, i = {};["ERR_BAD_OPTION_VALUE", "ERR_BAD_OPTION", "ECONNABORTED", "ETIMEDOUT", "ERR_NETWORK", "ERR_FR_TOO_MANY_REDIRECTS", "ERR_DEPRECATED", "ERR_BAD_RESPONSE", "ERR_BAD_REQUEST", "ERR_CANCELED"].forEach((function (t) { i[t] = { value: t } })), Object.defineProperties(o, i), Object.defineProperty(s, "isAxiosError", { value: !0 }), o.from = function (t, e, r, i, a, u) { var c = Object.create(s); return n.toFlatObject(t, c, (function (t) { return t !== Error.prototype })), o.call(c, t.message, e, r, i, a), c.name = t.name, u && Object.assign(c, u), c }, t.exports = o }, function (t, e, r) { "use strict"; var n = r(1); function o (t) { n.call(this, null == t ? "canceled" : t, n.ERR_CANCELED), this.name = "CanceledError" } r(0).inherits(o, n, { __CANCEL__: !0 }), t.exports = o }, function (t, e, r) { "use strict"; var n = r(0), o = r(18), s = r(1), i = r(6), a = { "Content-Type": "application/x-www-form-urlencoded" }; function u (t, e) { !n.isUndefined(t) && n.isUndefined(t["Content-Type"]) && (t["Content-Type"] = e) } var c, h = { transitional: i, adapter: (("undefined" != typeof XMLHttpRequest || "undefined" != typeof process && "[object process]" === Object.prototype.toString.call(process)) && (c = r(7)), c), transformRequest: [function (t, e) { return o(e, "Accept"), o(e, "Content-Type"), n.isFormData(t) || n.isArrayBuffer(t) || n.isBuffer(t) || n.isStream(t) || n.isFile(t) || n.isBlob(t) ? t : n.isArrayBufferView(t) ? t.buffer : n.isURLSearchParams(t) ? (u(e, "application/x-www-form-urlencoded;charset=utf-8"), t.toString()) : n.isObject(t) || e && "application/json" === e["Content-Type"] ? (u(e, "application/json"), function (t, e, r) { if (n.isString(t)) try { return (e || JSON.parse)(t), n.trim(t) } catch (t) { if ("SyntaxError" !== t.name) throw t } return (r || JSON.stringify)(t) }(t)) : t }], transformResponse: [function (t) { var e = this.transitional || h.transitional, r = e && e.silentJSONParsing, o = e && e.forcedJSONParsing, i = !r && "json" === this.responseType; if (i || o && n.isString(t) && t.length) try { return JSON.parse(t) } catch (t) { if (i) { if ("SyntaxError" === t.name) throw s.from(t, s.ERR_BAD_RESPONSE, this, null, this.response); throw t } } return t }], timeout: 0, xsrfCookieName: "XSRF-TOKEN", xsrfHeaderName: "X-XSRF-TOKEN", maxContentLength: -1, maxBodyLength: -1, validateStatus: function (t) { return t >= 200 && t < 300 }, headers: { common: { Accept: "application/json, text/plain, */*" } } }; n.forEach(["delete", "get", "head"], (function (t) { h.headers[t] = {} })), n.forEach(["post", "put", "patch"], (function (t) { h.headers[t] = n.merge(a) })), t.exports = h }, function (t, e, r) { "use strict"; t.exports = function (t, e) { return function () { for (var r = new Array(arguments.length), n = 0; n < r.length; n++)r[n] = arguments[n]; return t.apply(e, r) } } }, function (t, e, r) { "use strict"; var n = r(0); function o (t) { return encodeURIComponent(t).replace(/%3A/gi, ":").replace(/%24/g, "$").replace(/%2C/gi, ",").replace(/%20/g, "+").replace(/%5B/gi, "[").replace(/%5D/gi, "]") } t.exports = function (t, e, r) { if (!e) return t; var s; if (r) s = r(e); else if (n.isURLSearchParams(e)) s = e.toString(); else { var i = []; n.forEach(e, (function (t, e) { null != t && (n.isArray(t) ? e += "[]" : t = [t], n.forEach(t, (function (t) { n.isDate(t) ? t = t.toISOString() : n.isObject(t) && (t = JSON.stringify(t)), i.push(o(e) + "=" + o(t)) }))) })), s = i.join("&") } if (s) { var a = t.indexOf("#"); -1 !== a && (t = t.slice(0, a)), t += (-1 === t.indexOf("?") ? "?" : "&") + s } return t } }, function (t, e, r) { "use strict"; t.exports = { silentJSONParsing: !0, forcedJSONParsing: !0, clarifyTimeoutError: !1 } }, function (t, e, r) { "use strict"; var n = r(0), o = r(19), s = r(20), i = r(5), a = r(8), u = r(23), c = r(24), h = r(25), f = r(6), l = r(1), p = r(2); t.exports = function (t) { return new Promise((function (e, r) { var d, m = t.data, v = t.headers, y = t.responseType; function g () { t.cancelToken && t.cancelToken.unsubscribe(d), t.signal && t.signal.removeEventListener("abort", d) } n.isFormData(m) && delete v["Content-Type"]; var b = new XMLHttpRequest; if (t.auth) { var O = t.auth.username || "", E = t.auth.password ? unescape(encodeURIComponent(t.auth.password)) : ""; v.Authorization = "Basic " + btoa(O + ":" + E) } var x = a(t.baseURL, t.url), w = h.parse(x), j = n.getProtocol(w.protocol); function R () { if (b) { var n = "getAllResponseHeaders" in b ? u(b.getAllResponseHeaders()) : null, s = { data: y && "text" !== y && "json" !== y ? b.response : b.responseText, status: b.status, statusText: b.statusText, headers: n, config: t, request: b }; o((function (t) { e(t), g() }), (function (t) { r(t), g() }), s), b = null } } if (b.open(t.method.toUpperCase(), i(x, t.params, t.paramsSerializer), !0), b.timeout = t.timeout, "onloadend" in b ? b.onloadend = R : b.onreadystatechange = function () { b && 4 === b.readyState && (0 !== b.status || b.responseURL && 0 === b.responseURL.indexOf("file:")) && setTimeout(R) }, b.onabort = function () { b && (r(new l("Request aborted", l.ECONNABORTED, t, b)), b = null) }, b.onerror = function () { r(new l("Network Error", l.ERR_NETWORK, t, b, b)), b = null }, b.ontimeout = function () { var e = t.timeout ? "timeout of " + t.timeout + "ms exceeded" : "timeout exceeded", n = t.transitional || f; t.timeoutErrorMessage && (e = t.timeoutErrorMessage), r(new l(e, n.clarifyTimeoutError ? l.ETIMEDOUT : l.ECONNABORTED, t, b)), b = null }, n.isStandardBrowserEnv()) { var A = (t.withCredentials || c(x)) && t.xsrfCookieName ? s.read(t.xsrfCookieName) : void 0; A && (v[t.xsrfHeaderName] = A) } "setRequestHeader" in b && n.forEach(v, (function (t, e) { void 0 === m && "content-type" === e.toLowerCase() ? delete v[e] : b.setRequestHeader(e, t) })), n.isUndefined(t.withCredentials) || (b.withCredentials = !!t.withCredentials), y && "json" !== y && (b.responseType = t.responseType), "function" == typeof t.onDownloadProgress && b.addEventListener("progress", t.onDownloadProgress), "function" == typeof t.onUploadProgress && b.upload && b.upload.addEventListener("progress", t.onUploadProgress), (t.cancelToken || t.signal) && (d = function (t) { b && (r(!t || t && t.type ? new p : t), b.abort(), b = null) }, t.cancelToken && t.cancelToken.subscribe(d), t.signal && (t.signal.aborted ? d() : t.signal.addEventListener("abort", d))), m || (m = null), null !== w.path ? n.supportedProtocols.includes(j) ? b.send(m) : r(new l("Unsupported protocol " + j, l.ERR_BAD_REQUEST, t)) : r(new l("Malformed URL " + x, l.ERR_BAD_REQUEST, t)) })) } }, function (t, e, r) { "use strict"; var n = r(21), o = r(22); t.exports = function (t, e) { return t && !n(e) ? o(t, e) : e } }, function (t, e, r) { "use strict"; t.exports = function (t) { return !(!t || !t.__CANCEL__) } }, function (t, e, r) { "use strict"; var n = r(0); t.exports = function (t, e) { e = e || {}; var r = {}; function o (t, e) { return n.isPlainObject(t) && n.isPlainObject(e) ? n.merge(t, e) : n.isPlainObject(e) ? n.merge({}, e) : n.isArray(e) ? e.slice() : e } function s (r) { return n.isUndefined(e[r]) ? n.isUndefined(t[r]) ? void 0 : o(void 0, t[r]) : o(t[r], e[r]) } function i (t) { if (!n.isUndefined(e[t])) return o(void 0, e[t]) } function a (r) { return n.isUndefined(e[r]) ? n.isUndefined(t[r]) ? void 0 : o(void 0, t[r]) : o(void 0, e[r]) } function u (r) { return r in e ? o(t[r], e[r]) : r in t ? o(void 0, t[r]) : void 0 } var c = { url: i, method: i, data: i, baseURL: a, transformRequest: a, transformResponse: a, paramsSerializer: a, timeout: a, timeoutMessage: a, withCredentials: a, adapter: a, responseType: a, xsrfCookieName: a, xsrfHeaderName: a, onUploadProgress: a, onDownloadProgress: a, decompress: a, maxContentLength: a, maxBodyLength: a, beforeRedirect: a, transport: a, httpAgent: a, httpsAgent: a, cancelToken: a, socketPath: a, responseEncoding: a, validateStatus: u }; return n.forEach(Object.keys(t).concat(Object.keys(e)), (function (t) { var e = c[t] || s, o = e(t); n.isUndefined(o) && e !== u || (r[t] = o) })), r } }, function (t, e) { t.exports = { version: "0.26.1" } }, function (t, e, r) { t.exports = r(13) }, function (t, e, r) { "use strict"; var n = r(0), o = r(4), s = r(14), i = r(10); var a = function t (e) { var r = new s(e), a = o(s.prototype.request, r); return n.extend(a, s.prototype, r), n.extend(a, r), a.create = function (r) { return t(i(e, r)) }, a }(r(3)); a.Axios = s, a.CanceledError = r(2), a.CancelToken = r(34), a.isCancel = r(9), a.VERSION = r(11).version, a.AxiosError = r(1), a.Cancel = a.CanceledError, a.all = function (t) { return Promise.all(t) }, a.spread = r(35), a.isAxiosError = r(36), t.exports = a, t.exports.default = a }, function (t, e, r) { "use strict"; var n = r(0), o = r(5), s = r(15), i = r(16), a = r(10), u = r(8), c = r(33), h = c.validators; function f (t) { this.defaults = t, this.interceptors = { request: new s, response: new s } } f.prototype.request = function (t, e) { "string" == typeof t ? (e = e || {}).url = t : e = t || {}, (e = a(this.defaults, e)).method ? e.method = e.method.toLowerCase() : this.defaults.method ? e.method = this.defaults.method.toLowerCase() : e.method = "get"; var r = e.transitional; void 0 !== r && c.assertOptions(r, { silentJSONParsing: h.transitional(h.boolean), forcedJSONParsing: h.transitional(h.boolean), clarifyTimeoutError: h.transitional(h.boolean) }, !1); var n = [], o = !0; this.interceptors.request.forEach((function (t) { "function" == typeof t.runWhen && !1 === t.runWhen(e) || (o = o && t.synchronous, n.unshift(t.fulfilled, t.rejected)) })); var s, u = []; if (this.interceptors.response.forEach((function (t) { u.push(t.fulfilled, t.rejected) })), !o) { var f = [i, void 0]; for (Array.prototype.unshift.apply(f, n), f = f.concat(u), s = Promise.resolve(e); f.length;)s = s.then(f.shift(), f.shift()); return s } for (var l = e; n.length;) { var p = n.shift(), d = n.shift(); try { l = p(l) } catch (t) { d(t); break } } try { s = i(l) } catch (t) { return Promise.reject(t) } for (; u.length;)s = s.then(u.shift(), u.shift()); return s }, f.prototype.getUri = function (t) { t = a(this.defaults, t); var e = u(t.baseURL, t.url); return o(e, t.params, t.paramsSerializer) }, n.forEach(["delete", "get", "head", "options"], (function (t) { f.prototype[t] = function (e, r) { return this.request(a(r || {}, { method: t, url: e, data: (r || {}).data })) } })), n.forEach(["post", "put", "patch"], (function (t) { f.prototype[t] = function (e, r, n) { return this.request(a(n || {}, { method: t, url: e, data: r })) } })), t.exports = f }, function (t, e, r) { "use strict"; var n = r(0); function o () { this.handlers = [] } o.prototype.use = function (t, e, r) { return this.handlers.push({ fulfilled: t, rejected: e, synchronous: !!r && r.synchronous, runWhen: r ? r.runWhen : null }), this.handlers.length - 1 }, o.prototype.eject = function (t) { this.handlers[t] && (this.handlers[t] = null) }, o.prototype.forEach = function (t) { n.forEach(this.handlers, (function (e) { null !== e && t(e) })) }, t.exports = o }, function (t, e, r) { "use strict"; var n = r(0), o = r(17), s = r(9), i = r(3), a = r(2); function u (t) { if (t.cancelToken && t.cancelToken.throwIfRequested(), t.signal && t.signal.aborted) throw new a } t.exports = function (t) { return u(t), t.headers = t.headers || {}, t.data = o.call(t, t.data, t.headers, t.transformRequest), t.headers = n.merge(t.headers.common || {}, t.headers[t.method] || {}, t.headers), n.forEach(["delete", "get", "head", "post", "put", "patch", "common"], (function (e) { delete t.headers[e] })), (t.adapter || i.adapter)(t).then((function (e) { return u(t), e.data = o.call(t, e.data, e.headers, t.transformResponse), e }), (function (e) { return s(e) || (u(t), e && e.response && (e.response.data = o.call(t, e.response.data, e.response.headers, t.transformResponse))), Promise.reject(e) })) } }, function (t, e, r) { "use strict"; var n = r(0), o = r(3); t.exports = function (t, e, r) { var s = this || o; return n.forEach(r, (function (r) { t = r.call(s, t, e) })), t } }, function (t, e, r) { "use strict"; var n = r(0); t.exports = function (t, e) { n.forEach(t, (function (r, n) { n !== e && n.toUpperCase() === e.toUpperCase() && (t[e] = r, delete t[n]) })) } }, function (t, e, r) { "use strict"; var n = r(1); t.exports = function (t, e, r) { var o = r.config.validateStatus; r.status && o && !o(r.status) ? e(new n("Request failed with status code " + r.status, [n.ERR_BAD_REQUEST, n.ERR_BAD_RESPONSE][Math.floor(r.status / 100) - 4], r.config, r.request, r)) : t(r) } }, function (t, e, r) { "use strict"; var n = r(0); t.exports = n.isStandardBrowserEnv() ? { write: function (t, e, r, o, s, i) { var a = []; a.push(t + "=" + encodeURIComponent(e)), n.isNumber(r) && a.push("expires=" + new Date(r).toGMTString()), n.isString(o) && a.push("path=" + o), n.isString(s) && a.push("domain=" + s), !0 === i && a.push("secure"), document.cookie = a.join("; ") }, read: function (t) { var e = document.cookie.match(new RegExp("(^|;\\s*)(" + t + ")=([^;]*)")); return e ? decodeURIComponent(e[3]) : null }, remove: function (t) { this.write(t, "", Date.now() - 864e5) } } : { write: function () { }, read: function () { return null }, remove: function () { } } }, function (t, e, r) { "use strict"; t.exports = function (t) { return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(t) } }, function (t, e, r) { "use strict"; t.exports = function (t, e) { return e ? t.replace(/\/+$/, "") + "/" + e.replace(/^\/+/, "") : t } }, function (t, e, r) { "use strict"; var n = r(0), o = ["age", "authorization", "content-length", "content-type", "etag", "expires", "from", "host", "if-modified-since", "if-unmodified-since", "last-modified", "location", "max-forwards", "proxy-authorization", "referer", "retry-after", "user-agent"]; t.exports = function (t) { var e, r, s, i = {}; return t ? (n.forEach(t.split("\n"), (function (t) { if (s = t.indexOf(":"), e = n.trim(t.substr(0, s)).toLowerCase(), r = n.trim(t.substr(s + 1)), e) { if (i[e] && o.indexOf(e) >= 0) return; i[e] = "set-cookie" === e ? (i[e] ? i[e] : []).concat([r]) : i[e] ? i[e] + ", " + r : r } })), i) : i } }, function (t, e, r) { "use strict"; var n = r(0); t.exports = n.isStandardBrowserEnv() ? function () { var t, e = /(msie|trident)/i.test(navigator.userAgent), r = document.createElement("a"); function o (t) { var n = t; return e && (r.setAttribute("href", n), n = r.href), r.setAttribute("href", n), { href: r.href, protocol: r.protocol ? r.protocol.replace(/:$/, "") : "", host: r.host, search: r.search ? r.search.replace(/^\?/, "") : "", hash: r.hash ? r.hash.replace(/^#/, "") : "", hostname: r.hostname, port: r.port, pathname: "/" === r.pathname.charAt(0) ? r.pathname : "/" + r.pathname } } return t = o(window.location.href), function (e) { var r = n.isString(e) ? o(e) : e; return r.protocol === t.protocol && r.host === t.host } }() : function () { return !0 } }, function (t, e, r) { "use strict"; var n = r(26), o = r(29); function s () { this.protocol = null, this.slashes = null, this.auth = null, this.host = null, this.port = null, this.hostname = null, this.hash = null, this.search = null, this.query = null, this.pathname = null, this.path = null, this.href = null } e.parse = b, e.resolve = function (t, e) { return b(t, !1, !0).resolve(e) }, e.resolveObject = function (t, e) { return t ? b(t, !1, !0).resolveObject(e) : e }, e.format = function (t) { o.isString(t) && (t = b(t)); return t instanceof s ? t.format() : s.prototype.format.call(t) }, e.Url = s; var i = /^([a-z0-9.+-]+:)/i, a = /:[0-9]*$/, u = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/, c = ["{", "}", "|", "\\", "^", "`"].concat(["<", ">", '"', "`", " ", "\r", "\n", "\t"]), h = ["'"].concat(c), f = ["%", "/", "?", ";", "#"].concat(h), l = ["/", "?", "#"], p = /^[+a-z0-9A-Z_-]{0,63}$/, d = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, m = { javascript: !0, "javascript:": !0 }, v = { javascript: !0, "javascript:": !0 }, y = { http: !0, https: !0, ftp: !0, gopher: !0, file: !0, "http:": !0, "https:": !0, "ftp:": !0, "gopher:": !0, "file:": !0 }, g = r(30); function b (t, e, r) { if (t && o.isObject(t) && t instanceof s) return t; var n = new s; return n.parse(t, e, r), n } s.prototype.parse = function (t, e, r) { if (!o.isString(t)) throw new TypeError("Parameter 'url' must be a string, not " + typeof t); var s = t.indexOf("?"), a = -1 !== s && s < t.indexOf("#") ? "?" : "#", c = t.split(a); c[0] = c[0].replace(/\\/g, "/"); var b = t = c.join(a); if (b = b.trim(), !r && 1 === t.split("#").length) { var O = u.exec(b); if (O) return this.path = b, this.href = b, this.pathname = O[1], O[2] ? (this.search = O[2], this.query = e ? g.parse(this.search.substr(1)) : this.search.substr(1)) : e && (this.search = "", this.query = {}), this } var E = i.exec(b); if (E) { var x = (E = E[0]).toLowerCase(); this.protocol = x, b = b.substr(E.length) } if (r || E || b.match(/^\/\/[^@\/]+@[^@\/]+/)) { var w = "//" === b.substr(0, 2); !w || E && v[E] || (b = b.substr(2), this.slashes = !0) } if (!v[E] && (w || E && !y[E])) { for (var j, R, A = -1, C = 0; C < l.length; C++) { -1 !== (S = b.indexOf(l[C])) && (-1 === A || S < A) && (A = S) } -1 !== (R = -1 === A ? b.lastIndexOf("@") : b.lastIndexOf("@", A)) && (j = b.slice(0, R), b = b.slice(R + 1), this.auth = decodeURIComponent(j)), A = -1; for (C = 0; C < f.length; C++) { var S; -1 !== (S = b.indexOf(f[C])) && (-1 === A || S < A) && (A = S) } -1 === A && (A = b.length), this.host = b.slice(0, A), b = b.slice(A), this.parseHost(), this.hostname = this.hostname || ""; var _ = "[" === this.hostname[0] && "]" === this.hostname[this.hostname.length - 1]; if (!_) for (var N = this.hostname.split(/\./), P = (C = 0, N.length); C < P; C++) { var T = N[C]; if (T && !T.match(p)) { for (var U = "", q=0,B=T.length;q127?U+="x":U+=T[q];if(!U.match(p)){var D=N.slice(0,C),I=N.slice(C+1),k=T.match(d);k&&(D.push(k[1]),I.unshift(k[2])),I.length&&(b="/"+I.join(".")+b),this.hostname=D.join(".");break}}}this.hostname.length>255?this.hostname="":this.hostname=this.hostname.toLowerCase(),_||(this.hostname=n.toASCII(this.hostname));var L=this.port?":"+this.port:"",F=this.hostname||"";this.host=F+L,this.href+=this.host,_&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),"/"!==b[0]&&(b="/"+b))}if(!m[x])for(C=0,P=h.length;C0)&&r.host.split("@"))&&(r.auth=_.shift(),r.host=r.hostname=_.shift());return r.search=t.search,r.query=t.query,o.isNull(r.pathname)&&o.isNull(r.search)||(r.path=(r.pathname?r.pathname:"")+(r.search?r.search:"")),r.href=r.format(),r}if(!w.length)return r.pathname=null,r.search?r.path="/"+r.search:r.path=null,r.href=r.format(),r;for(var R=w.slice(-1)[0],A=(r.host||t.host||w.length>1)&&("."===R||".."===R)||""===R,C=0,S=w.length;S>=0;S--)"."===(R=w[S])?w.splice(S,1):".."===R?(w.splice(S,1),C++):C&&(w.splice(S,1),C--);if(!E&&!x)for(;C--;C)w.unshift("..");!E||""===w[0]||w[0]&&"/"===w[0].charAt(0)||w.unshift(""),A&&"/"!==w.join("/").substr(-1)&&w.push("");var _,N=""===w[0]||w[0]&&"/"===w[0].charAt(0);j&&(r.hostname=r.host=N?"":w.length?w.shift():"",(_=!!(r.host&&r.host.indexOf("@")>0)&&r.host.split("@"))&&(r.auth=_.shift(),r.host=r.hostname=_.shift()));return(E=E||r.host&&w.length)&&!N&&w.unshift(""),w.length?r.pathname=w.join("/"):(r.pathname=null,r.path=null),o.isNull(r.pathname)&&o.isNull(r.search)||(r.path=(r.pathname?r.pathname:"")+(r.search?r.search:"")),r.auth=t.auth||r.auth,r.slashes=r.slashes||t.slashes,r.href=r.format(),r},s.prototype.parseHost=function(){var t=this.host,e=a.exec(t);e&&(":"!==(e=e[0])&&(this.port=e.substr(1)),t=t.substr(0,t.length-e.length)),t&&(this.hostname=t)}},function(t,e,r){(function(t,n){var o;/*! https://mths.be/punycode v1.4.1 by @mathias */!function(s){e&&e.nodeType,t&&t.nodeType;var i="object"==typeof n&&n;i.global!==i&&i.window!==i&&i.self;var a,u=2147483647,c=/^xn--/,h=/[^\x20-\x7E]/,f=/[\x2E\u3002\uFF0E\uFF61]/g,l={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},p=Math.floor,d=String.fromCharCode;function m(t){throw new RangeError(l[t])}function v(t,e){for(var r=t.length,n=[];r--;)n[r]=e(t[r]);return n}function y(t,e){var r=t.split("@"),n="";return r.length>1&&(n=r[0]+"@",t=r[1]),n+v((t=t.replace(f,".")).split("."),e).join(".")}function g(t){for(var e,r,n=[],o=0,s=t.length;o=55296&&e<=56319&&o65535&&(e+=d((t-=65536)>>>10&1023|55296),t=56320|1023&t),e+=d(t)})).join("")}function O(t,e){return t+22+75*(t<26)-((0!=e)<<5)}function E(t,e,r){var n=0;for(t=r?p(t/700):t>>1,t+=p(t/e);t>455;n+=36)t=p(t/35);return p(n+36*t/(t+38))}function x(t){var e,r,n,o,s,i,a,c,h,f,l,d=[],v=t.length,y=0,g=128,O=72;for((r=t.lastIndexOf("-"))<0&&(r=0),n=0;n=128&&m("not-basic"),d.push(t.charCodeAt(n));for(o=r>0?r+1:0;o=v&&m("invalid-input"),((c=(l=t.charCodeAt(o++))-48<10?l-22:l-65<26?l-65:l-97<26?l-97:36)>=36||c>p((u-y)/i))&&m("overflow"),y+=c*i,!(c<(h=a<=O?1:a>=O+26?26:a-O));a+=36)i>p(u/(f=36-h))&&m("overflow"),i*=f;O=E(y-s,e=d.length+1,0==s),p(y/e)>u-g&&m("overflow"),g+=p(y/e),y%=e,d.splice(y++,0,g)}return b(d)}function w(t){var e,r,n,o,s,i,a,c,h,f,l,v,y,b,x,w=[];for(v=(t=g(t)).length,e=128,r=0,s=72,i=0;i=e&&lp((u-r)/(y=n+1))&&m("overflow"),r+=(a-e)*y,e=a,i=0;iu&&m("overflow"),l==e){for(c=r,h=36;!(c<(f=h<=s?1:h>=s+26?26:h-s));h+=36)x=c-f,b=36-f,w.push(d(O(f+x%b,0))),c=p(x/b);w.push(d(O(c,0))),s=E(r,y,n==o),r=0,++n}++r,++e}return w.join("")}a={version:"1.4.1",ucs2:{decode:g,encode:b},decode:x,encode:w,toASCII:function(t){return y(t,(function(t){return h.test(t)?"xn--"+w(t):t}))},toUnicode:function(t){return y(t,(function(t){return c.test(t)?x(t.slice(4).toLowerCase()):t}))}},void 0===(o=function(){return a}.call(e,r,e,t))||(t.exports=o)}()}).call(this,r(27)(t),r(28))},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},function(t,e){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(t){"object"==typeof window&&(r=window)}t.exports=r},function(t,e,r){"use strict";t.exports={isString:function(t){return"string"==typeof t},isObject:function(t){return"object"==typeof t&&null!==t},isNull:function(t){return null===t},isNullOrUndefined:function(t){return null==t}}},function(t,e,r){"use strict";e.decode=e.parse=r(31),e.encode=e.stringify=r(32)},function(t,e,r){"use strict";function n(t,e){return Object.prototype.hasOwnProperty.call(t,e)}t.exports=function(t,e,r,s){e=e||"&",r=r||"=";var i={};if("string"!=typeof t||0===t.length)return i;var a=/\+/g;t=t.split(e);var u=1e3;s&&"number"==typeof s.maxKeys&&(u=s.maxKeys);var c=t.length;u>0&&c>u&&(c=u);for(var h=0;h=0?(f=m.substr(0,v),l=m.substr(v+1)):(f=m,l=""),p=decodeURIComponent(f),d=decodeURIComponent(l),n(i,p)?o(i[p])?i[p].push(d):i[p]=[i[p],d]:i[p]=d}return i};var o=Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)}},function(t,e,r){"use strict";var n=function(t){switch(typeof t){case"string":return t;case"boolean":return t?"true":"false";case"number":return isFinite(t)?t:"";default:return""}};t.exports=function(t,e,r,a){return e=e||"&",r=r||"=",null===t&&(t=void 0),"object"==typeof t?s(i(t),(function(i){var a=encodeURIComponent(n(i))+r;return o(t[i])?s(t[i],(function(t){return a+encodeURIComponent(n(t))})).join(e):a+encodeURIComponent(n(t[i]))})).join(e):a?encodeURIComponent(n(a))+r+encodeURIComponent(n(t)):""};var o=Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)};function s(t,e){if(t.map)return t.map(e);for(var r=[],n=0;n0;){var i=n[s],a=e[i];if(a){var u=t[i],c=void 0===u||a(u,i,t);if(!0!==c)throw new o("option "+i+" must be "+c,o.ERR_BAD_OPTION_VALUE)}else if(!0!==r)throw new o("Unknown option "+i,o.ERR_BAD_OPTION)}},validators:s}},function(t,e,r){"use strict";var n=r(2);function o(t){if("function"!=typeof t)throw new TypeError("executor must be a function.");var e;this.promise=new Promise((function(t){e=t}));var r=this;this.promise.then((function(t){if(r._listeners){var e,n=r._listeners.length;for(e=0;e { + if (!data || !data.code || data.receiver != "content") { + return; + } + sendResponse("ok"); + console.log('收到消息:', data); + if (data.code == 'setTokenOk') { + sendToPage(data); + } else if (data.code == 'addBookmark') { + await addBookmark(data); + } +}); + +async function addBookmark (data) { + if (!checkTokenValid(data.token)) { + alert("登陆失效,请登陆后,重试"); + window.open(bookmarkHost + "/manage/sso/auth"); + return; + } + //新增书签 + console.log("新增书签", data.data); + bookmarkInfo = data.data; + addBlockDiv = document.createElement("div"); + addBlockDiv.setAttribute("style", "position:fixed;width:100%;height:100vh;z-index:100000;left:0;top:0;background:rgba(211, 211, 205, 0.8)"); + document.getElementsByTagName("body")[0].appendChild(addBlockDiv); + iframe = document.createElement("iframe"); + iframe.src = bookmarkHost + "/noHead/addBookmark?token=" + data.token; + iframe.setAttribute("style", "width:70%;min-height:60vh;margin-left:15%;margin-top:10vh;padding:0;border:0;border-radius:10px"); + addBlockDiv.appendChild(iframe); +} + +/** + * 发送消息给bg + * @param {*} data + */ +function sendToBg (data) { + data.receiver = "background"; + chrome.runtime.sendMessage(data, response => { + console.log(response); + }); +} + +/** + * 发消息到页面 + * @param {*} data + */ +function sendToPage (data) { + data.receiver = "page"; + window.postMessage(data, "*"); +} + +/** + * 检查token是否有效 + * @param {*} token + * @returns + */ +function checkTokenValid (token) { + try { + if (token && token.trim().length > 0) { + //检查token是否还有效 + let content = JSON.parse(window.atob(token.split(".")[1])); + if (content.exp > Date.now() / 1000) { + return true; + } + } + } catch (err) { + console.error(err); + } + return false; +} \ No newline at end of file diff --git a/浏览器插件/bookmarkBrowserPlugin/static/js/jquery.js b/浏览器插件/bookmarkBrowserPlugin/static/js/jquery.js new file mode 100644 index 0000000..8606c50 --- /dev/null +++ b/浏览器插件/bookmarkBrowserPlugin/static/js/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(g,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,v=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),m={},b=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},w=g.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function C(e,t,n){var r,i,o=(n=n||w).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function T(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector",E=function(e,t){return new E.fn.init(e,t)};function d(e){var t=!!e&&"length"in e&&e.length,n=T(e);return!b(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+R+")"+R+"*"),U=new RegExp(R+"|>"),V=new RegExp(W),X=new RegExp("^"+B+"$"),Q={ID:new RegExp("^#("+B+")"),CLASS:new RegExp("^\\.("+B+")"),TAG:new RegExp("^("+B+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),bool:new RegExp("^(?:"+I+")$","i"),needsContext:new RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,G=/^(?:input|select|textarea|button)$/i,K=/^h\d$/i,J=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+R+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){C()},ae=xe(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{O.apply(t=P.call(d.childNodes),d.childNodes),t[d.childNodes.length].nodeType}catch(e){O={apply:t.length?function(e,t){q.apply(e,P.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,d=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==d&&9!==d&&11!==d)return n;if(!r&&(C(e),e=e||T,E)){if(11!==d&&(u=Z.exec(t)))if(i=u[1]){if(9===d){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return O.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&p.getElementsByClassName&&e.getElementsByClassName)return O.apply(n,e.getElementsByClassName(i)),n}if(p.qsa&&!k[t+" "]&&(!v||!v.test(t))&&(1!==d||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===d&&(U.test(t)||_.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&p.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=A)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+be(l[o]);c=l.join(",")}try{return O.apply(n,f.querySelectorAll(c)),n}catch(e){k(t,!0)}finally{s===A&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>x.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[A]=!0,e}function ce(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)x.attrHandle[n[r]]=t}function de(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pe(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in p=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},C=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:d;return r!=T&&9===r.nodeType&&r.documentElement&&(a=(T=r).documentElement,E=!i(T),d!=T&&(n=T.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),p.scope=ce(function(e){return a.appendChild(e).appendChild(T.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),p.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),p.getElementsByTagName=ce(function(e){return e.appendChild(T.createComment("")),!e.getElementsByTagName("*").length}),p.getElementsByClassName=J.test(T.getElementsByClassName),p.getById=ce(function(e){return a.appendChild(e).id=A,!T.getElementsByName||!T.getElementsByName(A).length}),p.getById?(x.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(x.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),x.find.TAG=p.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):p.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},x.find.CLASS=p.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(p.qsa=J.test(T.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+R+"*(?:value|"+I+")"),e.querySelectorAll("[id~="+A+"-]").length||v.push("~="),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+R+"*name"+R+"*="+R+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+A+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=T.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+R+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(p.matchesSelector=J.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){p.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",W)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=J.test(a.compareDocumentPosition),y=t||J.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!p.sortDetached&&t.compareDocumentPosition(e)===n?e==T||e.ownerDocument==d&&y(d,e)?-1:t==T||t.ownerDocument==d&&y(d,t)?1:u?H(u,e)-H(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==T?-1:t==T?1:i?-1:o?1:u?H(u,e)-H(u,t):0;if(i===o)return de(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?de(a[r],s[r]):a[r]==d?-1:s[r]==d?1:0}),T},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(C(e),p.matchesSelector&&E&&!k[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||p.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){k(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&V.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return b(n)?E.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?E.grep(e,function(e){return e===n!==r}):"string"!=typeof n?E.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(E.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||L,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:j.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof E?t[0]:t,E.merge(this,E.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:w,!0)),k.test(r[1])&&E.isPlainObject(t))for(r in t)b(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=w.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):b(e)?void 0!==n.ready?n.ready(e):e(E):E.makeArray(e,this)}).prototype=E.fn,L=E(w);var q=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}E.fn.extend({has:function(e){var t=E(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,pe=/^$|^module$|\/(?:java|ecma)script/i;le=w.createDocumentFragment().appendChild(w.createElement("div")),(ce=w.createElement("input")).setAttribute("type","radio"),ce.setAttribute("checked","checked"),ce.setAttribute("name","t"),le.appendChild(ce),m.checkClone=le.cloneNode(!0).cloneNode(!0).lastChild.checked,le.innerHTML="",m.noCloneChecked=!!le.cloneNode(!0).lastChild.defaultValue,le.innerHTML="",m.option=!!le.lastChild;var he={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ge(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&S(e,t)?E.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var ye=/<|&#?\w+;/;function me(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),d=[],p=0,h=e.length;p\s*$/g;function ke(e,t){return S(e,"table")&&S(11!==t.nodeType?t:t.firstChild,"tr")&&E(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Le(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function je(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n
",2===lt.childNodes.length),E.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(m.createHTMLDocument?((r=(t=w.implementation.createHTMLDocument("")).createElement("base")).href=w.location.href,t.head.appendChild(r)):t=w),o=!n&&[],(i=k.exec(e))?[t.createElement(i[1])]:(i=me([e],t,o),o&&o.length&&E(o).remove(),E.merge([],i.childNodes)));var r,i,o},E.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=E.css(e,"position"),c=E(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=E.css(e,"top"),u=E.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),b(t)&&(t=t.call(e,n,E.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},E.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){E.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===E.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===E.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=E(e).offset()).top+=E.css(e,"borderTopWidth",!0),i.left+=E.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-E.css(r,"marginTop",!0),left:t.left-i.left-E.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===E.css(e,"position"))e=e.offsetParent;return e||re})}}),E.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;E.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),E.each(["top","left"],function(e,n){E.cssHooks[n]=Me(m.pixelPosition,function(e,t){if(t)return t=Be(e,n),Pe.test(t)?E(e).position()[n]+"px":t})}),E.each({Height:"height",Width:"width"},function(a,s){E.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){E.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?E.css(e,t,i):E.style(e,t,n,i)},s,n?e:void 0,n)}})}),E.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),E.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){E.fn[n]=function(e,t){return 0