Last active
February 26, 2024 07:26
-
-
Save 958/6820041 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Info | |
let PLUGIN_INFO = | |
<KeySnailPlugin> | |
<name>RedmineSnail</name> | |
<description>Redmine client</description> | |
<updateURL>https://gist.github.com/958/6820041/raw/redmine.ks.js</updateURL> | |
<author>958</author> | |
<version>0.0.1</version> | |
<license>MIT</license> | |
<include>main</include> | |
<detail lang="ja"><![CDATA[ | |
=== 使い方 === | |
]]></detail> | |
</KeySnailPlugin>; | |
// Option | |
let pOptions = plugins.setupOptions("redmine", { | |
"issues-keymap": { | |
preset: { | |
"C-z" : "prompt-toggle-edit-mode", | |
"SPC" : "prompt-next-page", | |
// redmine specific actions | |
"O" : "open-issue,c", | |
"E" : "edit-issue,c", | |
"P" : "select-project", | |
"S" : "change-status", | |
"p" : "change-priority", | |
"r" : "change-ratio", | |
"t" : "change-tracker", | |
"A" : "change-assign", | |
"C" : "new-issue", | |
"w" : "start-change-wizard", | |
}, | |
description: M({ | |
ja: "チケット一覧のキーマップ", | |
en: "Issues keymap" | |
}) | |
}, | |
}, PLUGIN_INFO); | |
// Setting | |
var settings = (function() { | |
const sitesKey = 'rmine_sites'; | |
const infoKey = 'rmine_info'; | |
return { | |
get info() { | |
return persist.restore(infoKey); | |
}, | |
set info(val) { | |
persist.preserve(val, infoKey); | |
}, | |
get sites() { | |
return persist.restore(sitesKey); | |
}, | |
set sites(val) { | |
persist.preserve(val, sitesKey); | |
}, | |
}; | |
})(); | |
// Site | |
var Site = (function() { | |
var _siteUrl, _apiKey; | |
var Site = function(siteUrl, apiKey) { | |
_siteUrl = siteUrl; | |
_apiKey = apiKey; | |
} | |
function createParams(params) { | |
for (var i in params) { | |
if (params[i]) | |
params[i] = encodeURIComponent(params[i]); | |
else | |
delete params[i]; | |
} | |
return params; | |
} | |
function requestPost(endpoint, params, onSuccess, onError) { | |
var url = _siteUrl + endpoint; | |
//params = createParams(params); | |
util.request('POST', url, { | |
header: { | |
'X-Redmine-API-Key': _apiKey, | |
'Content-Type': 'application/json', | |
}, | |
params: JSON.stringify(params), | |
callback: function(xhr) { | |
if (xhr.status == 200) { | |
if (onSuccess) onSuccess(JSON.parse(xhr.responseText)); | |
} else { | |
if (onError) onError(JSON.parse(xhr.responseText)); | |
} | |
} | |
}); | |
} | |
function requestPut(endpoint, params, onSuccess, onError) { | |
var url = _siteUrl + endpoint; | |
//params = createParams(params); | |
util.request('PUT', url, { | |
header: { | |
'X-Redmine-API-Key': _apiKey, | |
'Content-Type': 'application/json', | |
}, | |
params: JSON.stringify(params), | |
callback: function(xhr) { | |
util.fbug(xhr.status); | |
util.fbug(xhr.responseText); | |
if (xhr.status == 200) { | |
//if (onSuccess) onSuccess(JSON.parse(xhr.responseText)); | |
} else { | |
//if (onError) onError(JSON.parse(xhr.responseText)); | |
} | |
} | |
}); | |
} | |
function requestGet(endpoint, params, onSuccess, onError) { | |
var url = _siteUrl + endpoint; | |
params = createParams(params); | |
url += '?' + util.paramsToString(params); | |
util.request('GET', url, { | |
header: { | |
'X-Redmine-API-Key': _apiKey, | |
}, | |
callback: function(xhr) { | |
if (xhr.status == 200) { | |
if (onSuccess) onSuccess(JSON.parse(xhr.responseText)); | |
} else { | |
if (onError) onError(JSON.parse(xhr.responseText)); | |
} | |
} | |
}); | |
} | |
function requestDelete(endpoint, params, onSuccess, onError) { | |
var url = _siteUrl + endpoint; | |
params = createParams(params); | |
util.request('DELETE', url, { | |
header: { | |
'X-Redmine-API-Key': _apiKey, | |
}, | |
callback: function(xhr) { | |
if (xhr.status == 200) { | |
if (onSuccess) onSuccess(JSON.parse(xhr.responseText)); | |
} else { | |
if (onError) onError(JSON.parse(xhr.responseText)); | |
} | |
} | |
}); | |
} | |
function openUrl(url, where) { | |
openUILinkIn(_siteUrl + url, where || 'current'); | |
} | |
Site.prototype = { | |
requestPost: requestPost, | |
requestPut: requestPut, | |
requestGet: requestGet, | |
requestDelete: requestDelete, | |
openUrl: openUrl, | |
}; | |
return Site; | |
})(); | |
// Redmine API | |
var Api = (function() { | |
var _site; | |
function Api(siteUrl, apiKey) { | |
_site = new Site(siteUrl, apiKey); | |
} | |
function getProjects(callback) { | |
_site.requestGet('projects.json', {}, | |
function(res) { if (callback) callback(res); }, | |
function(res) { display.echoStatusBar('Request failed.'); if (callback) callback(res); } | |
); | |
} | |
function openProject(project, where) { | |
_site.openUrl('projects/' + project.identifier, where); | |
} | |
function getAllIssues(filter, callback) { | |
_site.requestGet('issues.json', | |
_.extend({ limit: 100 }, filter || {}), | |
function(res) { if (callback) callback(res); }, | |
function(res) { display.echoStatusBar('Request failed.'); if (callback) callback(res); } | |
); | |
} | |
function getProjectIssues(project, filter, callback) { | |
getAllIssues(_.extend({ project_id: project.id }, filter || {}), callback); | |
} | |
function updateIssue(issue, params, callback) { | |
_site.requestPut('issues/' + issue.id + '.json', | |
params, | |
function(res) { if (callback) callback(res); }, | |
function(res) { display.echoStatusBar('Request failed.'); if (callback) callback(res); } | |
); | |
} | |
function createIssue(project, where) { | |
_site.openUrl('projects/' + project.identifier + '/issues/new', where); | |
} | |
function openIssue(issue, where, edit) { | |
_site.openUrl('issues/' + issue.id + (edit ? '/edit' : ''), where); | |
} | |
function getIssueStatuses(callback) { | |
_site.requestGet('issue_statuses.json', {}, | |
function(res) { if (callback) callback(res); }, | |
function(res) { display.echoStatusBar('Request failed.'); if (callback) callback(res); } | |
); | |
} | |
function getProjectMemberships(project, callback) { | |
_site.requestGet('projects/' + project.id + '/memberships.json', {}, | |
function(res) { if (callback) callback(res); }, | |
function(res) { display.echoStatusBar('Request failed.'); if (callback) callback(res); } | |
); | |
} | |
Api.prototype = { | |
getProjects: getProjects, | |
openProject: openProject, | |
getProjectIssues: getProjectIssues, | |
getAllIssues: getAllIssues, | |
updateIssue: updateIssue, | |
createIssue: createIssue, | |
openIssue: openIssue, | |
getIssueStatuses: getIssueStatuses, | |
getProjectMemberships: getProjectMemberships, | |
}; | |
return Api; | |
})(); | |
// Main | |
var rmine = (function() { | |
var _info = settings.info || {}; | |
var _sites = settings.sites || {}; | |
var _api; | |
function setCurrentSite(url) { | |
_api = new Api(url, _sites[url].apiKey); | |
_info.currentSite = url; | |
_info.currentProject = 0; | |
_project = null; | |
settings.info = _info; | |
} | |
function setCurrentProject(project) { | |
_info.currentProject = project.id; | |
settings.info = _info; | |
} | |
function addSite() { | |
var url, apiKey; | |
function readUrl() { | |
prompt.read('Please enter redmine URL :', function(str){ | |
if (str != null) { | |
url = (str[str.length - 1] != '/') ? str + '/' : str; | |
readApiKey(); | |
} | |
}); | |
} | |
function readApiKey() { | |
prompt.read('Please enter your API key :', function(str){ | |
if (str != null) { | |
apiKey = str; | |
_sites[url] = { apiKey: apiKey }; | |
settings.sites = _sites; | |
setCurrentSite(url); | |
setTimeout(function() showProjects(), 0); | |
} | |
}); | |
} | |
readUrl(); | |
} | |
function selectSite() { | |
var collection = []; | |
for (var url in _sites) collection.push([ url ]); | |
prompt.selector({ | |
message : 'Select site :', | |
collection : collection, | |
header : ['URL'], | |
actions : [ | |
[ | |
function(index) { | |
if (index < 0) return; | |
setCurrentSite(collection[index]) | |
setTimeout(function() showProjects(!self.site.projects), 0); | |
}, | |
'Select site', 'select-site' | |
], | |
] | |
}); | |
} | |
function showProjects(isLoad) { | |
function showSelector(projects) { | |
prompt.selector({ | |
message : 'Select project :', | |
collection : projects.map(function(p) [p.name, p.description, p]), | |
header : ['Name', 'Description'], | |
flags : [0, 0, HIDDEN|IGNORE], | |
actions : [ | |
[ | |
function(index) { | |
if (index < 0) return; | |
setCurrentProject(projects[index]); | |
setTimeout(function() showProjectIssues(projects[index]), 0); | |
}, | |
'Select site', 'select-site' | |
], | |
] | |
}); | |
} | |
if (isLoad || !_sites[_info.currentSite].projects) | |
_api.getProjects(function(res) { | |
_sites[_info.currentSite].projects = res.projects; | |
settings.sites = _sites; | |
showSelector(res.projects); | |
}); | |
else | |
showSelector(_sites[_info.currentSite].projects); | |
} | |
function showIssuesSelector(project, issues) { | |
prompt.selector({ | |
message : 'Select issue :', | |
collection : issues.map(function(i) { | |
return [ | |
i.id, | |
(i.tracker) ? i.tracker.name : '' , | |
(i.category) ? i.category.name : '', | |
i.status.name, | |
i.subject, | |
(i.fixed_version) ? i.fixed_version.name : '', | |
(i.assigned_to) ? i.assigned_to.name : '', | |
i.project.name, | |
i] | |
}), | |
header : ['ID', 'Tracker', 'Category', 'Status', 'Subject', 'Fixed version', 'Assigned', 'Project'], | |
width : [5, 5, 5, 5, 50, 10, 10, 10], | |
flags : [0, 0, 0, 0, 0, 0, 0, 0, HIDDEN|IGNORE], | |
keymap : pOptions['issues-keymap'], | |
actions : [ | |
[ | |
function(index) { | |
if (index < 0) return; | |
_api.openIssue(issues[index], 'tab'); | |
}, | |
'Open issue', 'open-issue' | |
], | |
[ | |
function(index) { | |
if (index < 0) return; | |
_api.openIssue(issues[index], 'tab-shifted'); | |
}, | |
'Open issue for background', 'open-issue-background' | |
], | |
[ | |
function(index) { | |
if (index < 0) return; | |
_api.openIssue(issues[index], 'tab', true); | |
}, | |
'Edit issue', 'edit-issue' | |
], | |
[ | |
function(index) { | |
setTimeout(showProjects, 0); | |
}, | |
'Select project', 'select-project' | |
], | |
[ | |
function(index) { | |
if (index < 0) return; | |
setTimeout(function() { | |
selectIssueStatuses(issues[index], function(status) { | |
selectAssignedTo(issues[index], function(assignedTo) { | |
inputComment(function(comment) { | |
_api.updateIssue(issues[index], { | |
issue: { | |
status_id: status.id, | |
assigned_to_id: assignedTo.id, | |
notes: comment | |
} | |
}, | |
function() display.echoStatusBar('Done')); | |
}); | |
}); | |
}); | |
}, 0); | |
}, | |
'Change status', 'change-status' | |
], | |
[ | |
function(index) { | |
if (project) | |
_api.createIssue(project, 'tab'); | |
}, | |
'New issue', 'new-issue' | |
], | |
] | |
}); | |
} | |
function showProjectIssues(project, filter) { | |
project = project || self.project; | |
if (project) | |
_api.getProjectIssues(project, filter, function(res) { | |
showIssuesSelector(project, res.issues); | |
}); | |
else | |
showProjects(); | |
} | |
function showAllIssues(filter) { | |
_api.getAllIssues(filter, function(res) { | |
showIssuesSelector(null, res.issues); | |
}); | |
} | |
function selectIssueStatuses(issue, callback) { | |
_api.getIssueStatuses(function(res) { | |
var statuses = res.issue_statuses.map(function(s) [s.name, s.id]); | |
var initialIndex = 0; | |
res.issue_statuses.some(function(s, i) { | |
if (s.id == issue.status.id) { | |
initialIndex = i; | |
return true; | |
} | |
}); | |
prompt.selector({ | |
message : 'Select status :', | |
collection : statuses, | |
flags : [0, HIDDEN|IGNORE], | |
initialIndex: initialIndex, | |
actions : [ | |
[ | |
function(index) { | |
if (index < 0 || !callback) return; | |
setTimeout(function() callback({ name: statuses[index][0], id: statuses[index][1] }), 0); | |
}, | |
'Select status', 'select-status' | |
], | |
] | |
}); | |
}); | |
} | |
function selectAssignedTo(issue, callback) { | |
_api.getProjectMemberships(issue.project, function(res) { | |
var members = res.memberships.map(function(m) [m.user.name, m.user.id]); | |
var initialIndex = 0; | |
res.memberships.some(function(m, i) { | |
if (m.user.id == issue.assigned_to.id) { | |
initialIndex = i; | |
return true; | |
} | |
}); | |
prompt.selector({ | |
message : 'Select assigned member :', | |
collection : members, | |
initialIndex: initialIndex, | |
flags : [0, HIDDEN|IGNORE], | |
actions : [ | |
[ | |
function(index) { | |
if (index < 0 || !callback) return; | |
setTimeout(function() callback({ name: members[index][0], id: members[index][1] }), 0); | |
}, | |
'Select member', 'select-member' | |
], | |
] | |
}); | |
}); | |
} | |
function inputComment(callback) { | |
prompt.reader({ | |
message: 'Please enter comment:', | |
callback: function(str) { | |
if (str) | |
!callback || callback(str); | |
}, | |
onFinish: function() prompt.multiLine = false, | |
}); | |
prompt.multiLine = true; | |
setTimeout(function() document.getElementById("keysnail-prompt-textbox").focus(), 0); | |
} | |
var self = { | |
get site() { | |
return _sites[_info.currentSite]; | |
}, | |
get project() { | |
var prjs = self.site.projects.filter(function(p) p.id == _info.currentProject); | |
return (prjs.length > 0) ? prjs[0] : null; | |
}, | |
addSite: addSite, | |
selectSite: selectSite, | |
showProjects: showProjects, | |
showProjectIssues: showProjectIssues, | |
showAllIssues: showAllIssues, | |
}; | |
// Create pre selected site API | |
if (self.site) | |
_api = new Api(_info.currentSite, self.site.apiKey); | |
return self; | |
})(); | |
plugins.rmine = rmine; | |
// Add ext | |
plugins.withProvides(function (provide) { | |
provide('rmine-add-site', | |
function (ev, arg) { | |
rmine.addSite(); | |
}, | |
M({en:'rmine - Add redmine site', ja:'rmine - サイトを追加'})); | |
provide('rmine-show-projects', | |
function (ev, arg) { | |
rmine.showProjects(arg); | |
}, | |
M({en:'rmine - Show projects', ja:'rmine - プロジェクト一覧を表示'})); | |
provide('rmine-show-project-issues', | |
function (ev, arg) { | |
rmine.showProjectIssues(); | |
}, | |
M({en:'rmine - Show project issues', ja:'rmine - プロジェクトのチケット一覧を表示'})); | |
provide('rmine-show-all-issues', | |
function (ev, arg) { | |
rmine.showAllIssues(); | |
}, | |
M({en:'rmine - Show all issues', ja:'rmine - すべてのチケット一覧を表示'})); | |
}, PLUGIN_INFO); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment