cleanup scripts
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
||||
node_modules
|
||||
debug
|
||||
dist
|
||||
output
|
||||
chrome
|
||||
|
42
.yarnclean
Normal file
@ -0,0 +1,42 @@
|
||||
# test directories
|
||||
__tests__
|
||||
test
|
||||
tests
|
||||
powered-test
|
||||
|
||||
# asset directories
|
||||
docs
|
||||
doc
|
||||
website
|
||||
images
|
||||
assets
|
||||
|
||||
# examples
|
||||
example
|
||||
examples
|
||||
|
||||
# code coverage directories
|
||||
coverage
|
||||
.nyc_output
|
||||
|
||||
# build scripts
|
||||
Makefile
|
||||
Gulpfile.js
|
||||
Gruntfile.js
|
||||
|
||||
# configs
|
||||
.tern-project
|
||||
.gitattributes
|
||||
.editorconfig
|
||||
.*ignore
|
||||
.eslintrc
|
||||
.jshintrc
|
||||
.flowconfig
|
||||
.documentup.json
|
||||
.yarn-metadata.json
|
||||
.*.yml
|
||||
*.yml
|
||||
|
||||
# misc
|
||||
*.gz
|
||||
*.md
|
38
dist/client.debug.js
vendored
@ -1,38 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function param(obj) {
|
||||
var parts = [];
|
||||
for (var name_1 in obj) {
|
||||
if (obj.hasOwnProperty(name_1)) {
|
||||
parts.push(encodeURIComponent(name_1) + "=" + encodeURIComponent(obj[name_1]));
|
||||
}
|
||||
}
|
||||
return parts.join('&');
|
||||
}
|
||||
|
||||
var script = document.currentScript;
|
||||
var attrs = {};
|
||||
for (var i = 0; i < script.attributes.length; i++) {
|
||||
var attribute = script.attributes.item(i);
|
||||
attrs[attribute.name] = attribute.value;
|
||||
}
|
||||
attrs.url = location.href;
|
||||
attrs.origin = location.origin;
|
||||
attrs.pathname = location.pathname.substr(1).replace(/\.\w+$/, '');
|
||||
attrs.title = document.title;
|
||||
var descriptionMeta = document.querySelector("meta[name='description']");
|
||||
attrs.description = descriptionMeta ? descriptionMeta.content : '';
|
||||
document.head.insertAdjacentHTML('afterbegin', "<style>\n .utterances {\n position: relative;\n width: 100%;\n }\n .utterances-frame {\n position: absolute;\n left: 0;\n right: 0;\n width: 1px;\n min-width: 100%;\n max-width: 100%;\n height: 100%;\n border: 0;\n }\n </style>");
|
||||
var url = script.src.replace(/\/client(\.debug)?\.js(?:$|\?)/, '/utterances$1.html');
|
||||
script.insertAdjacentHTML('afterend', "<div class=\"utterances\">\n <iframe class=\"utterances-frame\" scrolling=\"no\" src=\"" + url + "?" + param(attrs) + "\"></iframe>\n </div>");
|
||||
var container = script.nextElementSibling;
|
||||
script.parentElement.removeChild(script);
|
||||
addEventListener('message', function (event) {
|
||||
var data = event.data;
|
||||
if (data && data.type === 'resize' && data.height) {
|
||||
container.style.height = data.height + "px";
|
||||
}
|
||||
});
|
||||
|
||||
}());
|
1
dist/client.js
vendored
@ -1 +0,0 @@
|
||||
!function(){"use strict";function e(e){var t=[];for(var n in e)e.hasOwnProperty(n)&&t.push(encodeURIComponent(n)+"="+encodeURIComponent(e[n]));return t.join("&")}for(var t=document.currentScript,n={},r=0;r<t.attributes.length;r++){var i=t.attributes.item(r);n[i.name]=i.value}n.url=location.href,n.origin=location.origin,n.pathname=location.pathname.substr(1).replace(/\.\w+$/,""),n.title=document.title;var a=document.querySelector("meta[name='description']");n.description=a?a.content:"",document.head.insertAdjacentHTML("afterbegin","<style>\n .utterances {\n position: relative;\n width: 100%;\n }\n .utterances-frame {\n position: absolute;\n left: 0;\n right: 0;\n width: 1px;\n min-width: 100%;\n max-width: 100%;\n height: 100%;\n border: 0;\n }\n </style>");var o=t.src.replace(/\/client(\.debug)?\.js(?:$|\?)/,"/utterances$1.html");t.insertAdjacentHTML("afterend",'<div class="utterances">\n <iframe class="utterances-frame" scrolling="no" src="'+o+"?"+e(n)+'"></iframe>\n </div>');var s=t.nextElementSibling;t.parentElement.removeChild(t),addEventListener("message",function(e){var t=e.data;t&&"resize"===t.type&&t.height&&(s.style.height=t.height+"px")})}();
|
1020
dist/index.css
vendored
2
dist/index.html
vendored
127
dist/index.js
vendored
@ -1,127 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var Token = (function () {
|
||||
function Token() {
|
||||
this.storageKey = 'OAUTH_TOKEN';
|
||||
}
|
||||
Object.defineProperty(Token.prototype, "value", {
|
||||
get: function () {
|
||||
return localStorage.getItem(this.storageKey);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue === null) {
|
||||
localStorage.removeItem(this.storageKey);
|
||||
}
|
||||
else {
|
||||
localStorage.setItem(this.storageKey, newValue);
|
||||
}
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
return Token;
|
||||
}());
|
||||
var token = new Token();
|
||||
|
||||
function decodeBase64UTF8(encoded) {
|
||||
encoded = encoded.replace(/\s/g, '');
|
||||
return decodeURIComponent(escape(atob(encoded)));
|
||||
}
|
||||
|
||||
var GITHUB_API = 'https://api.github.com/';
|
||||
var GITHUB_ENCODING__HTML = 'application/vnd.github.VERSION.html';
|
||||
var GITHUB_ENCODING__REACTIONS_PREVIEW = 'application/vnd.github.squirrel-girl-preview';
|
||||
var owner;
|
||||
var repo;
|
||||
var branch;
|
||||
function setRepoContext(context) {
|
||||
owner = context.owner;
|
||||
repo = context.repo;
|
||||
branch = context.branch;
|
||||
}
|
||||
function githubRequest(relativeUrl, init) {
|
||||
init = init || {};
|
||||
init.mode = 'cors';
|
||||
init.cache = 'no-cache';
|
||||
var request = new Request(GITHUB_API + relativeUrl, init);
|
||||
request.headers.set('Accept', GITHUB_ENCODING__REACTIONS_PREVIEW);
|
||||
if (token.value !== null) {
|
||||
request.headers.set('Authorization', "token " + token.value);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
var rateLimit = {
|
||||
standard: {
|
||||
limit: Number.MAX_VALUE,
|
||||
remaining: Number.MAX_VALUE,
|
||||
reset: 0
|
||||
},
|
||||
search: {
|
||||
limit: Number.MAX_VALUE,
|
||||
remaining: Number.MAX_VALUE,
|
||||
reset: 0
|
||||
}
|
||||
};
|
||||
function processRateLimit(response) {
|
||||
var limit = response.headers.get('X-RateLimit-Limit');
|
||||
var remaining = response.headers.get('X-RateLimit-Remaining');
|
||||
var reset = response.headers.get('X-RateLimit-Reset');
|
||||
var isSearch = /\/search\//.test(response.url);
|
||||
var rate = isSearch ? rateLimit.search : rateLimit.standard;
|
||||
rate.limit = +limit;
|
||||
rate.remaining = +remaining;
|
||||
rate.reset = +reset;
|
||||
if (response.status === 403 && rate.remaining === 0) {
|
||||
var resetDate = new Date(0);
|
||||
resetDate.setUTCSeconds(rate.reset);
|
||||
var mins = Math.round((resetDate.getTime() - new Date().getTime()) / 1000 / 60);
|
||||
var apiType = isSearch ? 'search API' : 'non-search APIs';
|
||||
console.warn("Rate limit exceeded for " + apiType + ". Resets in " + mins + " minute" + (mins === 1 ? '' : 's') + ".");
|
||||
}
|
||||
}
|
||||
function githubFetch(request) {
|
||||
return fetch(request).then(function (response) {
|
||||
if (response.status === 401) {
|
||||
token.value = null;
|
||||
}
|
||||
processRateLimit(response);
|
||||
return response;
|
||||
});
|
||||
}
|
||||
function loadJsonFile(path, html) {
|
||||
if (html === void 0) { html = false; }
|
||||
var request = githubRequest("repos/" + owner + "/" + repo + "/contents/" + path + "?ref=" + branch);
|
||||
if (html) {
|
||||
request.headers.set('accept', GITHUB_ENCODING__HTML);
|
||||
}
|
||||
return githubFetch(request).then(function (response) {
|
||||
if (response.status === 404) {
|
||||
throw new Error("Repo \"" + owner + "/" + repo + "\" does not have a file named \"" + path + "\" in the \"" + branch + "\" branch.");
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error("Error fetching " + path + ".");
|
||||
}
|
||||
return html ? response.text() : response.json();
|
||||
}).then(function (file) {
|
||||
if (html) {
|
||||
return file;
|
||||
}
|
||||
var content = file.content;
|
||||
var decoded = decodeBase64UTF8(content);
|
||||
return JSON.parse(decoded);
|
||||
});
|
||||
}
|
||||
|
||||
var context = {
|
||||
owner: 'utterance',
|
||||
repo: 'utterances',
|
||||
branch: 'master'
|
||||
};
|
||||
setRepoContext(context);
|
||||
loadJsonFile('README.md', true).then(function (html) {
|
||||
var commentDiv = document.querySelector('.comment');
|
||||
commentDiv.insertAdjacentHTML('beforeend', html);
|
||||
});
|
||||
|
||||
}());
|
1186
dist/utterances.css
vendored
1
dist/utterances.html
vendored
660
dist/utterances.js
vendored
@ -1,660 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function deparam(query) {
|
||||
var match;
|
||||
var plus = /\+/g;
|
||||
var search = /([^&=]+)=?([^&]*)/g;
|
||||
var decode = function (s) { return decodeURIComponent(s.replace(plus, ' ')); };
|
||||
var params = {};
|
||||
while (match = search.exec(query)) {
|
||||
params[decode(match[1])] = decode(match[2]);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
function param(obj) {
|
||||
var parts = [];
|
||||
for (var name_1 in obj) {
|
||||
if (obj.hasOwnProperty(name_1)) {
|
||||
parts.push(encodeURIComponent(name_1) + "=" + encodeURIComponent(obj[name_1]));
|
||||
}
|
||||
}
|
||||
return parts.join('&');
|
||||
}
|
||||
|
||||
function readPageAttributes() {
|
||||
var params = deparam(location.search.substr(1));
|
||||
var issueTerm = null;
|
||||
var issueNumber = null;
|
||||
if ('issue-term' in params) {
|
||||
issueTerm = params['issue-term'];
|
||||
if (issueTerm !== undefined) {
|
||||
if (issueTerm === '') {
|
||||
throw new Error('When issue-term is specified, it cannot be blank.');
|
||||
}
|
||||
if (['title', 'url', 'pathname'].indexOf(issueTerm) !== -1) {
|
||||
issueTerm = params[issueTerm];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ('issue-number' in params) {
|
||||
issueNumber = +params['issue-number'];
|
||||
if (issueNumber.toString(10) !== params['issue-number']) {
|
||||
throw new Error("issue-number is invalid. \"" + params['issue-number']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error('Invalid query string arguments. Either "issue-term" or "issue-number" must be specified.');
|
||||
}
|
||||
if (!('repo' in params)) {
|
||||
throw new Error('Invalid query string arguments. "repo" is required.');
|
||||
}
|
||||
if (!('origin' in params)) {
|
||||
throw new Error('Invalid query string arguments. "origin" is required.');
|
||||
}
|
||||
var matches = /^([a-z][\w-]+)\/([a-z][\w-]+)$/i.exec(params.repo);
|
||||
if (matches === null) {
|
||||
throw new Error("Invalid repo: \"" + params.repo + "\"");
|
||||
}
|
||||
return {
|
||||
owner: matches[1],
|
||||
repo: matches[2],
|
||||
branch: 'branch' in params ? params.branch : 'master',
|
||||
configPath: 'config-path' in params ? params['config-path'] : 'utterances.json',
|
||||
issueTerm: issueTerm,
|
||||
issueNumber: issueNumber,
|
||||
origin: params.origin,
|
||||
url: params.url,
|
||||
title: params.title,
|
||||
description: params.description
|
||||
};
|
||||
}
|
||||
var pageAttributes = readPageAttributes();
|
||||
|
||||
var authorizeUri = 'https://github.com/login/oauth/authorize';
|
||||
var tokenUri = 'https://utterances-oauth.herokuapp.com/access-token';
|
||||
var redirect_uri = 'https://utteranc.es/authorized.html';
|
||||
var client_id = '1a560753410b181458de';
|
||||
var scopes = 'public_repo';
|
||||
var Token = (function () {
|
||||
function Token() {
|
||||
this.storageKey = 'OAUTH_TOKEN';
|
||||
}
|
||||
Object.defineProperty(Token.prototype, "value", {
|
||||
get: function () {
|
||||
return localStorage.getItem(this.storageKey);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue === null) {
|
||||
localStorage.removeItem(this.storageKey);
|
||||
}
|
||||
else {
|
||||
localStorage.setItem(this.storageKey, newValue);
|
||||
}
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
return Token;
|
||||
}());
|
||||
var token = new Token();
|
||||
function popup(url) {
|
||||
var resolve;
|
||||
window.oautherized = function (query) {
|
||||
window.oautherized = null;
|
||||
resolve(deparam(query));
|
||||
};
|
||||
var promise = new Promise(function (r) { return resolve = r; });
|
||||
window.open(url);
|
||||
return promise;
|
||||
}
|
||||
function requestAuthorizationCode() {
|
||||
var args = {
|
||||
client_id: client_id,
|
||||
redirect_uri: redirect_uri,
|
||||
scope: scopes,
|
||||
state: Math.floor(Math.random() * 100000).toString()
|
||||
};
|
||||
var url = authorizeUri + "?" + param(args);
|
||||
return popup(url)
|
||||
.then(function (result) {
|
||||
if (!(result.code && result.state)) {
|
||||
throw new Error('Redirect did not include code and state parameters.');
|
||||
}
|
||||
if (result.state !== args.state) {
|
||||
throw new Error('State mismatch.');
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
function requestAccessToken(_a) {
|
||||
var code = _a.code, state = _a.state;
|
||||
var args = { code: code, state: state };
|
||||
var url = tokenUri + "?" + param(args);
|
||||
return fetch(url)
|
||||
.then(function (response) { return response.json(); });
|
||||
}
|
||||
function login() {
|
||||
return requestAuthorizationCode()
|
||||
.then(function (response) { return requestAccessToken(response); })
|
||||
.then(function (_a) {
|
||||
var access_token = _a.access_token;
|
||||
return token.value = access_token;
|
||||
})
|
||||
.catch(function (reason) {
|
||||
token.value = null;
|
||||
throw reason;
|
||||
});
|
||||
}
|
||||
|
||||
function decodeBase64UTF8(encoded) {
|
||||
encoded = encoded.replace(/\s/g, '');
|
||||
return decodeURIComponent(escape(atob(encoded)));
|
||||
}
|
||||
|
||||
var GITHUB_API = 'https://api.github.com/';
|
||||
var GITHUB_ENCODING__HTML_JSON = 'application/vnd.github.VERSION.html+json';
|
||||
var GITHUB_ENCODING__HTML = 'application/vnd.github.VERSION.html';
|
||||
var GITHUB_ENCODING__REACTIONS_PREVIEW = 'application/vnd.github.squirrel-girl-preview';
|
||||
var UTTERANCES_API = 'https://utterances-oauth.herokuapp.com';
|
||||
var PAGE_SIZE = 100;
|
||||
var owner;
|
||||
var repo;
|
||||
var branch;
|
||||
function setRepoContext(context) {
|
||||
owner = context.owner;
|
||||
repo = context.repo;
|
||||
branch = context.branch;
|
||||
}
|
||||
function githubRequest(relativeUrl, init) {
|
||||
init = init || {};
|
||||
init.mode = 'cors';
|
||||
init.cache = 'no-cache';
|
||||
var request = new Request(GITHUB_API + relativeUrl, init);
|
||||
request.headers.set('Accept', GITHUB_ENCODING__REACTIONS_PREVIEW);
|
||||
if (token.value !== null) {
|
||||
request.headers.set('Authorization', "token " + token.value);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
var rateLimit = {
|
||||
standard: {
|
||||
limit: Number.MAX_VALUE,
|
||||
remaining: Number.MAX_VALUE,
|
||||
reset: 0
|
||||
},
|
||||
search: {
|
||||
limit: Number.MAX_VALUE,
|
||||
remaining: Number.MAX_VALUE,
|
||||
reset: 0
|
||||
}
|
||||
};
|
||||
function processRateLimit(response) {
|
||||
var limit = response.headers.get('X-RateLimit-Limit');
|
||||
var remaining = response.headers.get('X-RateLimit-Remaining');
|
||||
var reset = response.headers.get('X-RateLimit-Reset');
|
||||
var isSearch = /\/search\//.test(response.url);
|
||||
var rate = isSearch ? rateLimit.search : rateLimit.standard;
|
||||
rate.limit = +limit;
|
||||
rate.remaining = +remaining;
|
||||
rate.reset = +reset;
|
||||
if (response.status === 403 && rate.remaining === 0) {
|
||||
var resetDate = new Date(0);
|
||||
resetDate.setUTCSeconds(rate.reset);
|
||||
var mins = Math.round((resetDate.getTime() - new Date().getTime()) / 1000 / 60);
|
||||
var apiType = isSearch ? 'search API' : 'non-search APIs';
|
||||
console.warn("Rate limit exceeded for " + apiType + ". Resets in " + mins + " minute" + (mins === 1 ? '' : 's') + ".");
|
||||
}
|
||||
}
|
||||
function readRelNext(response) {
|
||||
var link = response.headers.get('link');
|
||||
if (link === null) {
|
||||
return 0;
|
||||
}
|
||||
var match = /\?page=([2-9][0-9]*)>; rel="next"/.exec(link);
|
||||
if (match === null) {
|
||||
return 0;
|
||||
}
|
||||
return +match[1];
|
||||
}
|
||||
function githubFetch(request) {
|
||||
return fetch(request).then(function (response) {
|
||||
if (response.status === 401) {
|
||||
token.value = null;
|
||||
}
|
||||
processRateLimit(response);
|
||||
return response;
|
||||
});
|
||||
}
|
||||
function loadJsonFile(path, html) {
|
||||
if (html === void 0) { html = false; }
|
||||
var request = githubRequest("repos/" + owner + "/" + repo + "/contents/" + path + "?ref=" + branch);
|
||||
if (html) {
|
||||
request.headers.set('accept', GITHUB_ENCODING__HTML);
|
||||
}
|
||||
return githubFetch(request).then(function (response) {
|
||||
if (response.status === 404) {
|
||||
throw new Error("Repo \"" + owner + "/" + repo + "\" does not have a file named \"" + path + "\" in the \"" + branch + "\" branch.");
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error("Error fetching " + path + ".");
|
||||
}
|
||||
return html ? response.text() : response.json();
|
||||
}).then(function (file) {
|
||||
if (html) {
|
||||
return file;
|
||||
}
|
||||
var content = file.content;
|
||||
var decoded = decodeBase64UTF8(content);
|
||||
return JSON.parse(decoded);
|
||||
});
|
||||
}
|
||||
function loadIssueByTerm(term) {
|
||||
var q = "\"" + term + "\" type:issue in:title repo:" + owner + "/" + repo;
|
||||
var request = githubRequest("search/issues?q=" + encodeURIComponent(q) + "&sort=created&order=asc");
|
||||
return githubFetch(request).then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('Error fetching issue via search.');
|
||||
}
|
||||
return response.json();
|
||||
}).then(function (results) {
|
||||
if (results.total_count === 0) {
|
||||
return null;
|
||||
}
|
||||
if (results.total_count > 1) {
|
||||
console.warn("Multiple issues match \"" + q + "\". Using earliest created.");
|
||||
}
|
||||
return results.items[0];
|
||||
});
|
||||
}
|
||||
function loadIssueByNumber(issueNumber) {
|
||||
var request = githubRequest("repos/" + owner + "/" + repo + "/issues/" + issueNumber);
|
||||
return githubFetch(request).then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('Error fetching issue via issue number.');
|
||||
}
|
||||
return response.json();
|
||||
});
|
||||
}
|
||||
function commentsRequest(issueNumber, page) {
|
||||
var url = "repos/" + owner + "/" + repo + "/issues/" + issueNumber + "/comments?page=" + page + "&per_page=" + PAGE_SIZE;
|
||||
var request = githubRequest(url);
|
||||
var accept = GITHUB_ENCODING__HTML_JSON + "," + GITHUB_ENCODING__REACTIONS_PREVIEW;
|
||||
request.headers.set('Accept', accept);
|
||||
return request;
|
||||
}
|
||||
function loadCommentsPage(issueNumber, page) {
|
||||
var request = commentsRequest(issueNumber, page);
|
||||
return githubFetch(request).then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('Error fetching comments.');
|
||||
}
|
||||
var nextPage = readRelNext(response);
|
||||
return response.json()
|
||||
.then(function (items) { return ({ items: items, nextPage: nextPage }); });
|
||||
});
|
||||
}
|
||||
function loadUser() {
|
||||
if (token.value === null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return githubFetch(githubRequest('user'))
|
||||
.then(function (response) {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
function createIssue(issueTerm, documentUrl, title, description) {
|
||||
var request = new Request(UTTERANCES_API + "/repos/" + owner + "/" + repo + "/issues", {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
title: issueTerm,
|
||||
body: "# " + title + "\n\n" + description + "\n\n" + documentUrl + "\n\n> :crystal_ball: *Issue created by [utteranc.es](https://utteranc.es) bot*"
|
||||
})
|
||||
});
|
||||
request.headers.set('Accept', GITHUB_ENCODING__REACTIONS_PREVIEW);
|
||||
return fetch(request).then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('Error creating comments container issue');
|
||||
}
|
||||
return response.json();
|
||||
});
|
||||
}
|
||||
function postComment(issueNumber, markdown) {
|
||||
var url = "repos/" + owner + "/" + repo + "/issues/" + issueNumber + "/comments";
|
||||
var body = JSON.stringify({ body: markdown });
|
||||
var request = githubRequest(url, { method: 'POST', body: body });
|
||||
var accept = GITHUB_ENCODING__HTML_JSON + "," + GITHUB_ENCODING__REACTIONS_PREVIEW;
|
||||
request.headers.set('Accept', accept);
|
||||
return githubFetch(request).then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('Error posting comment.');
|
||||
}
|
||||
return response.json();
|
||||
});
|
||||
}
|
||||
|
||||
var thresholds = [
|
||||
1000, 'second',
|
||||
1000 * 60, 'minute',
|
||||
1000 * 60 * 60, 'hour',
|
||||
1000 * 60 * 60 * 24, 'day',
|
||||
1000 * 60 * 60 * 24 * 7, 'week',
|
||||
1000 * 60 * 60 * 24 * 27, 'month'
|
||||
];
|
||||
var formatOptions = { month: 'short', day: 'numeric', year: 'numeric' };
|
||||
function timeAgo(current, value) {
|
||||
var elapsed = current - value.getTime();
|
||||
if (elapsed < 5000) {
|
||||
return 'just now';
|
||||
}
|
||||
var i = 0;
|
||||
while (i + 2 < thresholds.length && elapsed * 1.1 > thresholds[i + 2]) {
|
||||
i += 2;
|
||||
}
|
||||
var divisor = thresholds[i];
|
||||
var text = thresholds[i + 1];
|
||||
var units = Math.round(elapsed / divisor);
|
||||
if (units > 3 && i === thresholds.length - 2) {
|
||||
return "on " + value.toLocaleDateString(undefined, formatOptions);
|
||||
}
|
||||
return units === 1 ? (text === 'hour' ? 'an' : 'a') + " " + text + " ago" : units + " " + text + "s ago";
|
||||
}
|
||||
|
||||
var avatarArgs = '?v=3&s=88';
|
||||
var CommentComponent = (function () {
|
||||
function CommentComponent(comment, currentUser, repoOwner) {
|
||||
this.comment = comment;
|
||||
this.currentUser = currentUser;
|
||||
this.repoOwner = repoOwner;
|
||||
var user = comment.user, html_url = comment.html_url, created_at = comment.created_at, body_html = comment.body_html;
|
||||
this.element = document.createElement('article');
|
||||
this.element.classList.add('timeline-comment');
|
||||
if (user.login === currentUser) {
|
||||
this.element.classList.add('current-user');
|
||||
}
|
||||
if (user.login === repoOwner) {
|
||||
this.element.classList.add('repo-owner');
|
||||
}
|
||||
this.element.innerHTML = "\n <a class=\"avatar\" href=\"" + user.html_url + "\" target=\"_blank\">\n <img alt=\"@" + user.login + "\" height=\"44\" width=\"44\"\n src=\"" + user.avatar_url + avatarArgs + "\">\n </a>\n <div class=\"comment\">\n <header class=\"comment-header\">\n <a class=\"text-link\" href=\"" + user.html_url + "\" target=\"_blank\">" + user.login + "</a>\n commented\n <a class=\"text-link\" href=\"" + html_url + "\" target=\"_blank\">\n " + timeAgo(Date.now(), new Date(created_at)) + "\n </a>\n </header>\n <div class=\"markdown-body\">\n " + body_html + "\n </div>\n </div>";
|
||||
this.retargetLinks();
|
||||
}
|
||||
CommentComponent.prototype.setComment = function (comment) {
|
||||
var commentDiv = this.element.lastElementChild;
|
||||
var user = comment.user, html_url = comment.html_url, created_at = comment.created_at, body_html = comment.body_html;
|
||||
if (this.comment.user.login !== user.login) {
|
||||
if (user.login === this.currentUser) {
|
||||
this.element.classList.add('current-user');
|
||||
}
|
||||
else {
|
||||
this.element.classList.remove('current-user');
|
||||
}
|
||||
if (user.login === this.repoOwner) {
|
||||
this.element.classList.add('repo-owner');
|
||||
}
|
||||
else {
|
||||
this.element.classList.remove('repo-owner');
|
||||
}
|
||||
var avatarAnchor = this.element.firstElementChild;
|
||||
var avatarImg = avatarAnchor.firstElementChild;
|
||||
avatarAnchor.href = user.html_url;
|
||||
avatarImg.alt = '@' + user.login;
|
||||
avatarImg.src = user.avatar_url + avatarArgs;
|
||||
var authorAnchor = commentDiv
|
||||
.firstElementChild.firstElementChild;
|
||||
authorAnchor.href = user.html_url;
|
||||
authorAnchor.textContent = user.login;
|
||||
}
|
||||
if (this.comment.created_at !== created_at || this.comment.html_url !== html_url) {
|
||||
var timestamp = commentDiv.firstElementChild.firstElementChild.lastElementChild;
|
||||
timestamp.href = html_url;
|
||||
timestamp.textContent = timeAgo(Date.now(), new Date(created_at));
|
||||
}
|
||||
if (this.comment.body_html !== body_html) {
|
||||
var body = commentDiv.lastElementChild;
|
||||
body.innerHTML = body_html;
|
||||
this.retargetLinks();
|
||||
}
|
||||
this.comment = comment;
|
||||
};
|
||||
CommentComponent.prototype.setCurrentUser = function (currentUser) {
|
||||
if (this.currentUser === currentUser) {
|
||||
return;
|
||||
}
|
||||
var commentDiv = this.element.firstElementChild;
|
||||
if (this.comment.user.login === this.currentUser) {
|
||||
commentDiv.classList.add('current-user');
|
||||
}
|
||||
else {
|
||||
commentDiv.classList.remove('current-user');
|
||||
}
|
||||
if (this.comment.user.login === this.repoOwner) {
|
||||
this.element.classList.add('repo-owner');
|
||||
}
|
||||
else {
|
||||
this.element.classList.remove('repo-owner');
|
||||
}
|
||||
this.currentUser = currentUser;
|
||||
};
|
||||
CommentComponent.prototype.retargetLinks = function () {
|
||||
var links = this.element.lastElementChild.lastElementChild.querySelectorAll('a');
|
||||
var j = links.length;
|
||||
while (j--) {
|
||||
var link = links.item(j);
|
||||
link.target = '_blank';
|
||||
}
|
||||
};
|
||||
return CommentComponent;
|
||||
}());
|
||||
|
||||
var hostOrigin;
|
||||
function setHostOrigin(origin) {
|
||||
hostOrigin = origin;
|
||||
addEventListener('resize', publishResize);
|
||||
}
|
||||
var lastHeight = -1;
|
||||
function publishResize() {
|
||||
var body = document.body, html = document.documentElement;
|
||||
var height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
|
||||
if (height === lastHeight) {
|
||||
return;
|
||||
}
|
||||
lastHeight = height;
|
||||
var message = { type: 'resize', height: height };
|
||||
parent.postMessage(message, hostOrigin);
|
||||
}
|
||||
|
||||
var TimelineComponent = (function () {
|
||||
function TimelineComponent(user, issue, repoOwner) {
|
||||
this.user = user;
|
||||
this.issue = issue;
|
||||
this.repoOwner = repoOwner;
|
||||
this.timeline = [];
|
||||
this.element = document.createElement('section');
|
||||
this.element.classList.add('timeline');
|
||||
this.element.innerHTML = "\n <h1 class=\"timeline-header\">\n <a class=\"text-link\" target=\"_blank\"></a>\n <em>\n - powered by\n <a class=\"text-link\" href=\"https://utteranc.es\" target=\"_blank\">utteranc.es</a>\n </em>\n </h1>";
|
||||
this.countAnchor = this.element.firstElementChild.firstElementChild;
|
||||
this.marker = document.createComment('marker');
|
||||
this.element.appendChild(this.marker);
|
||||
this.setIssue(issue);
|
||||
}
|
||||
TimelineComponent.prototype.setUser = function (user) {
|
||||
this.user = user;
|
||||
var login = user ? user.login : null;
|
||||
for (var i = 0; i < this.timeline.length; i++) {
|
||||
this.timeline[i].setCurrentUser(login);
|
||||
}
|
||||
publishResize();
|
||||
};
|
||||
TimelineComponent.prototype.setIssue = function (issue) {
|
||||
this.issue = issue;
|
||||
if (issue) {
|
||||
this.countAnchor.textContent = issue.comments + " Comment" + (issue.comments === 1 ? '' : 's');
|
||||
this.countAnchor.href = issue.html_url;
|
||||
}
|
||||
else {
|
||||
this.countAnchor.textContent = "0 Comments";
|
||||
this.countAnchor.removeAttribute('href');
|
||||
}
|
||||
};
|
||||
TimelineComponent.prototype.appendComment = function (comment) {
|
||||
var component = new CommentComponent(comment, this.user ? this.user.login : null, this.repoOwner);
|
||||
this.timeline.push(component);
|
||||
this.element.insertBefore(component.element, this.marker);
|
||||
publishResize();
|
||||
};
|
||||
TimelineComponent.prototype.replaceComments = function (comments) {
|
||||
var i;
|
||||
for (i = 0; i < comments.length; i++) {
|
||||
var comment = comments[i];
|
||||
if (i <= this.timeline.length) {
|
||||
this.appendComment(comment);
|
||||
continue;
|
||||
}
|
||||
this.timeline[i].setComment(comment);
|
||||
}
|
||||
for (; i < this.timeline.length; i++) {
|
||||
this.element.removeChild(this.element.lastElementChild);
|
||||
}
|
||||
publishResize();
|
||||
};
|
||||
return TimelineComponent;
|
||||
}());
|
||||
|
||||
var anonymousAvatarUrl = "data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 14 16\" version=\"1.1\"><path fill=\"rgb(179,179,179)\" fill-rule=\"evenodd\" d=\"M8 10.5L9 14H5l1-3.5L5.25 9h3.5L8 10.5zM10 6H4L2 7h10l-2-1zM9 2L7 3 5 2 4 5h6L9 2zm4.03 7.75L10 9l1 2-2 3h3.22c.45 0 .86-.31.97-.75l.56-2.28c.14-.53-.19-1.08-.72-1.22zM4 9l-3.03.75c-.53.14-.86.69-.72 1.22l.56 2.28c.11.44.52.75.97.75H5l-2-3 1-2z\"></path></svg>";
|
||||
var NewCommentComponent = (function () {
|
||||
function NewCommentComponent(user, submit) {
|
||||
this.user = user;
|
||||
this.submit = submit;
|
||||
this.element = document.createElement('article');
|
||||
this.element.classList.add('timeline-comment');
|
||||
this.element.addEventListener('mousemove', publishResize);
|
||||
this.element.innerHTML = "\n <a class=\"avatar\" target=\"_blank\">\n <img height=\"44\" width=\"44\">\n </a>\n <div class=\"new-comment\">\n <header class=\"new-comment-header\">\n Join the discussion\n </header>\n <form class=\"new-comment-body\" id=\"comment-form\" accept-charset=\"UTF-8\" action=\"javascript:\">\n <textarea placeholder=\"Leave a comment\" aria-label=\"comment\"></textarea>\n </form>\n <footer class=\"new-comment-footer\">\n <a class=\"text-link markdown-info\" tabindex=\"-1\" target=\"_blank\"\n href=\"https://guides.github.com/features/mastering-markdown/\">\n Styling with Markdown is supported\n </a>\n <button class=\"btn btn-primary\" form=\"comment-form\" type=\"submit\">Comment</button>\n </footer>\n </div>";
|
||||
this.setUser(user);
|
||||
}
|
||||
NewCommentComponent.prototype.setUser = function (user) {
|
||||
var _this = this;
|
||||
this.user = user;
|
||||
var avatarAnchor = this.element.firstElementChild;
|
||||
var avatar = avatarAnchor.firstElementChild;
|
||||
if (user) {
|
||||
avatarAnchor.href = user.html_url;
|
||||
avatar.alt = '@' + user.login;
|
||||
avatar.src = user.avatar_url + '?v=3&s=88';
|
||||
}
|
||||
else {
|
||||
avatarAnchor.removeAttribute('href');
|
||||
avatar.alt = '@anonymous';
|
||||
avatar.src = anonymousAvatarUrl;
|
||||
}
|
||||
var form = avatarAnchor.nextElementSibling.firstElementChild.nextElementSibling;
|
||||
var textarea = form.firstElementChild;
|
||||
var submitButton = form.nextElementSibling.lastElementChild;
|
||||
submitButton.textContent = user ? 'Comment' : 'Sign in to comment';
|
||||
submitButton.disabled = !!user;
|
||||
textarea.disabled = !user;
|
||||
textarea.addEventListener('input', function () {
|
||||
submitButton.disabled = /^\s*$/.test(textarea.value);
|
||||
if (textarea.scrollHeight < 450 && textarea.offsetHeight < textarea.scrollHeight) {
|
||||
textarea.style.height = textarea.scrollHeight + "px";
|
||||
publishResize();
|
||||
}
|
||||
});
|
||||
var submitting = false;
|
||||
form.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
if (submitting) {
|
||||
return;
|
||||
}
|
||||
submitting = true;
|
||||
if (_this.user) {
|
||||
textarea.disabled = true;
|
||||
submitButton.disabled = true;
|
||||
}
|
||||
_this.submit(textarea.value).catch(function () { return 0; }).then(function () {
|
||||
submitting = false;
|
||||
textarea.disabled = !_this.user;
|
||||
textarea.value = '';
|
||||
submitButton.disabled = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
NewCommentComponent.prototype.clear = function () {
|
||||
var textarea = this.element.lastElementChild.lastElementChild
|
||||
.firstElementChild.firstElementChild;
|
||||
textarea.value = '';
|
||||
};
|
||||
return NewCommentComponent;
|
||||
}());
|
||||
|
||||
function normalizeConfig(filename, rawConfig) {
|
||||
if (!Array.isArray(rawConfig.origins)) {
|
||||
throw new Error(filename + ": origins must be an array");
|
||||
}
|
||||
return rawConfig;
|
||||
}
|
||||
function loadRepoConfig(path) {
|
||||
return loadJsonFile(path)
|
||||
.then(function (config) { return normalizeConfig(path, config); });
|
||||
}
|
||||
|
||||
setRepoContext(pageAttributes);
|
||||
function loadIssue() {
|
||||
if (pageAttributes.issueNumber !== null) {
|
||||
return loadIssueByNumber(pageAttributes.issueNumber);
|
||||
}
|
||||
return loadIssueByTerm(pageAttributes.issueTerm);
|
||||
}
|
||||
Promise.all([loadRepoConfig(pageAttributes.configPath), loadIssue(), loadUser()])
|
||||
.then(function (_a) {
|
||||
var repoConfig = _a[0], issue = _a[1], user = _a[2];
|
||||
return bootstrap(repoConfig, issue, user);
|
||||
});
|
||||
function bootstrap(config, issue, user) {
|
||||
if (config.origins.indexOf(pageAttributes.origin) === -1) {
|
||||
throw new Error("The origins specified in " + pageAttributes.configPath + " do not include " + pageAttributes.origin);
|
||||
}
|
||||
setHostOrigin(pageAttributes.origin);
|
||||
var timeline = new TimelineComponent(user, issue, pageAttributes.owner);
|
||||
document.body.appendChild(timeline.element);
|
||||
if (issue && issue.comments > 0) {
|
||||
loadCommentsPage(issue.number, 1).then(function (_a) {
|
||||
var items = _a.items;
|
||||
return timeline.replaceComments(items);
|
||||
});
|
||||
}
|
||||
if (issue && issue.locked) {
|
||||
return;
|
||||
}
|
||||
var submit = function (markdown) {
|
||||
if (user) {
|
||||
var commentPromise = void 0;
|
||||
if (issue) {
|
||||
commentPromise = postComment(issue.number, markdown);
|
||||
}
|
||||
else {
|
||||
commentPromise = createIssue(pageAttributes.issueTerm, pageAttributes.url, pageAttributes.title, pageAttributes.description).then(function (newIssue) {
|
||||
issue = newIssue;
|
||||
timeline.setIssue(issue);
|
||||
return postComment(issue.number, markdown);
|
||||
});
|
||||
}
|
||||
return commentPromise.then(function (comment) {
|
||||
timeline.appendComment(comment);
|
||||
newCommentComponent.clear();
|
||||
});
|
||||
}
|
||||
return login().then(function () { return loadUser(); }).then(function (u) {
|
||||
user = u;
|
||||
timeline.setUser(user);
|
||||
newCommentComponent.setUser(user);
|
||||
});
|
||||
};
|
||||
var newCommentComponent = new NewCommentComponent(user, submit);
|
||||
timeline.element.appendChild(newCommentComponent.element);
|
||||
publishResize();
|
||||
}
|
||||
|
||||
}());
|
3
dist/utterances.json
vendored
@ -1,3 +0,0 @@
|
||||
{
|
||||
"origins": ["https://utteranc.es", "http://localhost:9000"]
|
||||
}
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 616 B After Width: | Height: | Size: 616 B |
Before Width: | Height: | Size: 923 B After Width: | Height: | Size: 923 B |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 722 B After Width: | Height: | Size: 722 B |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
39
package.json
@ -4,23 +4,30 @@
|
||||
"main": "dist/client.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rm -rf output",
|
||||
"rollup-client": "rollup output/client.js --format iife --output dist/client.debug.js",
|
||||
"rollup-utterances": "rollup output/utterances.js --format iife --output dist/utterances.js",
|
||||
"rollup-index": "rollup output/index.js --format iife --output dist/index.js",
|
||||
"rollup": "yarn run rollup-client && yarn run rollup-utterances && yarn run rollup-index",
|
||||
"sass": "node-sass src --output dist --include-path node_modules",
|
||||
"optimize-client": "uglifyjs --compress --mangle --output dist/client.js -- dist/client.debug.js",
|
||||
"optimize-utterances": "inliner --skip-absolute-urls dist/utterances.debug.html > dist/utterances.html",
|
||||
"optimize-index": "inliner --skip-absolute-urls dist/index.debug.html > dist/index.html",
|
||||
"optimize": "yarn run optimize-client && yarn run optimize-utterances && yarn run optimize-index",
|
||||
"prebuild": "yarn run clean",
|
||||
"build": "tsc && yarn run rollup && yarn run sass && yarn run optimize",
|
||||
"watch-output": "chokidar \"output/**/*.js\" --command \"yarn run rollup\"",
|
||||
"serve": "browser-sync start --server dist --files=\"dist/**/*\" --port 9000 --no-open --no-ui --no-online --no-ghost-mode",
|
||||
"predevelop": "yarn run build",
|
||||
"develop": "concurrently --kill-others \"yarn run watch-output\" \"tsc --watch\" \"yarn run sass -- --watch --recursive\" \"yarn run serve\"",
|
||||
"lint": "tslint --project tsconfig.json",
|
||||
"clean": "rm -rf output && rm -rf debug && rm -rf dist && mkdir debug && mkdir dist",
|
||||
"rollup-client": "rollup output/client.js --format iife --output debug/client.js",
|
||||
"rollup-utterances": "rollup output/utterances.js --format iife --output debug/utterances.js",
|
||||
"rollup-index": "rollup output/index.js --format iife --output debug/index.js",
|
||||
"rollup": "yarn run rollup-client && yarn run rollup-utterances && yarn run rollup-index",
|
||||
"html": "cp src/*.html debug/ && cp src/authorized.html dist/",
|
||||
"sass": "node-sass src --output debug --include-path node_modules",
|
||||
"optimize-client": "uglifyjs --compress --mangle --output dist/client.js -- debug/client.js",
|
||||
"optimize-utterances": "inliner --skip-absolute-urls debug/utterances.html > dist/utterances.html",
|
||||
"optimize-index": "inliner --skip-absolute-urls debug/index.html > dist/index.html",
|
||||
"optimize": "yarn run optimize-client && yarn run optimize-utterances && yarn run optimize-index",
|
||||
"watch-output": "chokidar \"output/**/*.js\" --command \"yarn run rollup\"",
|
||||
"watch-html": "chokidar \"src/*.html\" --command \"yarn run html\"",
|
||||
"watch-debug": "chokidar \"debug/*/**\" --command \"yarn run optimize\"",
|
||||
"watch": "concurrently --kill-others \"tsc --watch\" \"yarn run sass -- --watch --recursive\" \"yarn run watch-output\" \"yarn run watch-html\" \"yarn run watch-debug\"",
|
||||
"serve-debug": "browser-sync start --server debug --files=\"debug/**/*\" --port 9000 --no-open --no-ui --no-online --no-ghost-mode",
|
||||
"serve-dist": "browser-sync start --server dist --files=\"dist/**/*\" --port 8000 --no-open --no-ui --no-online --no-ghost-mode",
|
||||
"serve": "concurrently --kill-others \"yarn run serve-debug\" \"yarn run serve-dist\"",
|
||||
"predevelop": "yarn run clean && yarn run html && tsc && yarn run rollup && yarn run sass && yarn run optimize",
|
||||
"develop": "concurrently --kill-others \"yarn run watch\" \"yarn run serve\"",
|
||||
"prebuild": "yarn run clean",
|
||||
"build": "yarn run html && tsc && yarn run rollup && yarn run sass && yarn run optimize",
|
||||
"predeploy": "yarn run build && cp -R icons dist/ && cp .nojekyll dist/ && cp CNAME dist/ && cp utterances.json dist/",
|
||||
"deploy": "gh-pages --dist dist",
|
||||
"chrome": "google-chrome --disable-web-security --allow-running-insecure-content --user-data-dir=\"chrome\""
|
||||
},
|
||||
|
47
src/_buttons.scss
Normal file
@ -0,0 +1,47 @@
|
||||
@import "primer-base/index";
|
||||
|
||||
.btn {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
font-size: $body-font-size;
|
||||
font-weight: $font-weight-bold;
|
||||
line-height: 20px; // Specifically not inherit our `<body>` default
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background-repeat: repeat-x;
|
||||
background-position: -1px -1px;
|
||||
background-size: 110% 110%;
|
||||
border: 1px solid transparentize($black, 0.8);
|
||||
border-radius: 0.25em;
|
||||
appearance: none; // Corrects inability to style clickable `input` types in iOS.
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
// Repeat `background-position` because `:hover`
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&.selected {
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
|
||||
// .btn { @include btn-solid($text-gray-dark, $gray-000, darken($bg-gray, 2%)); }
|
||||
.btn-primary { @include btn-solid($text-white, $green-400, $green-500); }
|
||||
// .btn-purple { @include btn-solid($text-white, lighten($purple-500, 5%), darken($purple-500, 5%)); }
|
||||
// .btn-blue { @include btn-solid($text-white, lighten($blue-500, 8%), darken($blue-500, 2%)); }
|
||||
// .btn-danger { @include btn-inverse($red-600, $gray-000, darken($bg-gray, 2%)); }
|
111
src/_timeline-comment.scss
Normal file
@ -0,0 +1,111 @@
|
||||
@import "primer-base/index";
|
||||
@import "primer-markdown/index";
|
||||
@import "pygments-vs";
|
||||
|
||||
.timeline-comment {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin: $spacer-3 0;
|
||||
|
||||
.avatar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.avatar > img {
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.comment {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
min-width: 0px;
|
||||
border: $border;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.comment-header {
|
||||
padding: $spacer-2 $spacer-3;
|
||||
color: $text-gray;
|
||||
background-color: $bg-gray;
|
||||
border-bottom: $border;
|
||||
|
||||
.user-login {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
padding: $spacer-3 $spacer-3;
|
||||
max-height: 400px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
font-size: $body-font-size;
|
||||
}
|
||||
|
||||
.comment-body {
|
||||
padding: $spacer-2;
|
||||
|
||||
textarea {
|
||||
display: block;
|
||||
padding: $spacer-2;
|
||||
width: 100%;
|
||||
min-height: 90px;
|
||||
max-height: 550px;
|
||||
resize: vertical;
|
||||
border: $border;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
box-shadow: $btn-input-focus-shadow;
|
||||
}
|
||||
|
||||
textarea:disabled {
|
||||
background-color: $gray-100;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 $spacer-2 $spacer-2 $spacer-2;
|
||||
|
||||
.markdown-info {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-comment.current-user .comment-header {
|
||||
background-color: $bg-blue-light;
|
||||
}
|
||||
|
||||
.timeline-comment.repo-owner .comment-header :last-child::after {
|
||||
float: right;
|
||||
margin-top: -1px;
|
||||
padding: 2px 5px;
|
||||
border: $border;
|
||||
border-radius: $border-radius;
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-semibold;
|
||||
content: 'Owner';
|
||||
}
|
||||
|
||||
@media (min-width: map-get($breakpoints, sm)) {
|
||||
.timeline-comment {
|
||||
.avatar {
|
||||
display: block;
|
||||
margin-right: $spacer-3;
|
||||
}
|
||||
|
||||
.comment {
|
||||
@include double-caret($bg-gray, $border-gray-dark);
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-comment.current-user .comment {
|
||||
@include double-caret($bg-blue-light, $border-gray-dark);
|
||||
}
|
||||
}
|
27
src/_timeline.scss
Normal file
@ -0,0 +1,27 @@
|
||||
.timeline {
|
||||
max-width: 760px;
|
||||
margin: $spacer-3 $spacer-1;
|
||||
}
|
||||
|
||||
.timeline-header {
|
||||
margin: 0;
|
||||
padding-left: $spacer-3;
|
||||
font-size: $body-font-size;
|
||||
color: $text-gray;
|
||||
}
|
||||
|
||||
.timeline-header em {
|
||||
font-weight: $font-weight-normal;
|
||||
}
|
||||
|
||||
@media (min-width: map-get($breakpoints, sm)) {
|
||||
.timeline-header {
|
||||
padding-left: 44 + $spacer-3;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: map-get($breakpoints, md)) {
|
||||
.timeline {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
3
src/_util.scss
Normal file
@ -0,0 +1,3 @@
|
||||
.text-link {
|
||||
color: $text-gray;
|
||||
}
|
@ -27,7 +27,7 @@ export class CommentComponent {
|
||||
</a>
|
||||
<div class="comment">
|
||||
<header class="comment-header">
|
||||
<a class="text-link" href="${user.html_url}" target="_blank">${user.login}</a>
|
||||
<a class="user-login text-link" href="${user.html_url}" target="_blank">${user.login}</a>
|
||||
commented
|
||||
<a class="text-link" href="${html_url}" target="_blank">
|
||||
${timeAgo(Date.now(), new Date(created_at))}
|
||||
|
@ -26,7 +26,7 @@
|
||||
</a>
|
||||
<div class="comment">
|
||||
<header class="comment-header">
|
||||
utterances
|
||||
<a class="user-login text-link" href="https://github.com/utterance">utterances</a>
|
||||
</header>
|
||||
</div>
|
||||
</article>
|
@ -177,8 +177,7 @@ export function createIssue(issueTerm: string, documentUrl: string, title: strin
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
title: issueTerm,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
body: `# ${title}\n\n${description}\n\n${documentUrl}\n\n> :crystal_ball: *Issue created by [utteranc.es](https://utteranc.es) bot*`
|
||||
body: `# ${title}\n\n${description}\n\n[${documentUrl}](${documentUrl})`
|
||||
})
|
||||
});
|
||||
request.headers.set('Accept', GITHUB_ENCODING__REACTIONS_PREVIEW);
|
||||
|
@ -26,7 +26,7 @@
|
||||
</a>
|
||||
<div class="comment">
|
||||
<header class="comment-header">
|
||||
utterances
|
||||
<a class="user-login text-link" href="https://github.com/utterance">utterances</a>
|
||||
</header>
|
||||
</div>
|
||||
</article>
|
||||
@ -37,7 +37,7 @@
|
||||
<script src="https://utteranc.es/client.js"
|
||||
repo="utterance/utterances"
|
||||
branch="gh-pages"
|
||||
issue-term="pathname"
|
||||
issue-term="homepage"
|
||||
async>
|
||||
</script>
|
||||
|
@ -1,72 +1,17 @@
|
||||
@import "primer-base/index";
|
||||
@import "primer-markdown/index";
|
||||
@import "pygments-vs";
|
||||
@import "util";
|
||||
@import "timeline";
|
||||
@import "timeline-comment";
|
||||
|
||||
.timeline {
|
||||
max-width: 760px;
|
||||
margin: $spacer-3 $spacer-1;
|
||||
}
|
||||
.timeline-comment .markdown-body {
|
||||
max-height: unset;
|
||||
font-size: 16px;
|
||||
|
||||
.timeline-comment {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin: $spacer-3 0;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.avatar > img {
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.comment {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
min-width: 0px;
|
||||
border: $border;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.comment-header {
|
||||
padding: $spacer-2 $spacer-3;
|
||||
color: $text-gray;
|
||||
background-color: $bg-gray;
|
||||
border-bottom: $border;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
padding: $spacer-3 $spacer-3;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
h1, h2, h2, h4, h5, h6 {
|
||||
h1,h2,h3,h4,h5,h6 {
|
||||
.anchor {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: map-get($breakpoints, sm)) {
|
||||
.timeline-header {
|
||||
padding-left: 44 + $spacer-3;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: block;
|
||||
margin-right: $spacer-3;
|
||||
}
|
||||
|
||||
.comment {
|
||||
@include double-caret($bg-gray, $border-gray-dark);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: map-get($breakpoints, md)) {
|
||||
.timeline {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
@ -19,14 +19,14 @@ export class NewCommentComponent {
|
||||
<a class="avatar" target="_blank">
|
||||
<img height="44" width="44">
|
||||
</a>
|
||||
<div class="new-comment">
|
||||
<header class="new-comment-header">
|
||||
<div class="comment">
|
||||
<header class="comment-header">
|
||||
Join the discussion
|
||||
</header>
|
||||
<form class="new-comment-body" id="comment-form" accept-charset="UTF-8" action="javascript:">
|
||||
<form class="comment-body" id="comment-form" accept-charset="UTF-8" action="javascript:">
|
||||
<textarea placeholder="Leave a comment" aria-label="comment"></textarea>
|
||||
</form>
|
||||
<footer class="new-comment-footer">
|
||||
<footer class="comment-footer">
|
||||
<a class="text-link markdown-info" tabindex="-1" target="_blank"
|
||||
href="https://guides.github.com/features/mastering-markdown/">
|
||||
Styling with Markdown is supported
|
||||
|
@ -21,15 +21,15 @@ function readPageAttributes() {
|
||||
throw new Error(`issue-number is invalid. "${params['issue-number']}`);
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invalid query string arguments. Either "issue-term" or "issue-number" must be specified.');
|
||||
throw new Error('"issue-term" or "issue-number" must be specified.');
|
||||
}
|
||||
|
||||
if (!('repo' in params)) {
|
||||
throw new Error('Invalid query string arguments. "repo" is required.');
|
||||
throw new Error('"repo" is required.');
|
||||
}
|
||||
|
||||
if (!('origin' in params)) {
|
||||
throw new Error('Invalid query string arguments. "origin" is required.');
|
||||
throw new Error('"origin" is required.');
|
||||
}
|
||||
|
||||
const matches = /^([a-z][\w-]+)\/([a-z][\w-]+)$/i.exec(params.repo);
|
||||
|
@ -1,208 +1,11 @@
|
||||
@import "primer-base/index";
|
||||
@import "primer-markdown/index";
|
||||
@import "pygments-vs";
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.text-link {
|
||||
color: $text-gray;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
max-width: 760px;
|
||||
margin: $spacer-3 $spacer-1;
|
||||
}
|
||||
|
||||
.timeline-header {
|
||||
margin: 0;
|
||||
padding-left: $spacer-3;
|
||||
font-size: $body-font-size;
|
||||
color: $text-gray;
|
||||
}
|
||||
|
||||
.timeline-header em {
|
||||
font-weight: $font-weight-normal;
|
||||
}
|
||||
|
||||
.timeline-comment {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin: $spacer-3 0;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.avatar > img {
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.comment {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
min-width: 0px;
|
||||
border: $border;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.comment-header {
|
||||
padding: $spacer-2 $spacer-3;
|
||||
color: $text-gray;
|
||||
background-color: $bg-gray;
|
||||
border-bottom: $border;
|
||||
}
|
||||
|
||||
.comment-header :first-child {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
|
||||
.current-user .comment-header {
|
||||
background-color: $bg-blue-light;
|
||||
}
|
||||
|
||||
.repo-owner .comment-header :last-child::after {
|
||||
float: right;
|
||||
margin-top: -1px;
|
||||
padding: 2px 5px;
|
||||
border: $border;
|
||||
border-radius: $border-radius;
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-semibold;
|
||||
content: 'Owner';
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
padding: $spacer-3 $spacer-3;
|
||||
max-height: 400px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
font-size: $body-font-size;
|
||||
}
|
||||
|
||||
.new-comment {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
border: $border;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.new-comment-header {
|
||||
padding: $spacer-2 $spacer-3;
|
||||
font-weight: $font-weight-bold;
|
||||
color: $text-gray;
|
||||
background-color: $bg-blue-light;
|
||||
border-bottom: $border;
|
||||
}
|
||||
|
||||
.new-comment-body {
|
||||
padding: $spacer-2;
|
||||
}
|
||||
|
||||
.new-comment-body textarea {
|
||||
display: block;
|
||||
padding: $spacer-2;
|
||||
width: 100%;
|
||||
min-height: 90px;
|
||||
max-height: 550px;
|
||||
resize: vertical;
|
||||
border: $border;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.new-comment-body textarea:focus {
|
||||
box-shadow: $btn-input-focus-shadow;
|
||||
}
|
||||
|
||||
.new-comment-body textarea:disabled {
|
||||
background-color: $gray-100;
|
||||
}
|
||||
|
||||
.new-comment-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 $spacer-2 $spacer-2 $spacer-2;
|
||||
}
|
||||
|
||||
.markdown-info {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
.btn {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
font-size: $body-font-size;
|
||||
font-weight: $font-weight-bold;
|
||||
line-height: 20px; // Specifically not inherit our `<body>` default
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background-repeat: repeat-x;
|
||||
background-position: -1px -1px;
|
||||
background-size: 110% 110%;
|
||||
border: 1px solid transparentize($black, 0.8);
|
||||
border-radius: 0.25em;
|
||||
appearance: none; // Corrects inability to style clickable `input` types in iOS.
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
// Repeat `background-position` because `:hover`
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&.selected {
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
|
||||
// .btn { @include btn-solid($text-gray-dark, $gray-000, darken($bg-gray, 2%)); }
|
||||
.btn-primary { @include btn-solid($text-white, $green-400, $green-500); }
|
||||
// .btn-purple { @include btn-solid($text-white, lighten($purple-500, 5%), darken($purple-500, 5%)); }
|
||||
// .btn-blue { @include btn-solid($text-white, lighten($blue-500, 8%), darken($blue-500, 2%)); }
|
||||
// .btn-danger { @include btn-inverse($red-600, $gray-000, darken($bg-gray, 2%)); }
|
||||
|
||||
@media (min-width: map-get($breakpoints, sm)) {
|
||||
.timeline-header {
|
||||
padding-left: 44 + $spacer-3;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: block;
|
||||
margin-right: $spacer-3;
|
||||
}
|
||||
|
||||
.comment {
|
||||
@include double-caret($bg-gray, $border-gray-dark);
|
||||
}
|
||||
|
||||
.current-user .comment {
|
||||
@include double-caret($bg-blue-light, $border-gray-dark);
|
||||
}
|
||||
|
||||
.new-comment {
|
||||
@include double-caret($bg-blue-light, $border-gray-dark);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: map-get($breakpoints, md)) {
|
||||
.timeline {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
@import "primer-base/index";
|
||||
@import "primer-markdown/index";
|
||||
@import "pygments-vs";
|
||||
@import "util";
|
||||
@import "buttons";
|
||||
@import "timeline";
|
||||
@import "timeline-comment";
|
||||
|
3
utterances.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"origins": ["https://utteranc.es", "http://localhost:9000", "http://localhost:8000"]
|
||||
}
|