Markdown interpretation (#237)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
//Setup
|
||||
export default async function({login, data, rest, q, account, imports}, {enabled = false} = {}) {
|
||||
export default async function({login, data, rest, q, account, imports}, {enabled = false, markdown = "inline"} = {}) {
|
||||
//Plugin execution
|
||||
try {
|
||||
//Check if plugin is enabled and requirements are met
|
||||
@@ -18,6 +18,7 @@
|
||||
let {limit, days, filter, visibility, timestamps} = imports.metadata.plugins.activity.inputs({data, q, account})
|
||||
if (!days)
|
||||
days = Infinity
|
||||
const codelines = 2
|
||||
|
||||
//Get user recent activity
|
||||
console.debug(`metrics/compute/${login}/plugins > activity > querying api`)
|
||||
@@ -25,11 +26,11 @@
|
||||
console.debug(`metrics/compute/${login}/plugins > activity > ${events.length} events loaded`)
|
||||
|
||||
//Extract activity events
|
||||
const activity = events
|
||||
const activity = (await Promise.all(events
|
||||
.filter(({actor}) => account === "organization" ? true : actor.login === login)
|
||||
.filter(({created_at}) => Number.isFinite(days) ? new Date(created_at) > new Date(Date.now()-days*24*60*60*1000) : true)
|
||||
.filter(event => visibility === "public" ? event.public : true)
|
||||
.map(({type, payload, actor:{login:actor}, repo:{name:repo}, created_at}) => {
|
||||
.map(async({type, payload, actor:{login:actor}, repo:{name:repo}, created_at}) => {
|
||||
//See https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/github-event-types
|
||||
const timestamp = new Date(created_at)
|
||||
switch (type) {
|
||||
@@ -38,7 +39,7 @@
|
||||
if (!["created"].includes(payload.action))
|
||||
return null
|
||||
const {comment:{user:{login:user}, commit_id:sha, body:content}} = payload
|
||||
return {type:"comment", on:"commit", actor, timestamp, repo, content, user, mobile:null, number:sha.substring(0, 7), title:""}
|
||||
return {type:"comment", on:"commit", actor, timestamp, repo, content:await imports.markdown(content, {mode:markdown, codelines}), user, mobile:null, number:sha.substring(0, 7), title:""}
|
||||
}
|
||||
//Created a git branch or tag
|
||||
case "CreateEvent":{
|
||||
@@ -64,14 +65,14 @@
|
||||
if (!["created"].includes(payload.action))
|
||||
return null
|
||||
const {issue:{user:{login:user}, title, number}, comment:{body:content, performed_via_github_app:mobile}} = payload
|
||||
return {type:"comment", on:"issue", actor, timestamp, repo, content, user, mobile, number, title}
|
||||
return {type:"comment", on:"issue", actor, timestamp, repo, content:await imports.markdown(content, {mode:markdown, codelines}), user, mobile, number, title}
|
||||
}
|
||||
//Issue event
|
||||
case "IssuesEvent":{
|
||||
if (!["opened", "closed", "reopened"].includes(payload.action))
|
||||
return null
|
||||
const {action, issue:{user:{login:user}, title, number, body:content}} = payload
|
||||
return {type:"issue", actor, timestamp, repo, action, user, number, title, content}
|
||||
return {type:"issue", actor, timestamp, repo, action, user, number, title, content:await imports.markdown(content, {mode:markdown, codelines})}
|
||||
}
|
||||
//Activity from repository collaborators
|
||||
case "MemberEvent":{
|
||||
@@ -89,7 +90,7 @@
|
||||
if (!["opened", "closed"].includes(payload.action))
|
||||
return null
|
||||
const {action, pull_request:{user:{login:user}, title, number, body:content, additions:added, deletions:deleted, changed_files:changed, merged}} = payload
|
||||
return {type:"pr", actor, timestamp, repo, action:(action === "closed")&&(merged) ? "merged" : action, user, title, number, content, lines:{added, deleted}, files:{changed}}
|
||||
return {type:"pr", actor, timestamp, repo, action:(action === "closed")&&(merged) ? "merged" : action, user, title, number, content:await imports.markdown(content, {mode:markdown, codelines}), lines:{added, deleted}, files:{changed}}
|
||||
}
|
||||
//Reviewed a pull request
|
||||
case "PullRequestReviewEvent":{
|
||||
@@ -101,7 +102,7 @@
|
||||
if (!["created"].includes(payload.action))
|
||||
return null
|
||||
const {pull_request:{user:{login:user}, title, number}, comment:{body:content, performed_via_github_app:mobile}} = payload
|
||||
return {type:"comment", on:"pr", actor, timestamp, repo, content, user, mobile, number, title}
|
||||
return {type:"comment", on:"pr", actor, timestamp, repo, content:await imports.markdown(content, {mode:markdown, codelines}), user, mobile, number, title}
|
||||
}
|
||||
//Pushed commits
|
||||
case "PushEvent":{
|
||||
@@ -113,7 +114,7 @@
|
||||
if (!["published"].includes(payload.action))
|
||||
return null
|
||||
const {action, release:{name, prerelease, draft, body:content}} = payload
|
||||
return {type:"release", actor, timestamp, repo, action, name, prerelease, draft, content}
|
||||
return {type:"release", actor, timestamp, repo, action, name, prerelease, draft, content:await imports.markdown(content, {mode:markdown, codelines})}
|
||||
}
|
||||
//Starred a repository
|
||||
case "WatchEvent":{
|
||||
@@ -127,7 +128,7 @@
|
||||
return null
|
||||
}
|
||||
}
|
||||
})
|
||||
})))
|
||||
.filter(event => event)
|
||||
.filter(event => filter.includes("all") || filter.includes(event.type))
|
||||
.slice(0, limit)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
return null
|
||||
|
||||
//Load inputs
|
||||
let {sections, user, limit, lines} = imports.metadata.plugins.stackoverflow.inputs({data, account, q})
|
||||
let {sections, user, limit, lines, "lines.snippet":codelines} = imports.metadata.plugins.stackoverflow.inputs({data, account, q})
|
||||
if (!user)
|
||||
throw {error:{message:"You must provide a stackoverflow user id"}}
|
||||
|
||||
@@ -32,14 +32,14 @@
|
||||
//Load and format answers
|
||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for ${key}`)
|
||||
const {data:{items}} = await imports.axios.get(`${api.user}/answers?site=stackoverflow&pagesize=${limit}&filter=${filters.answer}&${sort}`)
|
||||
result[key] = items.map(item => format.answer(item, {imports, data}))
|
||||
result[key] = await Promise.all(items.map(item => format.answer(item, {imports, data, codelines})))
|
||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loaded ${result[key].length} items`)
|
||||
//Load related questions
|
||||
const ids = result[key].map(({question_id}) => question_id).filter(id => id)
|
||||
if (ids) {
|
||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loading ${ids.length} related items`)
|
||||
const {data:{items}} = await imports.axios.get(`${api.base}/questions/${ids.join(";")}?site=stackoverflow&filter=${filters.question}`)
|
||||
items.map(item => format.question(item, {imports, data}))
|
||||
await Promise.all(items.map(item => format.question(item, {imports, data, codelines})))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,14 +48,14 @@
|
||||
//Load and format questions
|
||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for ${key}`)
|
||||
const {data:{items}} = await imports.axios.get(`${api.user}/questions?site=stackoverflow&pagesize=${limit}&filter=${filters.question}&${sort}`)
|
||||
result[key] = items.map(item => format.question(item, {imports, data}))
|
||||
result[key] = await Promise.all(items.map(item => format.question(item, {imports, data, codelines})))
|
||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loaded ${result[key].length} items`)
|
||||
//Load related answers
|
||||
const ids = result[key].map(({accepted_answer_id}) => accepted_answer_id).filter(id => id)
|
||||
if (ids) {
|
||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loading ${ids.length} related items`)
|
||||
const {data:{items}} = await imports.axios.get(`${api.base}/answers/${ids.join(";")}?site=stackoverflow&filter=${filters.answer}`)
|
||||
items.map(item => format.answer(item, {imports, data}))
|
||||
await Promise.all(items.map(item => format.answer(item, {imports, data, codelines})))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,9 +74,13 @@
|
||||
const format = {
|
||||
/**Cached */
|
||||
cached:new Map(),
|
||||
/**Format stackoverflow code snippets */
|
||||
code(text) {
|
||||
return text.replace(/<!-- language: lang-(?<lang>\w+) -->\s*(?<snippet> {4}[\s\S]+?)(?=(?:<!-- end snippet -->)|(?:<!-- language: lang-))/g, "```$<lang>\n$<snippet>```")
|
||||
},
|
||||
/**Format answers */
|
||||
answer({body_markdown:body, score, up_vote_count:upvotes, down_vote_count:downvotes, is_accepted:accepted, comment_count:comments = 0, creation_date, owner:{display_name:author}, link, answer_id:id, question_id}, {imports, data}) {
|
||||
const formatted = {type:"answer", body:imports.htmlunescape(body), score, upvotes, downvotes, accepted, comments, author, created:imports.date(creation_date*1000, {dateStyle:"short", timeZone:data.config.timezone?.name}), link, id, question_id,
|
||||
async answer({body_markdown:body, score, up_vote_count:upvotes, down_vote_count:downvotes, is_accepted:accepted, comment_count:comments = 0, creation_date, owner:{display_name:author}, link, answer_id:id, question_id}, {imports, data, codelines}) {
|
||||
const formatted = {type:"answer", body:await imports.markdown(format.code(imports.htmlunescape(body)), {codelines}), score, upvotes, downvotes, accepted, comments, author, created:imports.date(creation_date*1000, {dateStyle:"short", timeZone:data.config.timezone?.name}), link, id, question_id,
|
||||
get question() {
|
||||
return format.cached.get(`q${this.question_id}`) ?? null
|
||||
},
|
||||
@@ -85,8 +89,8 @@
|
||||
return formatted
|
||||
},
|
||||
/**Format questions */
|
||||
question({title, body_markdown:body, score, up_vote_count:upvotes, down_vote_count:downvotes, favorite_count:favorites, tags, is_answered:answered, answer_count:answers, comment_count:comments, view_count:views, creation_date, owner:{display_name:author}, link, question_id:id, accepted_answer_id = null}, {imports, data}) {
|
||||
const formatted = {type:"question", title:imports.htmlunescape(title), body:imports.htmlunescape(body), score, upvotes, downvotes, favorites, tags, answered, answers, comments, views, author, created:imports.date(creation_date*1000, {dateStyle:"short", timeZone:data.config.timezone?.name}), link, id, accepted_answer_id,
|
||||
async question({title, body_markdown:body, score, up_vote_count:upvotes, down_vote_count:downvotes, favorite_count:favorites, tags, is_answered:answered, answer_count:answers, comment_count:comments, view_count:views, creation_date, owner:{display_name:author}, link, question_id:id, accepted_answer_id = null}, {imports, data, codelines}) {
|
||||
const formatted = {type:"question", title:await imports.markdown(title), body:await imports.markdown(format.code(imports.htmlunescape(body)), {codelines}), score, upvotes, downvotes, favorites, tags, answered, answers, comments, views, author, created:imports.date(creation_date*1000, {dateStyle:"short", timeZone:data.config.timezone?.name}), link, id, accepted_answer_id,
|
||||
get answer() {
|
||||
return format.cached.get(`a${this.accepted_answer_id}`) ?? null
|
||||
},
|
||||
|
||||
@@ -41,9 +41,18 @@ inputs:
|
||||
max: 30
|
||||
|
||||
# Number of lines to display per question or answer
|
||||
# Code snippets will take one line slot and should be configured with "plugin_stackoverflow_lines_snippet" instead
|
||||
# Set to 0 to disable limitations
|
||||
plugin_stackoverflow_lines:
|
||||
description: Maximum number of lines to display per question or answer
|
||||
type: number
|
||||
default: 4
|
||||
min: 0
|
||||
min: 0
|
||||
|
||||
# Number of lines to display per code snippet
|
||||
# Set to 0 to disable limitations
|
||||
plugin_stackoverflow_lines_snippet:
|
||||
description: Maximum number of lines to display per code snippet
|
||||
type: number
|
||||
default: 2
|
||||
min: 0
|
||||
|
||||
Reference in New Issue
Block a user