mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-11-23 18:17:20 +00:00
Add a GitHub saver
Fixes #3890 I think it would be useful to have a simple tutorial for setting up saving via GitHub pages.
This commit is contained in:
parent
662ae91067
commit
aa5eaa98fc
@ -89,6 +89,14 @@ Saving/DownloadSaver/Hint: These settings apply to the HTML5-compatible download
|
|||||||
Saving/General/Caption: General
|
Saving/General/Caption: General
|
||||||
Saving/General/Hint: These settings apply to all the loaded savers
|
Saving/General/Hint: These settings apply to all the loaded savers
|
||||||
Saving/Hint: Settings used for saving the entire TiddlyWiki as a single file via a saver module
|
Saving/Hint: Settings used for saving the entire TiddlyWiki as a single file via a saver module
|
||||||
|
Saving/GitHub/Branch: Target branch for saving (defaults to `master`)
|
||||||
|
Saving/GitHub/Caption: ~GitHub Saver
|
||||||
|
Saving/GitHub/Description: These settings are only used when saving to ~GitHub
|
||||||
|
Saving/GitHub/Filename: Filename of target file (e.g. `index.html`)
|
||||||
|
Saving/GitHub/Password: Password, OAUTH token, or personal access token
|
||||||
|
Saving/GitHub/Path: Path to target file (e.g. `/wiki/`)
|
||||||
|
Saving/GitHub/Repo: Target repository (e.g. `Jermolene/TiddlyWiki5`)
|
||||||
|
Saving/GitHub/UserName: Username
|
||||||
Saving/TiddlySpot/Advanced/Heading: Advanced Settings
|
Saving/TiddlySpot/Advanced/Heading: Advanced Settings
|
||||||
Saving/TiddlySpot/BackupDir: Backup Directory
|
Saving/TiddlySpot/BackupDir: Backup Directory
|
||||||
Saving/TiddlySpot/Backups: Backups
|
Saving/TiddlySpot/Backups: Backups
|
||||||
|
117
core/modules/savers/github.js
Normal file
117
core/modules/savers/github.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*\
|
||||||
|
title: $:/core/modules/savers/github.js
|
||||||
|
type: application/javascript
|
||||||
|
module-type: saver
|
||||||
|
|
||||||
|
Saves wiki by pushing a commit to the GitHub v3 REST API
|
||||||
|
|
||||||
|
\*/
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
/*jslint node: true, browser: true */
|
||||||
|
/*global $tw: false */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var base64utf8 = require("$:/core/modules/utils/base64-utf8/base64-utf8.module.js");
|
||||||
|
|
||||||
|
/*
|
||||||
|
Select the appropriate saver module and set it up
|
||||||
|
*/
|
||||||
|
var GitHubSaver = function(wiki) {
|
||||||
|
this.wiki = wiki;
|
||||||
|
};
|
||||||
|
|
||||||
|
GitHubSaver.prototype.save = function(text,method,callback) {
|
||||||
|
var self = this,
|
||||||
|
username = this.wiki.getTiddlerText("$:/GitHub/Username"),
|
||||||
|
password = $tw.utils.getPassword("github"),
|
||||||
|
repo = this.wiki.getTiddlerText("$:/GitHub/Repo"),
|
||||||
|
path = this.wiki.getTiddlerText("$:/GitHub/Path"),
|
||||||
|
filename = this.wiki.getTiddlerText("$:/GitHub/Filename"),
|
||||||
|
branch = this.wiki.getTiddlerText("$:/GitHub/Branch") || "master",
|
||||||
|
headers = {
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
"Content-Type": "application/json;charset=UTF-8",
|
||||||
|
"Authorization": "Basic " + window.btoa(username + ":" + password)
|
||||||
|
};
|
||||||
|
// Make sure the path start and ends with a slash
|
||||||
|
if(path.substring(0,1) !== "/") {
|
||||||
|
path = "/" + path;
|
||||||
|
}
|
||||||
|
if(path.substring(path.length - 1) !== "/") {
|
||||||
|
path = path + "/";
|
||||||
|
}
|
||||||
|
// Compose the base URI
|
||||||
|
var uri = "https://api.github.com/repos/" + repo + "/contents" + path;
|
||||||
|
// Bail if we don't have everything we need
|
||||||
|
if(!username || !password || !repo || !path || !filename) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Perform a get request to get the details (inc shas) of files in the same path as our file
|
||||||
|
$tw.utils.httpRequest({
|
||||||
|
url: uri,
|
||||||
|
type: "GET",
|
||||||
|
headers: headers,
|
||||||
|
data: {
|
||||||
|
ref: branch
|
||||||
|
},
|
||||||
|
callback: function(err,getResponseDataJson,xhr) {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
var getResponseData = JSON.parse(getResponseDataJson),
|
||||||
|
sha = "";
|
||||||
|
$tw.utils.each(getResponseData,function(details) {
|
||||||
|
if(details.name === filename) {
|
||||||
|
sha = details.sha;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var data = {
|
||||||
|
message: "Saved by TiddlyWiki",
|
||||||
|
content: base64utf8.base64.encode.call(base64utf8,text),
|
||||||
|
branch: branch,
|
||||||
|
sha: sha
|
||||||
|
};
|
||||||
|
// Perform a PUT request to save the file
|
||||||
|
$tw.utils.httpRequest({
|
||||||
|
url: uri + filename,
|
||||||
|
type: "PUT",
|
||||||
|
headers: headers,
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
callback: function(err,putResponseDataJson,xhr) {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
var putResponseData = JSON.parse(putResponseDataJson);
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Information about this saver
|
||||||
|
*/
|
||||||
|
GitHubSaver.prototype.info = {
|
||||||
|
name: "github",
|
||||||
|
priority: 2000,
|
||||||
|
capabilities: ["save", "autosave"]
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Static method that returns true if this saver is capable of working
|
||||||
|
*/
|
||||||
|
exports.canSave = function(wiki) {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Create an instance of this saver
|
||||||
|
*/
|
||||||
|
exports.create = function(wiki) {
|
||||||
|
return new GitHubSaver(wiki);
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
124
core/modules/utils/base64-utf8/base64-utf8.module.js
Normal file
124
core/modules/utils/base64-utf8/base64-utf8.module.js
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
// From https://gist.github.com/Nijikokun/5192472
|
||||||
|
//
|
||||||
|
// UTF8 Module
|
||||||
|
//
|
||||||
|
// Cleaner and modularized utf-8 encoding and decoding library for javascript.
|
||||||
|
//
|
||||||
|
// copyright: MIT
|
||||||
|
// author: Nijiko Yonskai, @nijikokun, nijikokun@gmail.com
|
||||||
|
(function (name, definition, context, dependencies) {
|
||||||
|
if (typeof context['module'] !== 'undefined' && context['module']['exports']) { if (dependencies && context['require']) { for (var i = 0; i < dependencies.length; i++) context[dependencies[i]] = context['require'](dependencies[i]); } context['module']['exports'] = definition.apply(context); }
|
||||||
|
else if (typeof context['define'] !== 'undefined' && context['define'] === 'function' && context['define']['amd']) { define(name, (dependencies || []), definition); }
|
||||||
|
else { context[name] = definition.apply(context); }
|
||||||
|
})('utf8', function () {
|
||||||
|
return {
|
||||||
|
encode: function (string) {
|
||||||
|
if (typeof string !== 'string') return string;
|
||||||
|
else string = string.replace(/\r\n/g, "\n");
|
||||||
|
var output = "", i = 0, charCode;
|
||||||
|
|
||||||
|
for (i; i < string.length; i++) {
|
||||||
|
charCode = string.charCodeAt(i);
|
||||||
|
|
||||||
|
if (charCode < 128)
|
||||||
|
output += String.fromCharCode(charCode);
|
||||||
|
else if ((charCode > 127) && (charCode < 2048))
|
||||||
|
output += String.fromCharCode((charCode >> 6) | 192),
|
||||||
|
output += String.fromCharCode((charCode & 63) | 128);
|
||||||
|
else
|
||||||
|
output += String.fromCharCode((charCode >> 12) | 224),
|
||||||
|
output += String.fromCharCode(((charCode >> 6) & 63) | 128),
|
||||||
|
output += String.fromCharCode((charCode & 63) | 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
},
|
||||||
|
|
||||||
|
decode: function (string) {
|
||||||
|
if (typeof string !== 'string') return string;
|
||||||
|
var output = "", i = 0, charCode = 0;
|
||||||
|
|
||||||
|
while (i < string.length) {
|
||||||
|
charCode = string.charCodeAt(i);
|
||||||
|
|
||||||
|
if (charCode < 128)
|
||||||
|
output += String.fromCharCode(charCode),
|
||||||
|
i++;
|
||||||
|
else if ((charCode > 191) && (charCode < 224))
|
||||||
|
output += String.fromCharCode(((charCode & 31) << 6) | (string.charCodeAt(i + 1) & 63)),
|
||||||
|
i += 2;
|
||||||
|
else
|
||||||
|
output += String.fromCharCode(((charCode & 15) << 12) | ((string.charCodeAt(i + 1) & 63) << 6) | (string.charCodeAt(i + 2) & 63)),
|
||||||
|
i += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
// Base64 Module
|
||||||
|
//
|
||||||
|
// Cleaner, modularized and properly scoped base64 encoding and decoding module for strings.
|
||||||
|
//
|
||||||
|
// copyright: MIT
|
||||||
|
// author: Nijiko Yonskai, @nijikokun, nijikokun@gmail.com
|
||||||
|
(function (name, definition, context, dependencies) {
|
||||||
|
if (typeof context['module'] !== 'undefined' && context['module']['exports']) { if (dependencies && context['require']) { for (var i = 0; i < dependencies.length; i++) context[dependencies[i]] = context['require'](dependencies[i]); } context['module']['exports'] = definition.apply(context); }
|
||||||
|
else if (typeof context['define'] !== 'undefined' && context['define'] === 'function' && context['define']['amd']) { define(name, (dependencies || []), definition); }
|
||||||
|
else { context[name] = definition.apply(context); }
|
||||||
|
})('base64', function (utf8) {
|
||||||
|
var $this = this;
|
||||||
|
var $utf8 = utf8 || this.utf8;
|
||||||
|
var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||||
|
|
||||||
|
return {
|
||||||
|
encode: function (input) {
|
||||||
|
if (typeof $utf8 === 'undefined') throw { error: "MissingMethod", message: "UTF8 Module is missing." };
|
||||||
|
if (typeof input !== 'string') return input;
|
||||||
|
else input = $utf8.encode(input);
|
||||||
|
var output = "", a, b, c, d, e, f, g, i = 0;
|
||||||
|
|
||||||
|
while (i < input.length) {
|
||||||
|
a = input.charCodeAt(i++);
|
||||||
|
b = input.charCodeAt(i++);
|
||||||
|
c = input.charCodeAt(i++);
|
||||||
|
d = a >> 2;
|
||||||
|
e = ((a & 3) << 4) | (b >> 4);
|
||||||
|
f = ((b & 15) << 2) | (c >> 6);
|
||||||
|
g = c & 63;
|
||||||
|
|
||||||
|
if (isNaN(b)) f = g = 64;
|
||||||
|
else if (isNaN(c)) g = 64;
|
||||||
|
|
||||||
|
output += map.charAt(d) + map.charAt(e) + map.charAt(f) + map.charAt(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
},
|
||||||
|
|
||||||
|
decode: function (input) {
|
||||||
|
if (typeof $utf8 === 'undefined') throw { error: "MissingMethod", message: "UTF8 Module is missing." };
|
||||||
|
if (typeof input !== 'string') return input;
|
||||||
|
else input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
||||||
|
var output = "", a, b, c, d, e, f, g, i = 0;
|
||||||
|
|
||||||
|
while (i < input.length) {
|
||||||
|
d = map.indexOf(input.charAt(i++));
|
||||||
|
e = map.indexOf(input.charAt(i++));
|
||||||
|
f = map.indexOf(input.charAt(i++));
|
||||||
|
g = map.indexOf(input.charAt(i++));
|
||||||
|
|
||||||
|
a = (d << 2) | (e >> 4);
|
||||||
|
b = ((e & 15) << 4) | (f >> 2);
|
||||||
|
c = ((f & 3) << 6) | g;
|
||||||
|
|
||||||
|
output += String.fromCharCode(a);
|
||||||
|
if (f != 64) output += String.fromCharCode(b);
|
||||||
|
if (g != 64) output += String.fromCharCode(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $utf8.decode(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this, [ "utf8" ]);
|
14
core/modules/utils/base64-utf8/tiddlywiki.files
Normal file
14
core/modules/utils/base64-utf8/tiddlywiki.files
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"tiddlers": [
|
||||||
|
{
|
||||||
|
"file": "base64-utf8.module.js",
|
||||||
|
"fields": {
|
||||||
|
"type": "application/javascript",
|
||||||
|
"title": "$:/core/modules/utils/base64-utf8/base64-utf8.module.js",
|
||||||
|
"module-type": "library"
|
||||||
|
},
|
||||||
|
"prefix": "(function(){",
|
||||||
|
"suffix": "}).call(exports);"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
14
core/ui/ControlPanel/Saving/GitHub.tid
Normal file
14
core/ui/ControlPanel/Saving/GitHub.tid
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
title: $:/core/ui/ControlPanel/Saving/GitHub
|
||||||
|
tags: $:/tags/ControlPanel/Saving
|
||||||
|
caption: {{$:/language/ControlPanel/Saving/GitHub/Caption}}
|
||||||
|
|
||||||
|
\define lingo-base() $:/language/ControlPanel/Saving/GitHub/
|
||||||
|
|
||||||
|
<<lingo Description>>
|
||||||
|
|
||||||
|
|<<lingo UserName>> |<$edit-text tiddler="$:/GitHub/Username" default="" tag="input"/> |
|
||||||
|
|<<lingo Password>> |<$password name="github"/> |
|
||||||
|
|<<lingo Repo>> |<$edit-text tiddler="$:/GitHub/Repo" default="" tag="input"/> |
|
||||||
|
|<<lingo Branch>> |<$edit-text tiddler="$:/GitHub/Branch" default="" tag="input"/> |
|
||||||
|
|<<lingo Path>> |<$edit-text tiddler="$:/GitHub/Path" default="" tag="input"/> |
|
||||||
|
|<<lingo Filename>> |<$edit-text tiddler="$:/GitHub/Filename" default="" tag="input"/> |
|
@ -15,6 +15,8 @@ Added serveral new string operators: [[length|length Operator]], [[uppercase|upp
|
|||||||
|
|
||||||
Added several new palettes. See the [[palette manager|$:/core/ui/ControlPanel/Palette]].
|
Added several new palettes. See the [[palette manager|$:/core/ui/ControlPanel/Palette]].
|
||||||
|
|
||||||
|
Added new [[GitHub saver|Saving to GitHub]].
|
||||||
|
|
||||||
!! Plugin Improvements
|
!! Plugin Improvements
|
||||||
|
|
||||||
New and improved plugins:
|
New and improved plugins:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
created: 20140910212609354
|
created: 20140910212609354
|
||||||
modified: 20140910212725412
|
modified: 20190408173002622
|
||||||
tags: Definitions
|
tags: Definitions
|
||||||
title: GitHub
|
title: GitHub
|
||||||
type: text/vnd.tiddlywiki
|
type: text/vnd.tiddlywiki
|
||||||
@ -9,3 +9,5 @@ GitHub is a hosting service for distributed projects that use git as their versi
|
|||||||
The code and documentation of TiddlyWiki is hosted on GitHub at:
|
The code and documentation of TiddlyWiki is hosted on GitHub at:
|
||||||
|
|
||||||
https://github.com/Jermolene/TiddlyWiki5
|
https://github.com/Jermolene/TiddlyWiki5
|
||||||
|
|
||||||
|
GitHub also offer a free web hosting service called [[GitHub Pages|https://pages.github.com/]] that can be used directly from the single file configuration. See [[Saving to GitHub]].
|
||||||
|
24
editions/tw5.com/tiddlers/saving/Saving to GitHub.tid
Normal file
24
editions/tw5.com/tiddlers/saving/Saving to GitHub.tid
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
created: 20190408173002622
|
||||||
|
modified: 20190408173002622
|
||||||
|
tags: Saving Android Chrome Firefox InternetExplorer iOS Linux Mac Opera Safari Windows
|
||||||
|
title: Saving to GitHub
|
||||||
|
type: text/vnd.tiddlywiki
|
||||||
|
delivery: Service
|
||||||
|
method: save
|
||||||
|
caption: GitHub Saver
|
||||||
|
description: Save changes directly to a GitHub repository
|
||||||
|
|
||||||
|
TiddlyWiki can save changes directly to a GitHub repository in the single file configuration.
|
||||||
|
|
||||||
|
Saving to GitHub is configured in the [[$:/ControlPanel]] in the ''~GitHub Saver'' tab under the ''Saving'' tab. The following settings are supported:
|
||||||
|
|
||||||
|
* ''Username'' - (mandatory) the username for the GitHub account used for saving changes
|
||||||
|
* ''Password'' - (mandatory) the password, OAUTH token or personal access token for the specified account. Note that GitHub permits [[several different mechanisms|https://developer.github.com/v3/#authentication]] for authentication
|
||||||
|
* ''Repository'' - (mandatory) the name of the GitHub repository. Both the owner name and the repository name must be specified. For example `Jermolene/TiddlyWiki5`
|
||||||
|
* ''Branch'' - (optional) the name of the branch to be used within the GitHub repository. Defaults to `master`
|
||||||
|
* ''Path'' - (optional) the path to the target file. Defaults to `/`
|
||||||
|
* ''Filename'' - (mandatory) the filename of the target file
|
||||||
|
|
||||||
|
Notes
|
||||||
|
|
||||||
|
* The GitHub password is stored persistently in browser local storage. Be sure to clear the password if using a shared machine. Using a [[personal access token|]] for authentication offers an extra layer of security: if the access token is accidentally exposed it can be revoked without needing to reset the account password
|
Loading…
Reference in New Issue
Block a user