Add stackoverflow plugin (#159)

This commit is contained in:
Simon Lecoq
2021-02-27 23:24:51 +01:00
committed by GitHub
parent a43f217297
commit cfca5d3892
11 changed files with 563 additions and 3 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -91,8 +91,8 @@
return string
.replace(/&lt;/g, u["<"] ? "<" : "&lt;")
.replace(/&gt;/g, u[">"] ? ">" : "&gt;")
.replace(/&quot;/g, u['"'] ? '"' : '&quot;')
.replace(/&apos;/g, u["'"] ? "'" : "&apos;")
.replace(/&quot;/g, u['"'] ? '"' : "&quot;")
.replace(/&(?:apos|#39);/g, u["'"] ? "'" : "&apos;")
.replace(/&amp;/g, u["&"] ? "&" : "&amp;")
}

View File

@@ -0,0 +1,99 @@
/**Mocked data */
export default function({faker, url, options, login = faker.internet.userName()}) {
//Stackoverflow api
if (/^https:..api.stackexchange.com.2.2.*$/.test(url)) {
//Extract user id
const user_id = url.match(/[/]users[/](?<id>\d+)/)?.groups?.id ?? NaN
const pagesize = Number(url.match(/pagesize=(?<pagesize>\d+)/)?.groups?.pagesize) || 30
//User account
if (/users[/]\d+[/][?]site=stackoverflow$/.test(url)) {
console.debug(`metrics/compute/mocks > mocking stackoverflow api result > ${url}`)
return ({
status:200,
data:{
items:[
{
badge_counts:{bronze:faker.random.number(500), silver:faker.random.number(300), gold:faker.random.number(100)},
accept_rate:faker.random.number(100),
answer_count:faker.random.number(1000),
question_count:faker.random.number(1000),
view_count:faker.random.number(10000),
creation_date:faker.date.past(),
display_name:faker.internet.userName(),
user_id,
reputation:faker.random.number(100000),
},
],
has_more:false,
quota_max:300,
quota_remaining:faker.random.number(300),
},
})
}
//Total metrics
if (/[?]site=stackoverflow&filter=total$/.test(url)) {
console.debug(`metrics/compute/mocks > mocking stackoverflow api result > ${url}`)
return ({
status:200,
data:{
total:faker.random.number(10000),
},
})
}
//Questions
if ((/questions[?]site=stackoverflow/.test(url))||(/questions[/][\d;]+[?]site=stackoverflow/.test(url))) {
console.debug(`metrics/compute/mocks > mocking stackoverflow api result > ${url}`)
return ({
status:200,
data:{
items:new Array(pagesize).fill(null).map(_ => ({
tags:new Array(5).fill(null).map(_ => faker.lorem.slug()),
owner:{display_name:faker.internet.userName()},
is_answered:faker.random.boolean(),
view_count:faker.random.number(10000),
accepted_answer_id:faker.random.number(1000000),
answer_count:faker.random.number(100),
score:faker.random.number(1000),
creation_date:faker.time.recent(),
down_vote_count:faker.random.number(1000),
up_vote_count:faker.random.number(1000),
comment_count:faker.random.number(1000),
favorite_count:faker.random.number(1000),
title:faker.lorem.sentence(),
body_markdown:faker.lorem.paragraphs(),
link:faker.internet.url(),
question_id:faker.random.number(1000000),
})),
has_more:false,
quota_max:300,
quota_remaining:faker.random.number(300),
},
})
}
//Answers
if ((/answers[?]site=stackoverflow/.test(url))||(/answers[/][\d;]+[?]site=stackoverflow/.test(url))) {
console.debug(`metrics/compute/mocks > mocking stackoverflow api result > ${url}`)
return ({
status:200,
data:{
items:new Array(pagesize).fill(null).map(_ => ({
owner:{display_name:faker.internet.userName()},
link:faker.internet.url(),
is_accepted:faker.random.boolean(),
score:faker.random.number(1000),
down_vote_count:faker.random.number(1000),
up_vote_count:faker.random.number(1000),
comment_count:faker.random.number(1000),
creation_date:faker.time.recent(),
question_id:faker.random.number(1000000),
body_markdown:faker.lorem.paragraphs(),
answer_id:faker.random.number(1000000),
})),
has_more:false,
quota_max:300,
quota_remaining:faker.random.number(300),
},
})
}
}
}

View File

@@ -651,6 +651,65 @@
duration:options["isocalendar.duration"]
}
}) : null),
//Stackoverflow
...(set.plugins.enabled.stackoverflow ? ({
stackoverflow:{
sections:options["stackoverflow.sections"].split(",").map(x => x.trim()).filter(x => x),
lines:options["stackoverflow.lines"],
user:{
reputation:faker.random.number(100000),
badges:faker.random.number(1000),
questions:faker.random.number(1000),
answers:faker.random.number(1000),
comments:faker.random.number(1000),
views:faker.random.number(1000),
},
"answers-top":new Array(options["stackoverflow.limit"]).fill(null).map(_ => ({
type:"answer",
body:faker.lorem.paragraphs(),
score:faker.random.number(1000),
upvotes:faker.random.number(1000),
downvotes:faker.random.number(1000),
accepted:faker.random.boolean(),
comments:faker.random.number(1000),
author:set.user,
created:"01/01/1970",
link:null,
id:faker.random.number(100000),
question_id:faker.random.number(100000),
question:{
title:faker.lorem.sentence(),
tags:[faker.lorem.slug(), faker.lorem.slug()],
}
})),
get ["answers-recent"]() {
return this["answers-top"]
},
"questions-top":new Array(options["stackoverflow.limit"]).fill(null).map(_ => ({
type:"question",
title:faker.lorem.sentence(),
body:faker.lorem.paragraphs(),
score:faker.random.number(1000),
upvotes:faker.random.number(1000),
downvotes:faker.random.number(1000),
favorites:faker.random.number(1000),
tags:[faker.lorem.slug(), faker.lorem.slug()],
answered:faker.random.boolean(),
answers:faker.random.number(1000),
comments:faker.random.number(1000),
views:faker.random.number(1000),
author:set.user,
created:"01/01/1970",
link:null,
id:faker.random.number(100000),
accepted_answer_id:faker.random.number(100000),
answer:null,
})),
get ["questions-recent"]() {
return this["questions-top"]
},
}
}) : null),
},
}
//Formatters

View File

@@ -0,0 +1,36 @@
### 🗨️ Stackoverflow plugin
The *stackoverflow* plugin lets you display your metrics, questions and answer from [stackoverflow](https://stackoverflow.com/).
<table>
<td align="center">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.stackoverflow.svg">
<img width="900" height="1" alt="">
</td>
</table>
<details>
<summary>💬 Get your user id</summary>
Go to [stackoverflow.com](https://stackoverflow.com/) and click on your account profile.
Your user id will be in both url and search bar.
![User id](/.github/readme/imgs/plugin_stackoverflow_user_id.png)
</details>
#### Examples workflows
[➡️ Available options for this plugin](metadata.yml)
```yaml
- uses: lowlighter/metrics@latest
with:
# ... other options
plugin_stackoverflow: yes
plugin_stackoverflow_user: 8332505 # Stackoverflow user id (required)
plugin_stackoverflow_sections: answers-top, questions-recent # Display top answers and recent questions
plugin_stackoverflow_limit: 2 # Display 2 entries per section
plugin_stackoverflow_lines: 4 # Display 4 lines per entry
```

View File

@@ -0,0 +1,97 @@
//Setup
export default async function({login, q, imports, data, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
if ((!enabled)||(!q.stackoverflow))
return null
//Load inputs
let {sections, user, limit, lines} = imports.metadata.plugins.stackoverflow.inputs({data, account, q})
if (!user)
throw {error:{message:"You must provide a stackoverflow user id"}}
//Initialization
//See https://api.stackexchange.com/docs
const api = {base:"https://api.stackexchange.com/2.2", user:`https://api.stackexchange.com/2.2/users/${user}`}
const filters = {user:"!0Z-LvgkLYnTCu1858)*D0lcx2", answer:"!7goY5TLWwCz.BaGpe)tv5C6Bks2q8siMH6", question:"!)EhwvzgX*hrClxjLzqxiZHHbTPRE5Pb3B9vvRaqCx5-ZY.vPr"}
const result = {sections, lines}
//Stackoverflow user metrics
{
//Account metrics
console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for user ${user}`)
const {data:{items:[{reputation, badge_counts:{bronze, silver, gold}, answer_count:answers, question_count:questions, view_count:views}]}} = await imports.axios.get(`${api.user}?site=stackoverflow&filter=${filters.user}`)
const {data:{total:comments}} = await imports.axios.get(`${api.user}/comments?site=stackoverflow&filter=total`)
//Save result
result.user = {reputation, badges:bronze+silver+gold, questions, answers, comments, views}
}
//Answers
for (const {key, sort} of [{key:"answers-recent", sort:"sort=activity&order=desc"}, {key:"answers-top", sort:"sort=votes&order=desc"}].filter(({key}) => sections.includes(key))) {
//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}))
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}))
}
}
//Questions
for (const {key, sort} of [{key:"questions-recent", sort:"sort=activity&order=desc"}, {key:"questions-top", sort:"sort=votes&order=desc"}].filter(({key}) => sections.includes(key))) {
//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}))
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}))
}
}
//Results
return result
}
//Handle errors
catch (error) {
if (error.error?.message)
throw error
throw {error:{message:"An error occured", instance:error}}
}
}
//Formatters
const format = {
/**Cached */
cached:new Map(),
/**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,
get question() {
return format.cached.get(`q${this.question_id}`) ?? null
},
}
this.cached.set(`a${id}`, formatted)
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,
get answer() {
return format.cached.get(`a${this.accepted_answer_id}`) ?? null
},
}
this.cached.set(`q${id}`, formatted)
return formatted
},
}

View File

@@ -0,0 +1,48 @@
name: "🗨️ Stackoverflow plugin"
cost: N/A
categorie: social
supports:
- user
- organization
inputs:
# Enable or disable plugin
plugin_stackoverflow:
description: Stackoverflow metrics
type: boolean
default: no
# Stackoverflow user id
# To obtain it, extract the identifier on your account page url
plugin_stackoverflow_user:
description: Stackoverflow user id
type: number
default: 0
# Sections to display
plugin_stackoverflow_sections:
description: Sections to display
type: array
format: comma-separated
default: answers-top, questions-recent
values:
- answers-top # Display top answers
- answers-recent # Display recent answers
- questions-top # Display top questions
- questions-recent # Display recent questions
# Number of entries to display per section
plugin_stackoverflow_limit:
description: Maximum number of entries to display per section
type: number
default: 2
min: 1
max: 30
# Number of lines to display per question or answer
# 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

View File

@@ -0,0 +1,16 @@
- name: Stackoverflow plugin (default)
uses: lowlighter/metrics@latest
with:
token: MOCKED_TOKEN
plugin_stackoverflow: yes
plugin_stackoverflow_user: 1
- name: Stackoverflow plugin (complete)
uses: lowlighter/metrics@latest
with:
token: MOCKED_TOKEN
plugin_stackoverflow: yes
plugin_stackoverflow_user: 1
plugin_stackoverflow_sections: answers-top, answers-recent, questions-top, questions-recent
plugin_stackoverflow_limit: 2
plugin_stackoverflow_lines: 4

View File

@@ -21,5 +21,6 @@
"activity",
"anilist",
"wakatime",
"skyline"
"skyline",
"stackoverflow"
]

View File

@@ -0,0 +1,161 @@
<% if (plugins.stackoverflow) { %>
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.75 14A1.75 1.75 0 016 12.25v-8.5C6 2.784 6.784 2 7.75 2h6.5c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0114.25 14h-6.5zm-.25-1.75c0 .138.112.25.25.25h6.5a.25.25 0 00.25-.25v-8.5a.25.25 0 00-.25-.25h-6.5a.25.25 0 00-.25.25v8.5zM4.9 3.508a.75.75 0 01-.274 1.025.25.25 0 00-.126.217v6.5a.25.25 0 00.126.217.75.75 0 01-.752 1.298A1.75 1.75 0 013 11.25v-6.5c0-.649.353-1.214.874-1.516a.75.75 0 011.025.274zM1.625 5.533a.75.75 0 10-.752-1.299A1.75 1.75 0 000 5.75v4.5c0 .649.353 1.214.874 1.515a.75.75 0 10.752-1.298.25.25 0 01-.126-.217v-4.5a.25.25 0 01.126-.217z"></path></svg>
Stackoverflow metrics
</h2>
<% if (plugins.stackoverflow.error) { %>
<div class="row">
<section>
<div class="field error">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>
<%= plugins.stackoverflow.error.message %>
</div>
</section>
</div>
<% } else { %>
<div class="row">
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M8.5.75a.75.75 0 00-1.5 0v5.19L4.391 3.33a.75.75 0 10-1.06 1.061L5.939 7H.75a.75.75 0 000 1.5h5.19l-2.61 2.609a.75.75 0 101.061 1.06L7 9.561v5.189a.75.75 0 001.5 0V9.56l2.609 2.61a.75.75 0 101.06-1.061L9.561 8.5h5.189a.75.75 0 000-1.5H9.56l2.61-2.609a.75.75 0 00-1.061-1.06L8.5 5.939V.75z"></path></svg>
<%= plugins.stackoverflow.user.reputation %> reputation point<%= s(plugins.stackoverflow.user.reputation) %>
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zM6.92 6.085c.081-.16.19-.299.34-.398.145-.097.371-.187.74-.187.28 0 .553.087.738.225A.613.613 0 019 6.25c0 .177-.04.264-.077.318a.956.956 0 01-.277.245c-.076.051-.158.1-.258.161l-.007.004a7.728 7.728 0 00-.313.195 2.416 2.416 0 00-.692.661.75.75 0 001.248.832.956.956 0 01.276-.245 6.3 6.3 0 01.26-.16l.006-.004c.093-.057.204-.123.313-.195.222-.149.487-.355.692-.662.214-.32.329-.702.329-1.15 0-.76-.36-1.348-.863-1.725A2.76 2.76 0 008 4c-.631 0-1.155.16-1.572.438-.413.276-.68.638-.849.977a.75.75 0 101.342.67z"></path></svg>
<%= plugins.stackoverflow.user.questions %> question<%= s(plugins.stackoverflow.user.questions) %>
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.75 2.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 01.75.75v2.19l2.72-2.72a.75.75 0 01.53-.22h4.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25H2.75zM1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0113.25 12H9.06l-2.573 2.573A1.457 1.457 0 014 13.543V12H2.75A1.75 1.75 0 011 10.25v-7.5z"></path></svg>
<%= plugins.stackoverflow.user.comments %> comment<%= s(plugins.stackoverflow.user.comments) %>
</div>
</section>
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M3.637 2.291A.75.75 0 014.23 2h7.54a.75.75 0 01.593.291l3.48 4.5a.75.75 0 01-.072.999l-7.25 7a.75.75 0 01-1.042 0l-7.25-7a.75.75 0 01-.072-.999l3.48-4.5zM4.598 3.5L1.754 7.177 8 13.207l6.246-6.03L11.402 3.5H4.598z"></path></svg>
<%= plugins.stackoverflow.user.badges %> badge<%= s(plugins.stackoverflow.user.badges) %>
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM0 8a8 8 0 1116 0A8 8 0 010 8zm11.78-1.72a.75.75 0 00-1.06-1.06L6.75 9.19 5.28 7.72a.75.75 0 00-1.06 1.06l2 2a.75.75 0 001.06 0l4.5-4.5z"></path></svg>
<%= plugins.stackoverflow.user.answers %> answer<%= s(plugins.stackoverflow.user.answers) %>
</div>
</section>
</div>
<% if (plugins.stackoverflow.lines) { %>
<style>
.stackoverflow .body {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: <%= plugins.stackoverflow.lines %>;
}
</style>
<% } %>
<% for (const section of plugins.stackoverflow.sections) { if (!plugins.stackoverflow[section]?.length) continue %>
<div class="row fill-width">
<section>
<div class="stackoverflow">
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2 4a1 1 0 100-2 1 1 0 000 2zm3.75-1.5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zm0 5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zm0 5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zM3 8a1 1 0 11-2 0 1 1 0 012 0zm-1 6a1 1 0 100-2 1 1 0 000 2z"></path></svg>
<%= {"questions-recent":"Recent questions", "questions-top":"Top questions", "answers-recent":"Recent answers", "answers-top":"Top answers"}[section] %>
</h2>
<% for (const {type, ...entry} of plugins.stackoverflow[section]) { %>
<div class="entry">
<% if (type === "question") { %>
<div class="field title">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.75 2.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 01.75.75v2.19l2.72-2.72a.75.75 0 01.53-.22h4.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25H2.75zM1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0113.25 12H9.06l-2.573 2.573A1.457 1.457 0 014 13.543V12H2.75A1.75 1.75 0 011 10.25v-7.5z"></path></svg>
<%= entry.title %>
</div>
<div class="infos">
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.5 7.775V2.75a.25.25 0 01.25-.25h5.025a.25.25 0 01.177.073l6.25 6.25a.25.25 0 010 .354l-5.025 5.025a.25.25 0 01-.354 0l-6.25-6.25a.25.25 0 01-.073-.177zm-1.5 0V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 010 2.474l-5.026 5.026a1.75 1.75 0 01-2.474 0l-6.25-6.25A1.75 1.75 0 011 7.775zM6 5a1 1 0 100 2 1 1 0 000-2z"></path></svg>
<%= entry.tags.join(", ") %>
</div>
<% if (entry.answered) { %>
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 16A8 8 0 108 0a8 8 0 000 16zm3.78-9.72a.75.75 0 00-1.06-1.06L6.75 9.19 5.28 7.72a.75.75 0 00-1.06 1.06l2 2a.75.75 0 001.06 0l4.5-4.5z"></path></svg>
Resolved
</div>
<% } %>
</div>
<div class="body">
<%= entry.body %>
</div>
<div class="infos">
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8.834.066C7.494-.087 6.5 1.048 6.5 2.25v.5c0 1.329-.647 2.124-1.318 2.614-.328.24-.66.403-.918.508A1.75 1.75 0 002.75 5h-1A1.75 1.75 0 000 6.75v7.5C0 15.216.784 16 1.75 16h1a1.75 1.75 0 001.662-1.201c.525.075 1.067.229 1.725.415.152.043.31.088.475.133 1.154.32 2.54.653 4.388.653 1.706 0 2.97-.153 3.722-1.14.353-.463.537-1.042.668-1.672.118-.56.208-1.243.313-2.033l.04-.306c.25-1.869.265-3.318-.188-4.316a2.418 2.418 0 00-1.137-1.2C13.924 5.085 13.353 5 12.75 5h-1.422l.015-.113c.07-.518.157-1.17.157-1.637 0-.922-.151-1.719-.656-2.3-.51-.589-1.247-.797-2.01-.884zM4.5 13.3c.705.088 1.39.284 2.072.478l.441.125c1.096.305 2.334.598 3.987.598 1.794 0 2.28-.223 2.528-.549.147-.193.276-.505.394-1.07.105-.502.188-1.124.295-1.93l.04-.3c.25-1.882.189-2.933-.068-3.497a.922.922 0 00-.442-.48c-.208-.104-.52-.174-.997-.174H11c-.686 0-1.295-.577-1.206-1.336.023-.192.05-.39.076-.586.065-.488.13-.97.13-1.328 0-.809-.144-1.15-.288-1.316-.137-.158-.402-.304-1.048-.378C8.357 1.521 8 1.793 8 2.25v.5c0 1.922-.978 3.128-1.933 3.825a5.861 5.861 0 01-1.567.81V13.3zM2.75 6.5a.25.25 0 01.25.25v7.5a.25.25 0 01-.25.25h-1a.25.25 0 01-.25-.25v-7.5a.25.25 0 01.25-.25h1z"></path></svg>
<%= f(entry.upvotes) %>
</div>
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.083 15.986c1.34.153 2.334-.982 2.334-2.183v-.5c0-1.329.646-2.123 1.317-2.614.329-.24.66-.403.919-.508a1.75 1.75 0 001.514.872h1a1.75 1.75 0 001.75-1.75v-7.5a1.75 1.75 0 00-1.75-1.75h-1a1.75 1.75 0 00-1.662 1.2c-.525-.074-1.068-.228-1.726-.415L9.305.705C8.151.385 6.765.053 4.917.053c-1.706 0-2.97.152-3.722 1.139-.353.463-.537 1.042-.669 1.672C.41 3.424.32 4.108.214 4.897l-.04.306c-.25 1.869-.266 3.318.188 4.316.244.537.622.943 1.136 1.2.495.248 1.066.334 1.669.334h1.422l-.015.112c-.07.518-.157 1.17-.157 1.638 0 .921.151 1.718.655 2.299.512.589 1.248.797 2.011.884zm4.334-13.232c-.706-.089-1.39-.284-2.072-.479a63.914 63.914 0 00-.441-.125c-1.096-.304-2.335-.597-3.987-.597-1.794 0-2.28.222-2.529.548-.147.193-.275.505-.393 1.07-.105.502-.188 1.124-.295 1.93l-.04.3c-.25 1.882-.19 2.933.067 3.497a.921.921 0 00.443.48c.208.104.52.175.997.175h1.75c.685 0 1.295.577 1.205 1.335-.022.192-.049.39-.075.586-.066.488-.13.97-.13 1.329 0 .808.144 1.15.288 1.316.137.157.401.303 1.048.377.307.035.664-.237.664-.693v-.5c0-1.922.978-3.127 1.932-3.825a5.862 5.862 0 011.568-.809V2.754zm1.75 6.798a.25.25 0 01-.25-.25v-7.5a.25.25 0 01.25-.25h1a.25.25 0 01.25.25v7.5a.25.25 0 01-.25.25h-1z"></path></svg>
<%= f(entry.downvotes) %>
</div>
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.679 7.932c.412-.621 1.242-1.75 2.366-2.717C5.175 4.242 6.527 3.5 8 3.5c1.473 0 2.824.742 3.955 1.715 1.124.967 1.954 2.096 2.366 2.717a.119.119 0 010 .136c-.412.621-1.242 1.75-2.366 2.717C10.825 11.758 9.473 12.5 8 12.5c-1.473 0-2.824-.742-3.955-1.715C2.92 9.818 2.09 8.69 1.679 8.068a.119.119 0 010-.136zM8 2c-1.981 0-3.67.992-4.933 2.078C1.797 5.169.88 6.423.43 7.1a1.619 1.619 0 000 1.798c.45.678 1.367 1.932 2.637 3.024C4.329 13.008 6.019 14 8 14c1.981 0 3.67-.992 4.933-2.078 1.27-1.091 2.187-2.345 2.637-3.023a1.619 1.619 0 000-1.798c-.45-.678-1.367-1.932-2.637-3.023C11.671 2.992 9.981 2 8 2zm0 8a2 2 0 100-4 2 2 0 000 4z"></path></svg>
<%= f(entry.views) %>
</div>
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M16 1.25v4.146a.25.25 0 01-.427.177L14.03 4.03l-3.75 3.75a.75.75 0 11-1.06-1.06l3.75-3.75-1.543-1.543A.25.25 0 0111.604 1h4.146a.25.25 0 01.25.25zM2.75 3.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 01.75.75v2.19l2.72-2.72a.75.75 0 01.53-.22h4.5a.25.25 0 00.25-.25v-2.5a.75.75 0 111.5 0v2.5A1.75 1.75 0 0113.25 13H9.06l-2.573 2.573A1.457 1.457 0 014 14.543V13H2.75A1.75 1.75 0 011 11.25v-7.5C1 2.784 1.784 2 2.75 2h5.5a.75.75 0 010 1.5h-5.5z"></path></svg>
<%= f(entry.answers) %>
</div>
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 2.75a.25.25 0 01.25-.25h8.5a.25.25 0 01.25.25v5.5a.25.25 0 01-.25.25h-3.5a.75.75 0 00-.53.22L3.5 11.44V9.25a.75.75 0 00-.75-.75h-1a.25.25 0 01-.25-.25v-5.5zM1.75 1A1.75 1.75 0 000 2.75v5.5C0 9.216.784 10 1.75 10H2v1.543a1.457 1.457 0 002.487 1.03L7.061 10h3.189A1.75 1.75 0 0012 8.25v-5.5A1.75 1.75 0 0010.25 1h-8.5zM14.5 4.75a.25.25 0 00-.25-.25h-.5a.75.75 0 110-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0114.25 12H14v1.543a1.457 1.457 0 01-2.487 1.03L9.22 12.28a.75.75 0 111.06-1.06l2.22 2.22v-2.19a.75.75 0 01.75-.75h1a.25.25 0 00.25-.25v-5.5z"></path></svg>
<%= f(entry.comments) %>
</div>
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.75 0a.75.75 0 01.75.75V2h5V.75a.75.75 0 011.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0113.25 16H2.75A1.75 1.75 0 011 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 014.75 0zm0 3.5h8.5a.25.25 0 01.25.25V6h-11V3.75a.25.25 0 01.25-.25h2zm-2.25 4v6.75c0 .138.112.25.25.25h10.5a.25.25 0 00.25-.25V7.5h-11z"></path></svg>
<%= entry.created %>
</div>
</div>
<% } else if (type === "answer") { %>
<div class="field title">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M16 1.25v4.146a.25.25 0 01-.427.177L14.03 4.03l-3.75 3.75a.75.75 0 11-1.06-1.06l3.75-3.75-1.543-1.543A.25.25 0 0111.604 1h4.146a.25.25 0 01.25.25zM2.75 3.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 01.75.75v2.19l2.72-2.72a.75.75 0 01.53-.22h4.5a.25.25 0 00.25-.25v-2.5a.75.75 0 111.5 0v2.5A1.75 1.75 0 0113.25 13H9.06l-2.573 2.573A1.457 1.457 0 014 14.543V13H2.75A1.75 1.75 0 011 11.25v-7.5C1 2.784 1.784 2 2.75 2h5.5a.75.75 0 010 1.5h-5.5z"></path></svg>
<%= entry.question?.title %>
</div>
<div class="infos">
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.5 7.775V2.75a.25.25 0 01.25-.25h5.025a.25.25 0 01.177.073l6.25 6.25a.25.25 0 010 .354l-5.025 5.025a.25.25 0 01-.354 0l-6.25-6.25a.25.25 0 01-.073-.177zm-1.5 0V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 010 2.474l-5.026 5.026a1.75 1.75 0 01-2.474 0l-6.25-6.25A1.75 1.75 0 011 7.775zM6 5a1 1 0 100 2 1 1 0 000-2z"></path></svg>
<%= entry.question?.tags.join(", ") %>
</div>
<% if (entry.question?.answered) { %>
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 16A8 8 0 108 0a8 8 0 000 16zm3.78-9.72a.75.75 0 00-1.06-1.06L6.75 9.19 5.28 7.72a.75.75 0 00-1.06 1.06l2 2a.75.75 0 001.06 0l4.5-4.5z"></path></svg>
Resolved
</div>
<% } %>
</div>
<div class="body">
<%= entry.body %>
</div>
<div class="infos">
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8.834.066C7.494-.087 6.5 1.048 6.5 2.25v.5c0 1.329-.647 2.124-1.318 2.614-.328.24-.66.403-.918.508A1.75 1.75 0 002.75 5h-1A1.75 1.75 0 000 6.75v7.5C0 15.216.784 16 1.75 16h1a1.75 1.75 0 001.662-1.201c.525.075 1.067.229 1.725.415.152.043.31.088.475.133 1.154.32 2.54.653 4.388.653 1.706 0 2.97-.153 3.722-1.14.353-.463.537-1.042.668-1.672.118-.56.208-1.243.313-2.033l.04-.306c.25-1.869.265-3.318-.188-4.316a2.418 2.418 0 00-1.137-1.2C13.924 5.085 13.353 5 12.75 5h-1.422l.015-.113c.07-.518.157-1.17.157-1.637 0-.922-.151-1.719-.656-2.3-.51-.589-1.247-.797-2.01-.884zM4.5 13.3c.705.088 1.39.284 2.072.478l.441.125c1.096.305 2.334.598 3.987.598 1.794 0 2.28-.223 2.528-.549.147-.193.276-.505.394-1.07.105-.502.188-1.124.295-1.93l.04-.3c.25-1.882.189-2.933-.068-3.497a.922.922 0 00-.442-.48c-.208-.104-.52-.174-.997-.174H11c-.686 0-1.295-.577-1.206-1.336.023-.192.05-.39.076-.586.065-.488.13-.97.13-1.328 0-.809-.144-1.15-.288-1.316-.137-.158-.402-.304-1.048-.378C8.357 1.521 8 1.793 8 2.25v.5c0 1.922-.978 3.128-1.933 3.825a5.861 5.861 0 01-1.567.81V13.3zM2.75 6.5a.25.25 0 01.25.25v7.5a.25.25 0 01-.25.25h-1a.25.25 0 01-.25-.25v-7.5a.25.25 0 01.25-.25h1z"></path></svg>
<%= f(entry.upvotes) %>
</div>
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.083 15.986c1.34.153 2.334-.982 2.334-2.183v-.5c0-1.329.646-2.123 1.317-2.614.329-.24.66-.403.919-.508a1.75 1.75 0 001.514.872h1a1.75 1.75 0 001.75-1.75v-7.5a1.75 1.75 0 00-1.75-1.75h-1a1.75 1.75 0 00-1.662 1.2c-.525-.074-1.068-.228-1.726-.415L9.305.705C8.151.385 6.765.053 4.917.053c-1.706 0-2.97.152-3.722 1.139-.353.463-.537 1.042-.669 1.672C.41 3.424.32 4.108.214 4.897l-.04.306c-.25 1.869-.266 3.318.188 4.316.244.537.622.943 1.136 1.2.495.248 1.066.334 1.669.334h1.422l-.015.112c-.07.518-.157 1.17-.157 1.638 0 .921.151 1.718.655 2.299.512.589 1.248.797 2.011.884zm4.334-13.232c-.706-.089-1.39-.284-2.072-.479a63.914 63.914 0 00-.441-.125c-1.096-.304-2.335-.597-3.987-.597-1.794 0-2.28.222-2.529.548-.147.193-.275.505-.393 1.07-.105.502-.188 1.124-.295 1.93l-.04.3c-.25 1.882-.19 2.933.067 3.497a.921.921 0 00.443.48c.208.104.52.175.997.175h1.75c.685 0 1.295.577 1.205 1.335-.022.192-.049.39-.075.586-.066.488-.13.97-.13 1.329 0 .808.144 1.15.288 1.316.137.157.401.303 1.048.377.307.035.664-.237.664-.693v-.5c0-1.922.978-3.127 1.932-3.825a5.862 5.862 0 011.568-.809V2.754zm1.75 6.798a.25.25 0 01-.25-.25v-7.5a.25.25 0 01.25-.25h1a.25.25 0 01.25.25v7.5a.25.25 0 01-.25.25h-1z"></path></svg>
<%= f(entry.downvotes) %>
</div>
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 2.75a.25.25 0 01.25-.25h8.5a.25.25 0 01.25.25v5.5a.25.25 0 01-.25.25h-3.5a.75.75 0 00-.53.22L3.5 11.44V9.25a.75.75 0 00-.75-.75h-1a.25.25 0 01-.25-.25v-5.5zM1.75 1A1.75 1.75 0 000 2.75v5.5C0 9.216.784 10 1.75 10H2v1.543a1.457 1.457 0 002.487 1.03L7.061 10h3.189A1.75 1.75 0 0012 8.25v-5.5A1.75 1.75 0 0010.25 1h-8.5zM14.5 4.75a.25.25 0 00-.25-.25h-.5a.75.75 0 110-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0114.25 12H14v1.543a1.457 1.457 0 01-2.487 1.03L9.22 12.28a.75.75 0 111.06-1.06l2.22 2.22v-2.19a.75.75 0 01.75-.75h1a.25.25 0 00.25-.25v-5.5z"></path></svg>
<%= f(entry.comments) %>
</div>
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.75 0a.75.75 0 01.75.75V2h5V.75a.75.75 0 011.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0113.25 16H2.75A1.75 1.75 0 011 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 014.75 0zm0 3.5h8.5a.25.25 0 01.25.25V6h-11V3.75a.25.25 0 01.25-.25h2zm-2.25 4v6.75c0 .138.112.25.25.25h10.5a.25.25 0 00.25-.25V7.5h-11z"></path></svg>
<%= entry.created %>
</div>
<% if (entry.accepted) { %>
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 16A8 8 0 108 0a8 8 0 000 16zm3.78-9.72a.75.75 0 00-1.06-1.06L6.75 9.19 5.28 7.72a.75.75 0 00-1.06 1.06l2 2a.75.75 0 001.06 0l4.5-4.5z"></path></svg>
Accepted
</div>
<% } %>
</div>
<% } %>
</div>
<% } %>
</div>
</section>
</div>
<% } %>
<% } %>
</section>
<% } %>

View File

@@ -724,6 +724,49 @@
margin: 0 13px 2px;
}
/* Stackoverflow */
.stackoverflow {
margin-left: 38px;
}
.stackoverflow .entry {
margin: 4px 0 12px;
}
.stackoverflow .title {
color: #58a6ff;
white-space: normal;
align-items: flex-start;
}
.stackoverflow .body, .stackoverflow .infos {
color: #666666;
font-size: 13px;
margin-left: 32px;
}
.stackoverflow .infos {
display: flex;
align-items: center;
}
.stackoverflow .infos > div {
display: inline-flex;
align-items: center;
margin-right: 16px;
}
.stackoverflow .infos svg {
fill: currentColor;
height: 12px;
width: 12px;
margin: 0;
margin-right: 4px;
flex-shrink: 0;
}
.stackoverflow .body {
overflow: hidden;
text-overflow: ellipsis;
border-left: 3px solid #777777B2;
padding-left: 6px;
width: 400px;
}
/* Fade animation */
.af {
opacity: 0;