first commit
This commit is contained in:
37
README.en.md
Normal file
37
README.en.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# bookmark_sync(书签同步到码云[Gitee](https://gitee.com/))
|
||||
此插件为Chrome浏览器的插件,支持将Chrome浏览器的书签数据同步到码云以及从码云上下载已经保存的书签数据,使得不方便访问谷歌的人能够有另外一种同步浏览器书签的选择。
|
||||
|
||||
#### 1:界面
|
||||

|
||||
|
||||
#### 2.填写说明
|
||||
* access_token:码云的用户授权码(获取方式见[3:access_token获取说明](#3access_token获取说明))
|
||||
* owner:仓库所属空间地址(设置方法如下,只能设置一次)
|
||||

|
||||
* repo:仓库路径(新建仓库方法如下)
|
||||

|
||||
* path:文件的路径(获取如下)
|
||||

|
||||
* branch:分支(通常是master)
|
||||
|
||||
|
||||
#### 3:access_token获取说明
|
||||
###### 3.1:点击头像下拉框的“设置”按钮
|
||||

|
||||
###### 3.2:点击左边菜单的“私人令牌”
|
||||

|
||||
###### 3.3:点击界面右上角的“生成新令牌”按钮
|
||||

|
||||
###### 3.4:输入私人令牌描述,勾选全部的权限,点击提交按钮
|
||||

|
||||
###### 3.5:输入账号密码,然后点击验证按钮
|
||||

|
||||

|
||||
###### 3.6:点击右边的复制按钮(只会出现一次,下次刷新页面就不会出现了,所以一定要及时保存和记住)
|
||||

|
||||

|
||||
|
||||
欢迎交流:新浪微博:@x扬f
|
||||
|
||||
|
||||
|
||||
31
README.md
Normal file
31
README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# bookmark_sync(书签同步到码云[Gitee](https://gitee.com/) 或自建 Gitea)
|
||||
此插件为Chrome浏览器的插件,支持将Chrome浏览器的书签数据同步到码云或自建的Gitea服务器,以及从服务器上下载已经保存的书签数据,使得不方便访问谷歌的人能够有另外一种同步浏览器书签的选择。
|
||||
|
||||
#### 1:界面
|
||||

|
||||
|
||||
#### 2.填写说明
|
||||
* server:Gitea服务器URL(例如:https://gitee.com/api/v5/ 或 https://your-gitea.com/api/v1/)
|
||||
* access_token:码云/Gitea的用户授权码(获取方式见[3:access_token获取说明](#3access_token获取说明))
|
||||
* owner:仓库所属空间地址(设置方法如下,只能设置一次)
|
||||

|
||||
* repo:仓库路径(新建仓库方法如下)
|
||||

|
||||
* path:文件的路径(获取如下)
|
||||

|
||||
* branch:分支(通常是master)
|
||||
|
||||
|
||||
#### 3:access_token获取说明
|
||||
###### 3.1:码云(Gitee)
|
||||
点击头像下拉框的“设置”按钮
|
||||

|
||||
...(其余步骤相同)
|
||||
|
||||
###### 3.2:Gitea(自建服务器)
|
||||
在你的Gitea实例中,进入用户设置 > 应用 > 生成令牌。输入描述,勾选必要权限(如repo),生成并复制令牌。
|
||||
|
||||
欢迎交流:新浪微博:@x扬f
|
||||
|
||||
|
||||
|
||||
231
css/popup.css
Normal file
231
css/popup.css
Normal file
@@ -0,0 +1,231 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Segoe UI", "Lucida Grande", Tahoma, sans-serif;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/** 内容面板样式 */
|
||||
#content {
|
||||
width: 320px;
|
||||
padding: 0 15px 15px 15px;
|
||||
min-height: 300px;
|
||||
background: url(../pic/background.svg) center center no-repeat #fff;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.item {
|
||||
align-content: stretch;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.item .label {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.item .input {
|
||||
border: solid 1px #ccc;
|
||||
float: right;
|
||||
width: 200px;
|
||||
height: 30px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.item .input.error {
|
||||
border: solid 1px #c00;
|
||||
}
|
||||
|
||||
/** 提示框样式 */
|
||||
.toast {
|
||||
display: none;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
text-indent: 110px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
/** 记住按钮样式 */
|
||||
.rememberWrap {
|
||||
padding-left: 80px;
|
||||
}
|
||||
|
||||
.rememberWrap .text {
|
||||
opacity: 1;
|
||||
transition: all .4s ease-in-out;
|
||||
}
|
||||
|
||||
.rememberWrap .text.grey {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.rememberWrap .dot {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin: 0 0 0 10px;
|
||||
width: 25px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.rememberWrap .dot::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
top: 50%;
|
||||
width: 25px;
|
||||
height: 2px;
|
||||
border-radius: 1px;
|
||||
box-shadow: 0 0 1px #000;
|
||||
}
|
||||
|
||||
.rememberWrap .dot::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
top: 50%;
|
||||
left: -5px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 1px #000;
|
||||
background: #fff;
|
||||
transform: translate(0, -50%);
|
||||
transition: all .4s ease-in-out;
|
||||
}
|
||||
|
||||
.rememberWrap .dot.green::after {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
left: 12px;
|
||||
background: #009933;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/** 按钮样式 */
|
||||
.buttonWrap {
|
||||
padding-left: 80px;
|
||||
}
|
||||
|
||||
.button {
|
||||
float: left;
|
||||
width: 80px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.buttonGreen {
|
||||
border: solid 1px #009933;
|
||||
background: #009933;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.buttonGreen:hover {
|
||||
border: solid 1px #006633;
|
||||
background: #006633;
|
||||
}
|
||||
|
||||
.buttonGray {
|
||||
border: solid 1px #ccc;
|
||||
background: #fefefe;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.buttonGray:hover {
|
||||
border: solid 1px #ccc;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
/** 进度条样式 */
|
||||
#loaderWrap {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.loader, .loader:after {
|
||||
border-radius: 50%;
|
||||
width: 10em;
|
||||
height: 10em;
|
||||
}
|
||||
|
||||
.loader {
|
||||
margin: 100px auto;
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
text-indent: -9999em;
|
||||
border-top: 1.1em solid rgba(255, 255, 255, 0.1);
|
||||
border-right: 1.1em solid rgba(255, 255, 255, 0.1);
|
||||
border-bottom: 1.1em solid rgba(255, 255, 255, 0.1);
|
||||
border-left: 1.1em solid green;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
-webkit-animation: load 1.1s infinite linear;
|
||||
animation: load 1.1s infinite linear;
|
||||
}
|
||||
|
||||
@-webkit-keyframes load {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/** 成功样式 */
|
||||
#successWrap {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 10000;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.successIcon {
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
background: url(../pic/right.svg) center center/100% 100% no-repeat #fff;
|
||||
}
|
||||
|
||||
.successText {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
BIN
icon/icon.png
Normal file
BIN
icon/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 613 B |
BIN
icon/icon128.png
Normal file
BIN
icon/icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
BIN
icon/icon16.png
Normal file
BIN
icon/icon16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 352 B |
BIN
icon/icon48.png
Normal file
BIN
icon/icon48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 811 B |
113
js/base64.js
Normal file
113
js/base64.js
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
*
|
||||
* Base64 encode / decode
|
||||
*
|
||||
* @author haitao.tu
|
||||
* @date 2010-04-26
|
||||
* @email tuhaitao@foxmail.com
|
||||
*
|
||||
*/
|
||||
|
||||
function Base64() {
|
||||
|
||||
// private property
|
||||
_keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
|
||||
// public method for encoding
|
||||
this.encode = function(input) {
|
||||
var output = "";
|
||||
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
|
||||
var i = 0;
|
||||
input = _utf8_encode(input);
|
||||
while (i < input.length) {
|
||||
chr1 = input.charCodeAt(i++);
|
||||
chr2 = input.charCodeAt(i++);
|
||||
chr3 = input.charCodeAt(i++);
|
||||
enc1 = chr1 >> 2;
|
||||
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
||||
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
||||
enc4 = chr3 & 63;
|
||||
if (isNaN(chr2)) {
|
||||
enc3 = enc4 = 64;
|
||||
} else if (isNaN(chr3)) {
|
||||
enc4 = 64;
|
||||
}
|
||||
output = output +
|
||||
_keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
|
||||
_keyStr.charAt(enc3) + _keyStr.charAt(enc4);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// public method for decoding
|
||||
this.decode = function(input) {
|
||||
var output = "";
|
||||
var chr1, chr2, chr3;
|
||||
var enc1, enc2, enc3, enc4;
|
||||
var i = 0;
|
||||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
||||
while (i < input.length) {
|
||||
enc1 = _keyStr.indexOf(input.charAt(i++));
|
||||
enc2 = _keyStr.indexOf(input.charAt(i++));
|
||||
enc3 = _keyStr.indexOf(input.charAt(i++));
|
||||
enc4 = _keyStr.indexOf(input.charAt(i++));
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
output = output + String.fromCharCode(chr1);
|
||||
if (enc3 != 64) {
|
||||
output = output + String.fromCharCode(chr2);
|
||||
}
|
||||
if (enc4 != 64) {
|
||||
output = output + String.fromCharCode(chr3);
|
||||
}
|
||||
}
|
||||
output = _utf8_decode(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
// private method for UTF-8 encoding
|
||||
_utf8_encode = function(string) {
|
||||
string = string.replace(/\r\n/g, "\n");
|
||||
var utftext = "";
|
||||
for (var n = 0; n < string.length; n++) {
|
||||
var c = string.charCodeAt(n);
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c);
|
||||
} else if ((c > 127) && (c < 2048)) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
} else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224);
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
|
||||
}
|
||||
return utftext;
|
||||
}
|
||||
|
||||
// private method for UTF-8 decoding
|
||||
_utf8_decode = function(utftext) {
|
||||
var string = "";
|
||||
var i = 0;
|
||||
var c = c1 = c2 = 0;
|
||||
while (i < utftext.length) {
|
||||
c = utftext.charCodeAt(i);
|
||||
if (c < 128) {
|
||||
string += String.fromCharCode(c);
|
||||
i++;
|
||||
} else if ((c > 191) && (c < 224)) {
|
||||
c2 = utftext.charCodeAt(i + 1);
|
||||
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
|
||||
i += 2;
|
||||
} else {
|
||||
c2 = utftext.charCodeAt(i + 1);
|
||||
c3 = utftext.charCodeAt(i + 2);
|
||||
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
|
||||
i += 3;
|
||||
}
|
||||
}
|
||||
return string;
|
||||
}
|
||||
}
|
||||
4
js/jquery-2.1.0.min.js
vendored
Normal file
4
js/jquery-2.1.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
585
js/popup.js
Normal file
585
js/popup.js
Normal file
@@ -0,0 +1,585 @@
|
||||
$(function () {
|
||||
const isFirefox = typeof window.browser !== "undefined";
|
||||
const browserAPI = window.browser || window.chrome;
|
||||
const storageAPI = {
|
||||
get: function (keys, cb) {
|
||||
if (isFirefox) {
|
||||
browserAPI.storage.local.get(keys).then(cb);
|
||||
} else {
|
||||
browserAPI.storage.local.get(keys, cb);
|
||||
}
|
||||
},
|
||||
set: function (obj, cb) {
|
||||
if (isFirefox) {
|
||||
browserAPI.storage.local.set(obj).then(function () {
|
||||
cb && cb();
|
||||
});
|
||||
} else {
|
||||
browserAPI.storage.local.set(obj, cb);
|
||||
}
|
||||
},
|
||||
clear: function (cb) {
|
||||
if (isFirefox) {
|
||||
browserAPI.storage.local.clear().then(function () {
|
||||
cb && cb();
|
||||
});
|
||||
} else {
|
||||
browserAPI.storage.local.clear(cb);
|
||||
}
|
||||
}
|
||||
};
|
||||
const bookmarksAPI = {
|
||||
getTree: function (cb) {
|
||||
if (isFirefox) {
|
||||
browserAPI.bookmarks.getTree().then(cb);
|
||||
} else {
|
||||
browserAPI.bookmarks.getTree(cb);
|
||||
}
|
||||
},
|
||||
getSubTree: function (id, cb) {
|
||||
if (isFirefox) {
|
||||
browserAPI.bookmarks.getSubTree(id).then(cb);
|
||||
} else {
|
||||
browserAPI.bookmarks.getSubTree(id, cb);
|
||||
}
|
||||
},
|
||||
getChildren: function (id, cb) {
|
||||
if (isFirefox) {
|
||||
browserAPI.bookmarks.getChildren(id).then(cb);
|
||||
} else {
|
||||
browserAPI.bookmarks.getChildren(id, cb);
|
||||
}
|
||||
},
|
||||
removeTree: function (id, cb) {
|
||||
if (isFirefox) {
|
||||
browserAPI.bookmarks.removeTree(id).then(cb);
|
||||
} else {
|
||||
browserAPI.bookmarks.removeTree(id, cb);
|
||||
}
|
||||
},
|
||||
create: function (options, cb) {
|
||||
if (isFirefox) {
|
||||
browserAPI.bookmarks.create(options).then(cb);
|
||||
} else {
|
||||
browserAPI.bookmarks.create(options, cb);
|
||||
}
|
||||
}
|
||||
};
|
||||
sync = {
|
||||
/**
|
||||
* 初始化函数
|
||||
*/
|
||||
init: function () {
|
||||
let that = this;
|
||||
that.base64 = new Base64();
|
||||
// 码云的API根地址(现在从表单读取)
|
||||
that.baseUrl = $("#tbServer").val().endsWith('/') ? $("#tbServer").val() : $("#tbServer").val() + '/';
|
||||
that.getCacheUserInfo();
|
||||
that.bindEvent();
|
||||
},
|
||||
/**
|
||||
* 表单收集
|
||||
*/
|
||||
createFormObj: function () {
|
||||
let formObj = {};
|
||||
formObj.server = $("#tbServer").val();
|
||||
formObj.access_token = $("#tbAccessToken").val();
|
||||
formObj.owner = $("#tbOwner").val();
|
||||
formObj.repo = $("#tbRepo").val();
|
||||
formObj.path = $("#tbPath").val();
|
||||
formObj.branch = $("#tbBranch").val();
|
||||
return formObj;
|
||||
},
|
||||
/**
|
||||
* 弹出窗开始时从缓存读取已经存储的数据
|
||||
*/
|
||||
getCacheUserInfo: function () {
|
||||
let that = this;
|
||||
// 从缓存中读取数据并设置到表单中
|
||||
storageAPI.get(["server", "access_token", "owner", "repo", "path", "branch", "remember"], function (obj) {
|
||||
// 插件最开始的状态是记住开关打开了,但是chrome本地缓存中还没有设置值
|
||||
if (!obj.remember) {
|
||||
if ($("#rememberDot").hasClass("green")) {
|
||||
that.saveRememberState("on");
|
||||
} else {
|
||||
that.saveRememberState("off");
|
||||
}
|
||||
} else if (obj.remember == "on") {
|
||||
$("#rememberDot").addClass("green");
|
||||
} else {
|
||||
$("#rememberDot").removeClass("green");
|
||||
}
|
||||
if ($("#rememberDot").hasClass("green")) {
|
||||
if (obj.server || obj.access_token || obj.owner || obj.repo || obj.path || obj.branch) {
|
||||
$("#tbServer").val(obj.server || "https://gitee.com/api/v5/");
|
||||
that.baseUrl = ($("#tbServer").val().endsWith('/') ? $("#tbServer").val() : $("#tbServer").val() + '/');
|
||||
$("#tbAccessToken").val(obj.access_token);
|
||||
$("#tbOwner").val(obj.owner);
|
||||
$("#tbRepo").val(obj.repo);
|
||||
$("#tbPath").val(obj.path);
|
||||
$("#tbBranch").val(obj.branch);
|
||||
that.rememberOn();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
rememberOn: function () {
|
||||
let that = this;
|
||||
$("#rememberText").addClass("grey");
|
||||
$("#rememberDot").addClass("green");
|
||||
that.saveRememberState("on");
|
||||
},
|
||||
rememberOff: function () {
|
||||
let that = this;
|
||||
$("#rememberText").removeClass("grey");
|
||||
$("#rememberDot").removeClass("green");
|
||||
that.saveRememberState("off");
|
||||
},
|
||||
/**
|
||||
* 清除用户信息
|
||||
*/
|
||||
clearUserInfo: function () {
|
||||
storageAPI.clear(function () {
|
||||
console.log('clear user info success');
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 保存用户信息
|
||||
*/
|
||||
saveUserInfo: function () {
|
||||
let that = this;
|
||||
let formObj = that.createFormObj();
|
||||
storageAPI.set(formObj, function () {
|
||||
console.log('save user info success');
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 保存记住按钮的状态
|
||||
* state:on(记住),off(不记住)
|
||||
*/
|
||||
saveRememberState: function (state) {
|
||||
let rememberObj = {};
|
||||
rememberObj.remember = state;
|
||||
storageAPI.set(rememberObj, function () {
|
||||
console.log('save remember state success');
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 获取书签栏根节点 ID
|
||||
*/
|
||||
getBookmarkBarId: function (fn) {
|
||||
let that = this;
|
||||
if (!isFirefox) {
|
||||
fn("1");
|
||||
return;
|
||||
}
|
||||
bookmarksAPI.getTree(function (tree) {
|
||||
let root = Array.isArray(tree) ? tree[0] : tree;
|
||||
if (!root || !root.children) {
|
||||
fn("toolbar_____");
|
||||
return;
|
||||
}
|
||||
let toolbar = root.children.find(function (node) {
|
||||
return node.id === "toolbar_____" || node.title === "Bookmarks Toolbar" || (node.title && node.title.toLowerCase().indexOf("toolbar") !== -1);
|
||||
});
|
||||
fn(toolbar ? toolbar.id : "toolbar_____");
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 获取浏览器的书签数据
|
||||
*/
|
||||
getBookmarks: function (fn) {
|
||||
let that = this;
|
||||
that.getBookmarkBarId(function (rootId) {
|
||||
bookmarksAPI.getSubTree(rootId, function (bookmarks) {
|
||||
console.log('Retrieved bookmarks:', bookmarks);
|
||||
fn(bookmarks);
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 清空书签栏文件夹(不能直接清除根书签栏,只能遍历一个一个文件夹清除)
|
||||
*/
|
||||
emptyBookmarks: function (fn) {
|
||||
let that = this;
|
||||
that.getBookmarkBarId(function (rootId) {
|
||||
bookmarksAPI.getChildren(rootId, function (children) {
|
||||
// 需要判断书签栏是否原来就是空的
|
||||
if (!children || children.length <= 0) {
|
||||
fn();
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let item = children[i];
|
||||
bookmarksAPI.removeTree(item.id, function () {
|
||||
// 判断是不是已经删除到最后一个了,是的话就调用回调函数
|
||||
if (i == (children.length - 1)) {
|
||||
fn();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
bindEvent: function () {
|
||||
let that = this;
|
||||
// 记住按钮点击事件
|
||||
$("#rememberDot").on("click", function () {
|
||||
// 先判断当前记住按钮的状态,假如已经记住则不再记住,清空缓存
|
||||
if ($("#rememberDot").hasClass("green")) {
|
||||
that.clearUserInfo();
|
||||
that.rememberOff();
|
||||
} else {
|
||||
// 获取表单数据,有则保存到缓存中
|
||||
let formObj = that.createFormObj();
|
||||
let existRes = that.checkFormExist(formObj);
|
||||
if (existRes) {
|
||||
that.saveUserInfo();
|
||||
}
|
||||
that.rememberOn();
|
||||
}
|
||||
});
|
||||
// 输入框的输入事件,用来记忆表单数据
|
||||
$(".input").on("input", function () {
|
||||
if ($("#rememberDot").hasClass("green")) {
|
||||
that.saveUserInfo();
|
||||
that.rememberOn();
|
||||
}
|
||||
});
|
||||
// 点击上传按钮
|
||||
$("#btnUpload").on("click", function () {
|
||||
console.log('Upload button clicked');
|
||||
let formObj = that.createFormObj();
|
||||
console.log('Form object:', formObj);
|
||||
let formRes = that.checkForm(formObj);
|
||||
if (formRes != "ok") {
|
||||
$("#toast").text(formRes).show();
|
||||
return;
|
||||
}
|
||||
// 更新baseUrl
|
||||
that.baseUrl = formObj.server.endsWith('/') ? formObj.server : formObj.server + '/';
|
||||
console.log('Base URL set to:', that.baseUrl);
|
||||
// 假如记住按钮已经点击了
|
||||
if ($("#rememberDot").hasClass("green")) {
|
||||
that.saveUserInfo();
|
||||
that.rememberOn();
|
||||
} else {
|
||||
that.clearUserInfo();
|
||||
that.rememberOff();
|
||||
}
|
||||
$("#loaderWrap").show();
|
||||
// 从码云上获取书签内容
|
||||
console.log('Calling getGit');
|
||||
that.getGit(formObj, function (getState, getRes) {
|
||||
console.log('getGit callback:', getState, getRes);
|
||||
// 获取浏览器的书签
|
||||
that.getBookmarks(function (bookmarks) {
|
||||
console.log('getBookmarks callback');
|
||||
// 获取码云的内容存在则更新文件,否则更新文件
|
||||
if (getState) {
|
||||
let sha = getRes.sha;
|
||||
that.updateGit(formObj, bookmarks, sha, function (updState, updRes) {
|
||||
// 更新成功,则提示,否则提示错误
|
||||
if (updState) {
|
||||
$("#successWrap").show();
|
||||
$(".successText").text("Upload Success");
|
||||
} else {
|
||||
$("#toast").text(updRes).show();
|
||||
}
|
||||
$("#loaderWrap").hide();
|
||||
});
|
||||
} else {
|
||||
that.createGit(formObj, bookmarks, function (createState, createRes) {
|
||||
// 创建成功,则提示,否则提示错误
|
||||
if (createState) {
|
||||
$("#successWrap").show();
|
||||
$(".successText").text("Upload Success");
|
||||
} else {
|
||||
$("#toast").text(createRes).show();
|
||||
}
|
||||
$("#loaderWrap").hide();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
// 点击下载按钮事件
|
||||
$("#btnDownload").on("click", function () {
|
||||
let formObj = that.createFormObj();
|
||||
let formRes = that.checkForm(formObj);
|
||||
if (formRes != "ok") {
|
||||
$("#toast").text(formRes).show();
|
||||
return;
|
||||
}
|
||||
// 更新baseUrl
|
||||
that.baseUrl = formObj.server.endsWith('/') ? formObj.server : formObj.server + '/';
|
||||
// 假如记住按钮已经点击了
|
||||
if ($("#rememberDot").hasClass("green")) {
|
||||
that.saveUserInfo();
|
||||
that.rememberOn();
|
||||
} else {
|
||||
that.clearUserInfo();
|
||||
that.rememberOff();
|
||||
}
|
||||
$("#loaderWrap").show();
|
||||
// 从码云上获取书签内容
|
||||
that.getGit(formObj, function (getState, getRes) {
|
||||
// 获取码云的内容存在则创建文件,否则提示文件不存在
|
||||
if (getState) {
|
||||
let gitContent = that.base64.decode(getRes["content"]);
|
||||
let bookmarksParent = JSON.parse(gitContent);
|
||||
let bookmarks = bookmarksParent[0];
|
||||
// 清空书签栏
|
||||
that.emptyBookmarks(function () {
|
||||
// 等所有书签都清空后开始设置书签
|
||||
that.getBookmarkBarId(function (rootId) {
|
||||
that.setBookmarks(rootId, bookmarks);
|
||||
$("#successWrap").show();
|
||||
$(".successText").text("Download Success");
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$("#toast").text("Git文件不存在").show();
|
||||
}
|
||||
$("#loaderWrap").hide();
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 将git获取的内容设置到浏览器书签上去
|
||||
*/
|
||||
setBookmarks: function (parentId, bookmarks) {
|
||||
let that = this;
|
||||
if (!bookmarks.children || bookmarks.children.length <= 0) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < bookmarks.children.length; i++) {
|
||||
let item = bookmarks.children[i];
|
||||
bookmarksAPI.create({
|
||||
parentId: parentId,
|
||||
index: item.index,
|
||||
title: item.title,
|
||||
url: item.url
|
||||
}, function (res) {
|
||||
// 判断假如还有子元素则递归调用自身添加
|
||||
if (item.children && item.children.length > 0) {
|
||||
that.setBookmarks(res.id, item);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 校验表单
|
||||
*/
|
||||
checkForm: function (formObj) {
|
||||
if (!formObj.server) {
|
||||
$("#tbServer").addClass('error');
|
||||
return 'server is required';
|
||||
}
|
||||
$("#tbServer").removeClass('error');
|
||||
|
||||
if (!formObj.access_token) {
|
||||
$("#tbAccessToken").addClass('error');
|
||||
return 'access_token is required';
|
||||
}
|
||||
$("#tbAccessToken").removeClass('error');
|
||||
|
||||
if (!formObj.owner) {
|
||||
$("#tbOwner").addClass('error');
|
||||
return 'owner is required';
|
||||
}
|
||||
$("#tbOwner").removeClass('error');
|
||||
|
||||
if (!formObj.repo) {
|
||||
$("#tbRepo").addClass('error');
|
||||
return 'repo is required';
|
||||
}
|
||||
$("#tbRepo").removeClass('error');
|
||||
|
||||
if (!formObj.path) {
|
||||
$("#tbPath").addClass('error');
|
||||
return 'path is required';
|
||||
}
|
||||
$("#tbPath").removeClass('error');
|
||||
|
||||
if (!formObj.branch) {
|
||||
$("#tbBranch").addClass('error');
|
||||
return 'branch is required';
|
||||
}
|
||||
$("#tbBranch").removeClass('error');
|
||||
return "ok";
|
||||
},
|
||||
/**
|
||||
* 查看表单是否至少有一个值
|
||||
*/
|
||||
checkFormExist: function (formObj) {
|
||||
if (formObj.server != '') {
|
||||
return true;
|
||||
}
|
||||
if (formObj.access_token != '') {
|
||||
return true;
|
||||
}
|
||||
if (formObj.owner != '') {
|
||||
return true;
|
||||
}
|
||||
if (formObj.repo != '') {
|
||||
return true;
|
||||
}
|
||||
if (formObj.path != '') {
|
||||
return true;
|
||||
}
|
||||
if (formObj.branch != '') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* 获取Gitee上的数据
|
||||
* curl -X GET --header 'Content-Type: application/json;charset=UTF-8' 'https://gitee.com/api/v5/repos/xieyf00/chrome/contents/bookmark/bookmark-4.json?access_token=16c6176faea12d4b2dba667872d9b21c&ref=master'
|
||||
*/
|
||||
getGit: function (formObj, fn) {
|
||||
let that = this;
|
||||
let getUrl = that.baseUrl + "repos/" + formObj.owner + "/" + formObj.repo + "/contents/" + formObj.path + "?ref=" + formObj.branch;
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: getUrl,
|
||||
headers: {
|
||||
'Authorization': 'token ' + formObj.access_token
|
||||
},
|
||||
crossDomain: true,
|
||||
success: function (res) {
|
||||
fn && fn(true, res);
|
||||
},
|
||||
error: function (xhr, textStatus, error) {
|
||||
fn && fn(false, error);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 创建Git文件
|
||||
* curl -X POST --header 'Content-Type: application/json;charset=UTF-8' 'https://gitee.com/api/v5/repos/xieyf00/chrome/contents/bookmark/bookmark-copy.json' -d '{"access_token":"16c6176faea12d4b2dba667872d9b21c","content":"xxx","message":"xieyangfan commit","branch":"master"}'
|
||||
* createContentRaw:文件内容, 要用 base64 编码
|
||||
*/
|
||||
createGit: function (formObj, createContentRaw, fn) {
|
||||
let that = this;
|
||||
if (createContentRaw == null) {
|
||||
console.error('createGit missing contentRaw');
|
||||
fn && fn(false, 'No bookmark data to upload');
|
||||
return;
|
||||
}
|
||||
console.log('createContentRaw:', createContentRaw);
|
||||
let jsonStr = JSON.stringify(createContentRaw);
|
||||
console.log('JSON string:', jsonStr);
|
||||
let createContent = that.base64.encode(jsonStr);
|
||||
console.log('Base64 encoded:', createContent);
|
||||
// Git提交信息
|
||||
let createMessage = "Chrome Browser Bookmark Created" + new Date();
|
||||
// 构建
|
||||
let createData = {};
|
||||
createData.access_token = formObj.access_token;
|
||||
createData.content = createContent;
|
||||
createData.message = createMessage;
|
||||
createData.branch = formObj.branch;
|
||||
let createUrl = that.baseUrl + "repos/" + formObj.owner + "/" + formObj.repo + "/contents/" + formObj.path;
|
||||
console.log('Create URL:', createUrl);
|
||||
console.log('Create data:', createData);
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: createUrl,
|
||||
contentType: 'application/json',
|
||||
headers: {
|
||||
'Authorization': 'token ' + formObj.access_token
|
||||
},
|
||||
data: JSON.stringify(createData),
|
||||
crossDomain: true,
|
||||
success: function (res) {
|
||||
console.log('Create success:', res);
|
||||
fn && fn(true, res);
|
||||
},
|
||||
error: function (xhr, textStatus, error) {
|
||||
console.log('Create error:', xhr, textStatus, error);
|
||||
fn && fn(false, error);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 更新Git文件
|
||||
* curl -X PUT --header 'Content-Type: application/json;charset=UTF-8' 'https://gitee.com/api/v5/repos/xieyf00/chrome/contents/bookmark/bookmark-6.json' -d '{"access_token":"16c6176faea12d4b2dba667872d9b21c","content":"InhpZXlhbmdmYW4gMjAxOTA3MjQgMTU0OCI=","sha":"dce85293664c50792d2ebcfc4ede23bf3e1197c2","message":"xieyangfan 20190724 1736","branch":"master"}'
|
||||
* updateContentRaw:文件内容, 要用 base64 编码
|
||||
* sha:文件的 Blob SHA,可通过 [获取仓库具体路径下的内容] API 获取
|
||||
*/
|
||||
updateGit: function (formObj, updateContentRaw, sha, fn) {
|
||||
let that = this;
|
||||
if (updateContentRaw == null) {
|
||||
console.error('updateGit missing updateContentRaw');
|
||||
fn && fn(false, 'No bookmark data to update');
|
||||
return;
|
||||
}
|
||||
console.log('updateContentRaw:', updateContentRaw);
|
||||
let jsonStr = JSON.stringify(updateContentRaw);
|
||||
console.log('JSON string:', jsonStr);
|
||||
let updateContent = that.base64.encode(jsonStr);
|
||||
console.log('Base64 encoded:', updateContent);
|
||||
// Git提交信息
|
||||
let updateMessage = "Chrome Browser Bookmark Updated" + new Date();
|
||||
// 构建
|
||||
let updateData = {};
|
||||
updateData.access_token = formObj.access_token;
|
||||
updateData.content = updateContent;
|
||||
updateData.message = updateMessage;
|
||||
updateData.sha = sha;
|
||||
updateData.branch = formObj.branch;
|
||||
let updateUrl = that.baseUrl + "repos/" + formObj.owner + "/" + formObj.repo + "/contents/" + formObj.path;
|
||||
console.log('Update URL:', updateUrl);
|
||||
console.log('Update data:', updateData);
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
url: updateUrl,
|
||||
contentType: 'application/json',
|
||||
headers: {
|
||||
'Authorization': 'token ' + formObj.access_token
|
||||
},
|
||||
data: JSON.stringify(updateData),
|
||||
crossDomain: true,
|
||||
success: function (res) {
|
||||
console.log('Update success:', res);
|
||||
fn && fn(true, res);
|
||||
},
|
||||
error: function (xhr, textStatus, error) {
|
||||
console.log('Update error:', xhr, textStatus, error);
|
||||
fn && fn(false, error);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 删除Git文件
|
||||
* curl -X DELETE --header 'Content-Type: application/json;charset=UTF-8' 'https://gitee.com/api/v5/repos/xieyf00/chrome/contents/bookmark/bookmark-6.json?access_token=16c6176faea12d4b2dba667872d9b21c&sha=dce85293664c50792d2ebcfc4ede23bf3e1197c2&message=Chrome%20Browser%20Bookmark%20Deleted&branch=master'
|
||||
* sha:文件的 Blob SHA,可通过 [获取仓库具体路径下的内容] API 获取
|
||||
*/
|
||||
deleteGit: function (formObj, sha, fn) {
|
||||
let that = this;
|
||||
// Git提交信息
|
||||
let deleteMessage = "Chrome Browser Bookmark Deleted" + new Date();
|
||||
// 构建
|
||||
let deleteData = {};
|
||||
deleteData.access_token = formObj.access_token;
|
||||
deleteData.message = deleteMessage;
|
||||
deleteData.sha = sha;
|
||||
deleteData.branch = formObj.branch;
|
||||
let deleteUrl = that.baseUrl + "repos/" + formObj.owner + "/" + formObj.repo + "/contents/" + formObj.path;
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
url: deleteUrl,
|
||||
data: deleteData,
|
||||
crossDomain: true,
|
||||
success: function (res) {
|
||||
fn && fn(true, res);
|
||||
},
|
||||
error: function (xhr, textStatus, error) {
|
||||
fn && fn(false, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
sync.init();
|
||||
});
|
||||
25
manifest.json
Normal file
25
manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"browser_action": {
|
||||
"default_icon": "./icon/icon.png",
|
||||
"default_popup": "popup.html",
|
||||
"default_title": "书签同步"
|
||||
},
|
||||
"description": "上传/下载书签信息",
|
||||
"icons": {
|
||||
"128": "./icon/icon128.png",
|
||||
"16": "./icon/icon16.png",
|
||||
"48": "./icon/icon48.png"
|
||||
},
|
||||
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Pbc87Fr52I1EkFIpNer8fhoBOZD/WZhtQ4hlljOdu9DjZ+8CrL7WiDsWJjD/OeWw+1L0/vTe0hFCVmkb2FHtzfPIt134nlCrbY0O6dABvLPSqZ1+qiG1R9hYtjcpCY5xutM7walHPh48Zb4Mmap6gxUzZtbclZd0dTa9SycLzQrmlOZI7O2u84+pdI8VRuVxDOauwl38QmuwPfz6xIxsRIEya8DCnSdH3btlDUp2uyf8QukwsQWEwnXKEjZFQrV/uiyF8insc2EktofOVcOQ6bAGRGhxZjiVnwXYJ2TUJkXfruMYUrs13nVik3T7VNzaQfvXtWdRKJoqJ+4sugcowIDAQAB",
|
||||
"manifest_version": 2,
|
||||
"name": "书签同步",
|
||||
"permissions": [ "webRequest", "webRequestBlocking", "storage", "bookmarks", "*://*/*" ],
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "bookmark_sync@local",
|
||||
"strict_min_version": "91.0"
|
||||
}
|
||||
},
|
||||
"update_url": "https://clients2.google.com/service/update2/crx",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
1
pic/background.svg
Normal file
1
pic/background.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1565150251746" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3987" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><defs><style type="text/css"></style></defs><path d="M793.490046 949.181991 511.428995 743.862765 229.368968 949.181991l114.072035-335.980078L63.451124 407.879618l335.986217 0L511.428995 73.969689l111.992677 333.909929 335.986217 0L679.418011 613.201913 793.490046 949.181991zM511.428995 702.383048l215.693504 159.699212-85.029582-261.32375 217.763652-159.695119L598.538959 441.063391l-87.10894-263.394922-87.10894 263.394922L163.002444 441.063391 380.766097 600.757486l-85.029582 261.32375L511.428995 702.383048z" p-id="3988" fill="#cdcdcd"></path></svg>
|
||||
|
After Width: | Height: | Size: 877 B |
10
pic/right.svg
Normal file
10
pic/right.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1504406023517" class="icon" style="" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="995"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path d="M407.36 807.552a16 16 0 0 1-12.16-5.632L147.84 512a16 16 0 1 1 24.32-20.8l234.944 275.2 444.544-544a16 16 0 1 1 24.768 20.224l-456.704 559.04a16 16 0 0 1-12.224 5.888z" p-id="996" fill="#009933"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 632 B |
85
popup.html
Normal file
85
popup.html
Normal file
@@ -0,0 +1,85 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
|
||||
<meta charset="UTF-8">
|
||||
<title>书签同步码云</title>
|
||||
<link rel="stylesheet" href="css/popup.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="content">
|
||||
<div class="container">
|
||||
<div class="item">
|
||||
<label for="tbServer" class="label">server</label>
|
||||
<input type="text" class="input" id="tbServer" placeholder="Gitea服务器URL (e.g., https://gitee.com/api/v5/)"
|
||||
value="https://gitee.com/api/v5/"/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<label for="tbAccessToken" class="label">access_token</label>
|
||||
<input type="password" class="input" id="tbAccessToken" placeholder="用户授权码"
|
||||
value=""/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<label for="tbOwner" class="label">owner</label>
|
||||
<input type="text" class="input" id="tbOwner" placeholder="仓库所属空间地址"
|
||||
value=""/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<label for="tbRepo" class="label">repo</label>
|
||||
<input type="text" class="input" id="tbRepo" placeholder="仓库路径"
|
||||
value=""/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<label for="tbPath" class="label">path</label>
|
||||
<input type="text" class="input" id="tbPath" placeholder="文件的路径"
|
||||
value=""/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<label for="tbBranch" class="label">branch</label>
|
||||
<input type="text" class="input" id="tbBranch" placeholder="分支(通常是master)"
|
||||
value=""/>
|
||||
</div>
|
||||
|
||||
<!-- toast -->
|
||||
<div class="item">
|
||||
<div class="toast" id="toast"></div>
|
||||
</div>
|
||||
|
||||
<!-- remember -->
|
||||
<div class="item">
|
||||
<div class="rememberWrap">
|
||||
<span class="text grey" id="rememberText">Remember</span>
|
||||
<span class="dot green" id="rememberDot"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 按钮 -->
|
||||
<div class="item">
|
||||
<div class="buttonWrap">
|
||||
<button class="button buttonGreen" id="btnUpload">Upload</button>
|
||||
<button class="button buttonGray" id="btnDownload">Download</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- loader -->
|
||||
<div id="loaderWrap">
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
|
||||
<!-- success -->
|
||||
<div id="successWrap">
|
||||
<div class="successIcon"></div>
|
||||
<div class="successText"></div>
|
||||
</div>
|
||||
|
||||
<!-- scripts -->
|
||||
<script src="js/jquery-2.1.0.min.js"></script>
|
||||
<script src="js/base64.js"></script>
|
||||
<script src="js/popup.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user