first commit

This commit is contained in:
Jesse
2026-04-19 19:58:17 +08:00
commit 40ade69297
14 changed files with 1122 additions and 0 deletions

37
README.en.md Normal file
View File

@@ -0,0 +1,37 @@
# bookmark_sync书签同步到码云[Gitee](https://gitee.com/)
此插件为Chrome浏览器的插件支持将Chrome浏览器的书签数据同步到码云以及从码云上下载已经保存的书签数据使得不方便访问谷歌的人能够有另外一种同步浏览器书签的选择。
#### 1:界面
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/103915_9cf93b07_4859264.png "屏幕截图.png")
#### 2.填写说明
* access_token码云的用户授权码获取方式见[3:access_token获取说明](#3access_token获取说明)
* owner仓库所属空间地址设置方法如下只能设置一次
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/111658_15a42660_4859264.png "屏幕截图.png")
* repo仓库路径新建仓库方法如下
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/111855_c71b934f_4859264.png "屏幕截图.png")
* path文件的路径获取如下
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/111950_1f8806b3_4859264.png "屏幕截图.png")
* branch分支(通常是master)
#### 3:access_token获取说明
###### 3.1:点击头像下拉框的“设置”按钮
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/104632_64161a1c_4859264.png "屏幕截图.png")
###### 3.2:点击左边菜单的“私人令牌”
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/104732_a3377c1d_4859264.png "屏幕截图.png")
###### 3.3:点击界面右上角的“生成新令牌”按钮
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/104805_3632f013_4859264.png "屏幕截图.png")
###### 3.4:输入私人令牌描述,勾选全部的权限,点击提交按钮
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/105041_f3c38794_4859264.png "屏幕截图.png")
###### 3.5:输入账号密码,然后点击验证按钮
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/105234_e9154306_4859264.png "屏幕截图.png")
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/105255_4e4cfd6d_4859264.png "屏幕截图.png")
###### 3.6:点击右边的复制按钮(只会出现一次,下次刷新页面就不会出现了,所以一定要及时保存和记住)
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/105523_b31fd892_4859264.png "屏幕截图.png")
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/105654_d57107d6_4859264.png "屏幕截图.png")
欢迎交流:新浪微博:@x扬f

31
README.md Normal file
View File

@@ -0,0 +1,31 @@
# bookmark_sync书签同步到码云[Gitee](https://gitee.com/) 或自建 Gitea
此插件为Chrome浏览器的插件支持将Chrome浏览器的书签数据同步到码云或自建的Gitea服务器以及从服务器上下载已经保存的书签数据使得不方便访问谷歌的人能够有另外一种同步浏览器书签的选择。
#### 1:界面
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/103915_9cf93b07_4859264.png "屏幕截图.png")
#### 2.填写说明
* serverGitea服务器URL例如https://gitee.com/api/v5/ 或 https://your-gitea.com/api/v1/
* access_token码云/Gitea的用户授权码获取方式见[3:access_token获取说明](#3access_token获取说明)
* owner仓库所属空间地址设置方法如下只能设置一次
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/111658_15a42660_4859264.png "屏幕截图.png")
* repo仓库路径新建仓库方法如下
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/111855_c71b934f_4859264.png "屏幕截图.png")
* path文件的路径获取如下
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/111950_1f8806b3_4859264.png "屏幕截图.png")
* branch分支(通常是master)
#### 3:access_token获取说明
###### 3.1:码云Gitee
点击头像下拉框的“设置”按钮
![输入图片说明](https://images.gitee.com/uploads/images/2019/0808/104632_64161a1c_4859264.png "屏幕截图.png")
...(其余步骤相同)
###### 3.2:Gitea自建服务器
在你的Gitea实例中进入用户设置 > 应用 > 生成令牌。输入描述勾选必要权限如repo生成并复制令牌。
欢迎交流:新浪微博:@x扬f

231
css/popup.css Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

BIN
icon/icon128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
icon/icon16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 B

BIN
icon/icon48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

113
js/base64.js Normal file
View 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

File diff suppressed because one or more lines are too long

585
js/popup.js Normal file
View 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');
});
},
/**
* 保存记住按钮的状态
* stateon记住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
View 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
View 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
View 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
View 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>