From a1277cfc19142056f8b01467edbca4c327a81b97 Mon Sep 17 00:00:00 2001
From: Simon Lecoq <22963968+lowlighter@users.noreply.github.com>
Date: Mon, 11 Jan 2021 22:10:23 +0100
Subject: [PATCH] Split SVG templates into partials and add config_order option
(#53)
---
action.yml | 12 +
source/app/action/index.mjs | 2 +
source/app/metrics.mjs | 14 +-
source/app/setup.mjs | 17 +-
source/templates/classic/image.svg | 1101 +----------------
source/templates/classic/partials/_.json | 20 +
.../templates/classic/partials/activity.ejs | 145 +++
.../partials/base.activity+community.ejs | 57 +
.../classic/partials/base.header.ejs | 46 +
.../classic/partials/base.repositories.ejs | 70 ++
.../templates/classic/partials/followup.ejs | 67 +
source/templates/classic/partials/gists.ejs | 39 +
source/templates/classic/partials/habits.ejs | 80 ++
.../classic/partials/isocalendar.ejs | 33 +
.../templates/classic/partials/languages.ejs | 31 +
source/templates/classic/partials/music.ejs | 43 +
.../templates/classic/partials/pagespeed.ejs | 111 ++
source/templates/classic/partials/people.ejs | 59 +
source/templates/classic/partials/posts.ejs | 39 +
.../templates/classic/partials/projects.ejs | 59 +
.../templates/classic/partials/stargazers.ejs | 47 +
source/templates/classic/partials/stars.ejs | 69 ++
source/templates/classic/partials/topics.ejs | 34 +
source/templates/classic/partials/tweets.ejs | 45 +
source/templates/repository/image.svg | 379 +-----
source/templates/repository/partials/_.json | 8 +
.../repository/partials/base.header.ejs | 56 +
.../repository/partials/followup.ejs | 67 +
.../repository/partials/languages.ejs | 31 +
.../repository/partials/pagespeed.ejs | 111 ++
.../repository/partials/projects.ejs | 59 +
.../repository/partials/stargazers.ejs | 47 +
source/templates/terminal/image.svg | 107 +-
source/templates/terminal/partials/_.json | 8 +
.../partials/base.activity+community.ejs | 22 +
.../terminal/partials/base.header.ejs | 7 +
.../terminal/partials/base.repositories.ejs | 33 +
source/templates/terminal/partials/gists.ejs | 7 +
.../templates/terminal/partials/languages.ejs | 9 +
.../templates/terminal/partials/pagespeed.ejs | 21 +
40 files changed, 1624 insertions(+), 1588 deletions(-)
create mode 100644 source/templates/classic/partials/_.json
create mode 100644 source/templates/classic/partials/activity.ejs
create mode 100644 source/templates/classic/partials/base.activity+community.ejs
create mode 100644 source/templates/classic/partials/base.header.ejs
create mode 100644 source/templates/classic/partials/base.repositories.ejs
create mode 100644 source/templates/classic/partials/followup.ejs
create mode 100644 source/templates/classic/partials/gists.ejs
create mode 100644 source/templates/classic/partials/habits.ejs
create mode 100644 source/templates/classic/partials/isocalendar.ejs
create mode 100644 source/templates/classic/partials/languages.ejs
create mode 100644 source/templates/classic/partials/music.ejs
create mode 100644 source/templates/classic/partials/pagespeed.ejs
create mode 100644 source/templates/classic/partials/people.ejs
create mode 100644 source/templates/classic/partials/posts.ejs
create mode 100644 source/templates/classic/partials/projects.ejs
create mode 100644 source/templates/classic/partials/stargazers.ejs
create mode 100644 source/templates/classic/partials/stars.ejs
create mode 100644 source/templates/classic/partials/topics.ejs
create mode 100644 source/templates/classic/partials/tweets.ejs
create mode 100644 source/templates/repository/partials/_.json
create mode 100644 source/templates/repository/partials/base.header.ejs
create mode 100644 source/templates/repository/partials/followup.ejs
create mode 100644 source/templates/repository/partials/languages.ejs
create mode 100644 source/templates/repository/partials/pagespeed.ejs
create mode 100644 source/templates/repository/partials/projects.ejs
create mode 100644 source/templates/repository/partials/stargazers.ejs
create mode 100644 source/templates/terminal/partials/_.json
create mode 100644 source/templates/terminal/partials/base.activity+community.ejs
create mode 100644 source/templates/terminal/partials/base.header.ejs
create mode 100644 source/templates/terminal/partials/base.repositories.ejs
create mode 100644 source/templates/terminal/partials/gists.ejs
create mode 100644 source/templates/terminal/partials/languages.ejs
create mode 100644 source/templates/terminal/partials/pagespeed.ejs
diff --git a/action.yml b/action.yml
index 96c352e5..6a3f9efb 100644
--- a/action.yml
+++ b/action.yml
@@ -66,6 +66,18 @@ inputs:
description: Image padding
default: 6%
+ # Configure metrics content order (comma-separated values)
+ # Specify in which order base and plugins will be displayed
+ # It is not mandatory to specify all partials when using this option, in this case, remaining parts will be appended
+ #
+ # For example, to display "base.repositories" before "base.activity" and "base.community" in "classic template" you can use:
+ # config_order: base.header, base.repositories, base.activity+community
+ #
+ # See source/templates/*/partials/_.json for a list of supported partials for each template.
+ config_order:
+ description: Configure metrics content order
+ default: ""
+
# Number of repositories to use for metrics
# A high number increase metrics accuracy, but will consume additional API requests when using plugins
repositories:
diff --git a/source/app/action/index.mjs b/source/app/action/index.mjs
index bf996e28..c189c817 100644
--- a/source/app/action/index.mjs
+++ b/source/app/action/index.mjs
@@ -118,11 +118,13 @@
"config.output":input.string("config_output"),
"config.animations":input.bool("config_animations"),
"config.padding":input.string("config_padding"),
+ "config.order":input.array("config_order"),
}
info("Timezone", config["config.timezone"] ?? "(system default)")
info("Convert SVG", config["config.output"] ?? "(no)")
info("Enable SVG animations", config["config.animations"])
info("SVG bottom padding", config["config.padding"])
+ info("Content order", config["config.order"])
//Additional plugins
const plugins = {
diff --git a/source/app/metrics.mjs b/source/app/metrics.mjs
index 1a6b2cae..e4b31c73 100644
--- a/source/app/metrics.mjs
+++ b/source/app/metrics.mjs
@@ -22,12 +22,12 @@
const template = q.template || conf.settings.templates.default
const repositories = Math.max(0, Number(q.repositories)) || conf.settings.repositories || 100
const pending = []
- const s = (value, end = "") => value !== 1 ? {y:"ies", "":"s"}[end] : end
if ((!(template in Templates))||(!(template in conf.templates))||((conf.settings.templates.enabled.length)&&(!conf.settings.templates.enabled.includes(template))))
throw new Error("unsupported template")
- const {image, style, fonts} = conf.templates[template]
+ const {image, style, fonts, views, partials} = conf.templates[template]
const queries = conf.queries
const data = {animated:true, base:{}, config:{}, errors:[], plugins:{}, computed:{}}
+ const s = (value, end = "") => value !== 1 ? {y:"ies", "":"s"}[end] : end
//Base parts
{
@@ -35,6 +35,14 @@
for (const part of conf.settings.plugins.base.parts)
data.base[part] = `base.${part}` in q ? !!q[ `base.${part}`] : defaulted
}
+ //Partial parts
+ {
+ data.partials = new Set([
+ ...decodeURIComponent(q["config.order"] ?? "").split(",").map(x => x.trim().toLocaleLowerCase()).filter(partial => partials.includes(partial)),
+ ...partials,
+ ])
+ console.debug(`metrics/compute/${login} > content order : ${[...data.partials]}`)
+ }
//Placeholder
if (login === "placeholder")
@@ -82,7 +90,7 @@
//Template rendering
console.debug(`metrics/compute/${login} > render`)
- let rendered = await ejs.render(image, {...data, s, style, fonts}, {async:true})
+ let rendered = await ejs.render(image, {...data, s, style, fonts}, {views, async:true})
//Apply resizing
const {resized, mime} = await svgresize(rendered, {paddings:q["config.padding"], convert})
rendered = resized
diff --git a/source/app/setup.mjs b/source/app/setup.mjs
index ce37c143..855a3b1f 100644
--- a/source/app/setup.mjs
+++ b/source/app/setup.mjs
@@ -57,15 +57,19 @@
//Load templates
for (const name of await fs.promises.readdir(__templates)) {
- //Cache templates file
- if (!(await fs.promises.lstat(path.join(__templates, name))).isDirectory())
+ //Search for template
+ const directory = path.join(__templates, name)
+ if (!(await fs.promises.lstat(directory)).isDirectory())
continue
logger(`metrics/setup > load template [${name}]`)
- const files = ["image.svg", "style.css", "fonts.css"].map(file => path.join(__templates, (fs.existsSync(path.join(__templates, name, file)) ? name : "classic"), file))
+ //Cache templates files
+ const files = ["image.svg", "style.css", "fonts.css"].map(file => path.join(__templates, (fs.existsSync(path.join(directory, file)) ? name : "classic"), file))
const [image, style, fonts] = await Promise.all(files.map(async file => `${await fs.promises.readFile(file)}`))
- conf.templates[name] = {image, style, fonts}
+ const partials = JSON.parse(`${await fs.promises.readFile(path.join(directory, "partials/_.json"))}`)
+ conf.templates[name] = {image, style, fonts, partials, views:[directory]}
+
//Cache templates scripts
- Templates[name] = (await import(url.pathToFileURL(path.join(__templates, name, "template.mjs")).href)).default
+ Templates[name] = (await import(url.pathToFileURL(path.join(directory, "template.mjs")).href)).default
logger(`metrics/setup > load template [${name}] > success`)
//Debug
if (conf.settings.debug) {
@@ -73,8 +77,9 @@
get() {
logger(`metrics/setup > reload template [${name}]`)
const [image, style, fonts] = files.map(file => `${fs.readFileSync(file)}`)
+ const partials = JSON.parse(`${fs.readFileSync(path.join(directory, "partials/_.json"))}`)
logger(`metrics/setup > reload template [${name}] > success`)
- return {image, style, fonts}
+ return {image, style, fonts, partials, views:[directory]}
}
})
}
diff --git a/source/templates/classic/image.svg b/source/templates/classic/image.svg
index 5719973f..67f6431d 100644
--- a/source/templates/classic/image.svg
+++ b/source/templates/classic/image.svg
@@ -1,1104 +1,12 @@
-
-
+
-
- <% if (base.header) { %>
-
-
-
- <%= user.name || user.login %>
-
-
-
-
- <% if (computed.cakeday) { %>
-
- Joined GitHub <%= computed.registration %>
- <% } else { %>
-
- Joined GitHub <%= computed.registration %>
- <% } %>
-
-
-
- Followed by <%= user.followers.totalCount %> user<%= s(user.followers.totalCount) %>
-
- <% if (user.isHireable) { %>
-
-
- Available for hire!
-
- <% } %>
-
-
-
-
-
- <% for (const [x, {color}] of Object.entries(computed.calendar)) { %>
-
- <% } %>
-
-
-
-
-
- Contributed to <%= user.repositoriesContributedTo.totalCount %> repositor<%= s(user.repositoriesContributedTo.totalCount, "y") %>
-
-
-
-
- <% } %>
-
-
- <% if (base.activity) { %>
-
-
-
- Activity
-
-
-
- <%= computed.commits %> Commit<%= s(computed.commits) %>
-
-
-
- <%= user.contributionsCollection.totalPullRequestReviewContributions %> Pull request<%= s(user.contributionsCollection.totalPullRequestReviewContributions) %> reviewed
-
-
-
- <%= user.contributionsCollection.totalPullRequestContributions %> Pull request<%= s(user.contributionsCollection.totalPullRequestContributions) %> opened
-
-
-
- <%= user.contributionsCollection.totalIssueContributions %> Issue<%= s(user.contributionsCollection.totalIssueContributions) %> opened
-
-
-
- <%= user.issueComments.totalCount %> issue comment<%= s(user.issueComments.totalCount) %>
-
-
- <% } %>
- <% if (base.community) { %>
-
-
- Community stats
-
-
-
- Member of <%= user.organizations.totalCount %> organization<%= s(user.organizations.totalCount) %>
-
-
-
- Following <%= user.following.totalCount %> user<%= s(user.followers.totalCount) %>
-
-
-
- Sponsoring <%= user.sponsorshipsAsSponsor.totalCount %> repositor<%= s(user.sponsorshipsAsSponsor.totalCount, "y") %>
-
-
-
- Starred <%= user.starredRepositories.totalCount %> repositor<%= s(user.starredRepositories.totalCount, "y") %>
-
-
-
- Watching <%= user.watching.totalCount %> repositor<%= s(user.watching.totalCount, "y") %>
-
-
- <% } %>
-
-
- <% if (base.repositories) { %>
-
-
-
- <%= user.repositories.totalCount %> Repositor<%= s(user.repositories.totalCount, "y") %>
-
-
-
-
-
- <% if (computed.licenses.favorite.length) { %>
- Prefers <%= computed.licenses.favorite %> license
- <% } else { %>
- No license preference
- <% } %>
-
-
-
- <%= computed.repositories.releases %> Release<%= s(computed.repositories.releases) %>
-
-
-
- <%= user.packages.totalCount %> Package<%= s(user.packages.totalCount) %>
-
-
-
- <%= computed.diskUsage %> used
-
- <% if (plugins.lines) { %>
-
-
- <% if (plugins.lines.error) { %>
- <%= plugins.lines.error.message %>
- <% } else { %>
- <%= plugins.lines.added %> added, <%= plugins.lines.deleted %> removed
- <% } %>
-
- <% } %>
-
-
-
-
- <%= user.sponsorshipsAsMaintainer.totalCount %> Sponsor<%= s(user.sponsorshipsAsMaintainer.totalCount) %>
-
-
-
- <%= computed.repositories.stargazers %> Stargazer<%= s(computed.repositories.stargazers) %>
-
-
-
- <%= computed.repositories.forks %> Fork<%= s(computed.repositories.forks) %>
-
-
-
- <%= computed.repositories.watchers %> Watcher<%= s(computed.repositories.watchers) %>
-
- <% if (plugins.traffic) { %>
-
-
- <% if (plugins.traffic.error) { %>
- <%= plugins.traffic.error.message %>
- <% } else { %>
- <%= plugins.traffic.views.count %> view<%= s(plugins.traffic.views.count) %> in last two weeks
- <% } %>
-
- <% } %>
-
-
-
- <% } %>
-
- <% if (plugins.followup) { %>
-
-
-
- Issues
- <% if (plugins.followup.error) { %>
-
-
-
- <%= plugins.followup.error.message %>
-
-
- <% } else { %>
-
-
-
-
-
-
-
-
-
-
-
-
<%= plugins.followup.issues.closed %> Closed
-
-
-
-
<%= plugins.followup.issues.open %> Open
-
-
- <% } %>
-
-
-
- Pull requests
- <% if (plugins.followup.error) { %>
-
-
-
- <%= plugins.followup.error.message %>
-
-
- <% } else { %>
-
-
-
-
-
-
-
-
-
-
-
-
<%= plugins.followup.pr.merged %> Merged
-
-
-
-
<%= plugins.followup.pr.open %> Open
-
-
- <% } %>
-
-
-
- <% } %>
-
- <% if (plugins.languages) { %>
-
- Most used languages
- <% if (plugins.languages.error) { %>
-
-
-
- <%= plugins.languages.error.message %>
-
-
- <% } else { %>
-
-
-
-
-
- <% for (const {name, value, color, x} of plugins.languages.favorites) { %>
-
- <% } %>
-
-
- <% for (const {name, value, color} of plugins.languages.favorites) { %>
-
- <% } %>
-
- <% } %>
-
- <% } %>
-
- <% if (plugins.projects) { %>
-
-
-
- <%= plugins.projects.totalCount %> Project<%= s(plugins.projects.totalCount) %>
-
-
- <% if (plugins.projects.error) { %>
-
-
-
- <%= plugins.projects.error.message %>
-
-
- <% } else { %>
-
- <% for (const {name, updated, progress} of plugins.projects.list) { %>
-
-
-
-
-
- Updated <%= updated %>
-
-
- <% if (progress.enabled) { %>
-
-
-
- <%= [progress.done ? `${progress.done} done` : "", progress.doing ? `${progress.doing} doing` : "", progress.todo ? `${progress.todo} todo` : ""].filter(str => str).join(" · ") %>
-
-
- <% } %>
-
- <% if (progress.enabled) { %>
-
-
-
-
-
-
-
-
-
-
- <% } %>
- <% } %>
-
- <% } %>
-
-
- <% } %>
-
- <% if (plugins.gists) { %>
-
-
-
- <%= plugins.gists.totalCount %> Gist<%= s(plugins.gists.totalCount) %>
-
-
- <% if (plugins.gists.error) { %>
-
-
-
- <%= plugins.gists.error.message %>
-
-
- <% } else { %>
-
-
-
- <%= plugins.gists.files %> File<%= s(plugins.gists.files) %>
-
-
-
- <%= plugins.gists.comments %> Comment<%= s(plugins.gists.comments) %>
-
-
-
-
-
- <%= plugins.gists.stargazers %> Stargazer<%= s(plugins.gists.stargazers) %>
-
-
-
- <%= plugins.gists.forks %> Fork<%= s(plugins.gists.forks) %>
-
-
- <% } %>
-
-
- <% } %>
-
- <% if (plugins.pagespeed) { %>
-
-
-
-
- PageSpeed Insights
-
-
-
- <%= plugins.pagespeed.url %>
-
-
-
- <% if (plugins.pagespeed.error) { %>
-
-
-
-
- <%= plugins.pagespeed.error.message %>
-
-
-
- <% } else { %>
-
-
-
- <% for (const {score, title} of plugins.pagespeed.scores) { %>
-
-
-
- <% if (!Number.isNaN(score)) { %>
-
- <%= Math.round(score*100) %>
- <% } else { %>
- -
- <% } %>
-
- <%= title %>
-
- <% } %>
-
-
-
- <% if (plugins.pagespeed.detailed) { %>
-
-
-
-
- Time to interactive
-
-
-
-
- Total Blocking Time
-
-
-
- First Contentful Paint
-
-
-
- Largest Contentful Paint
-
-
-
- Cumulative Layout Shift
-
-
-
- <% for (const {score, suffix = "", threshold} of [
- {score:plugins.pagespeed.metrics.interactive/1000, suffix:"s", threshold:[3.785, 7.3]},
- {score:plugins.pagespeed.metrics.speedIndex/1000, suffix:"s", threshold:[3.387, 5.8]},
- {score:plugins.pagespeed.metrics.totalBlockingTime/1000, suffix:"s", threshold:[.287, .6]},
- {score:plugins.pagespeed.metrics.firstContentfulPaint/1000, suffix:"s", threshold:[2.336, 4]},
- {score:plugins.pagespeed.metrics.largestContentfulPaint/1000, suffix:"s", threshold:[2.5, 4]},
- {score:+plugins.pagespeed.metrics.cumulativeLayoutShift, threshold:[.1, .25]}
- ]) { %>
-
-
- <% if (!Number.isNaN(score)) { %>
- <%= score.toFixed(2).replace(/[.]0+$/, "") %> <%= suffix %>
- <% } else { %>
- N/A
- <% } %>
-
- <% if (Number.isNaN(score)) { %>
-
- <% } else if (score <= threshold[0]) { %>
-
- <% } else if (score <= threshold[1]) { %>
-
- <% } else { %>
-
- <% } %>
-
- <% } %>
-
-
- <% } %>
- <% if (plugins.pagespeed.screenshot) { %>
-
-
-
-
-
- <% } %>
- <% } %>
- <% } %>
-
- <% if (plugins.habits) { %>
-
-
-
- Coding habits and recent activity
-
- <% if (plugins.habits.facts) { %>
-
- <% if (plugins.habits.error) { %>
-
-
-
- <%= plugins.habits.error.message %>
-
-
- <% } else { %>
-
- <% if (plugins.habits.indents.style) { %>
- Use <%= plugins.habits.indents.style %> for indents
- <% } %>
- <% if (!Number.isNaN(plugins.habits.commits.hour)) { %>
- Mostly push code around <%= plugins.habits.commits.hour %>:00
- <% } %>
- <% if (plugins.habits.commits.day) { %>
- Mostly active on <%= plugins.habits.commits.day.toLocaleLowerCase() %>
- <% } %>
-
- <% } %>
-
- <% } %>
-
-
- <% if (plugins.habits.charts) { %>
- <% if (!Number.isNaN(plugins.habits.commits.hour)) { %>
-
- Commit activity per time of the day
-
- <% for (let h = 0; h < 24; h++) { const p = (plugins.habits.commits.hours[h]??0)/(plugins.habits.commits.hours.max??1); %>
-
-
<%= plugins.habits.commits.hours[h] %>
-
- <%= `${h}`.padStart(2, 0) %>
-
- <% } %>
-
-
- <% } %>
-
-
- <% if (!Number.isNaN(plugins.habits.commits.day)) { %>
-
- Commit activity per day
-
- <% for (let d = 0; d < 7; d++) { const p = (plugins.habits.commits.days[d]??0)/(plugins.habits.commits.days.max??1); %>
-
-
<%= plugins.habits.commits.days[d] %>
-
- <%= ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][d] %>
-
- <% } %>
-
-
- <% } %>
- <% if (plugins.habits.linguist.available) { %>
-
- Language activity
-
- <% for (const [language, p] of plugins.habits.linguist.ordered) { %>
-
-
<%= language %>
-
-
<%= Math.round(100*p) %>%
-
- <% } %>
-
-
- <% } %>
-
- <% } %>
- <% } %>
-
- <% if (plugins.topics) { %>
-
-
-
- <%= {starred:"Starred topics", mastered:"Mastered technologies and topics"}[plugins.topics.mode] %>
-
-
- <% if (plugins.topics.error) { %>
-
-
-
- <%= plugins.topics.error.message %>
-
-
- <% } else { %>
-
-
- <% if (plugins.topics.mode === "starred") { %>
- <% for (const {name, description} of plugins.topics.list) { %>
-
<%= name.toLocaleLowerCase() %>
- <% } %>
- <% } else if (plugins.topics.mode === "mastered") { %>
- <% for (const {name, icon} of plugins.topics.list) { %>
- <% if (icon) { %>
-
- <% } %>
- <% } %>
- <% } %>
-
-
- <% } %>
-
-
- <% } %>
-
- <% if (plugins.music) { %>
-
-
-
- <%= plugins.music.mode %>
-
-
-
- <% if (plugins.music.provider) { %>
-
-
- From <%= plugins.music.provider %>
-
- <% } %>
- <% if (plugins.music.error) { %>
-
-
- <%= plugins.music.error.message %>
-
- <% } else { %>
- <% if (plugins.music.tracks.length) { %>
-
- <% for (const {name = "", artist = "", artwork = ""} of plugins.music.tracks) { %>
-
-
-
-
<%= name %>
-
<%= artist %>
-
-
- <% } %>
-
- <% } else { %>
-
-
- No music recently listened
-
- <% } %>
- <% } %>
-
-
-
- <% } %>
-
- <% if (plugins.posts) { %>
-
-
-
- Recent articles
-
-
-
- <% if (plugins.posts.error) { %>
-
-
- <%= plugins.posts.error.message %>
-
- <% } else { %>
-
-
- From <%= plugins.posts.source %>
-
- <% if (plugins.posts.list.length) { %>
- <% for (const {title, date} of plugins.posts.list) { %>
-
-
-
-
<%= date %>
-
<%= title %>
-
-
- <% } %>
- <% } else { %>
-
- <% } %>
- <% } %>
-
-
-
- <% } %>
-
- <% if (plugins.tweets) { %>
-
-
-
- Latest tweets
-
-
-
- <% if (plugins.tweets.error) { %>
-
-
- <%= plugins.tweets.error.message %>
-
- <% } else { %>
-
- <% if (plugins.tweets.profile) { %>
- <% if (plugins.tweets.list.length) { %>
- <% for (const {text, created_at} of plugins.tweets.list) { %>
-
- <% } %>
- <% } else { %>
-
- <% } %>
- <% } %>
- <% } %>
-
-
-
- <% } %>
-
- <% if (plugins.isocalendar) { %>
-
-
-
- Contributions calendar
-
-
-
- <% if (plugins.isocalendar.error) { %>
-
-
- <%= plugins.isocalendar.error.message %>
-
- <% } %>
-
- <% if (!plugins.isocalendar.error) { %>
-
-
-
- Current streak <%= plugins.isocalendar.streak.current %> day<%= s(plugins.isocalendar.streak.current) %>
-
-
-
- ~<%= plugins.isocalendar.average %> commits per day
-
-
- <% } %>
-
- <% if (plugins.isocalendar.svg) { %>
- <%- plugins.isocalendar.svg %>
- <% } %>
-
- <% } %>
-
- <% if (plugins.stars) { %>
-
-
-
- Recently starred repositories
-
-
-
- <% if (plugins.stars.error) { %>
-
-
- <%= plugins.stars.error.message %>
-
- <% } else { %>
- <% for (const {starred, node:repository} of plugins.stars.repositories) { %>
-
-
-
- <% if (repository.isFork) { %>
-
- <% } else { %>
-
- <% } %>
-
- <%= repository.nameWithOwner %>
- starred <%= starred %>
-
-
-
- <%= repository.description %>
-
-
- <% if (repository.primaryLanguage) { %>
-
-
- <%= repository.primaryLanguage.name %>
-
- <% } %>
- <% if (repository.licenseInfo) { %>
-
-
- <%= repository.licenseInfo.nickname ?? repository.licenseInfo.name %>
-
- <% } %>
-
-
- <%= repository.stargazers %>
-
-
-
- <%= repository.forks %>
-
-
-
- <%= repository.issues.totalCount %>
-
-
-
- <%= repository.pullRequests.totalCount %>
-
-
-
-
- <% } %>
- <% } %>
-
-
-
- <% } %>
-
- <% if (plugins.stargazers) { %>
-
-
-
- Stargazers over the last two weeks
-
- <% if (plugins.stargazers.error) { %>
-
-
- <%= plugins.stargazers.error.message %>
-
- <% } else { %>
-
-
- Total stargazers
-
- <% { let previous = null; for (const [date, value] of Object.entries(plugins.stargazers.total.dates)) { const p = 0.05+0.95*(value-plugins.stargazers.total.min)/(plugins.stargazers.total.max-plugins.stargazers.total.min); const [y, m, d] = date.split("-").map(Number) %>
-
-
<%= (value-(previous ?? 0)) ? value : "" %>
-
- <%= d %>
- <% if ((previous === null)||(d === 1)) { %>
-
<%= plugins.stargazers.months[m] %>
- <% } %>
-
- <% previous = value } } %>
-
-
-
- New stargazers per day
-
- <% { let previous = null; for (const [date, value] of Object.entries(plugins.stargazers.increments.dates)) { const p = value/plugins.stargazers.increments.max; const [y, m, d] = date.split("-").map(Number) %>
-
-
<%= value != 0 ? value : "" %>
-
- <%= d %>
- <% if ((previous === null)||(d === 1)) { %>
-
<%= plugins.stargazers.months[m] %>
- <% } %>
-
- <% previous = value } } %>
-
-
-
- <% } %>
-
- <% } %>
-
- <% if (plugins.people) { %>
- <% if (plugins.people.error) { %>
-
-
-
-
-
-
-
-
-
- <%= plugins.people.error.message %>
-
-
-
-
- <% } else { %>
- <% if (plugins.people.types?.includes("followers")) { %>
-
-
-
- <%= user.followers.totalCount %> follower<%= s(user.followers.totalCount) %>
-
-
-
- <% if (plugins.people.error) { %>
-
-
- <%= plugins.people.error.message %>
-
- <% } else { %>
- <% for (const user of plugins.people.followers) { %> <% } %>
- <% } %>
-
-
-
- <% } %>
- <% if (plugins.people.types?.includes("following")) { %>
-
-
-
- <%= user.following.totalCount %> followed
-
-
-
- <% if (plugins.people.error) { %>
-
-
- <%= plugins.people.error.message %>
-
- <% } else { %>
- <% for (const user of plugins.people.following) { %> <% } %>
- <% } %>
-
-
-
- <% } %>
- <% } %>
- <% } %>
-
- <% if (plugins.activity) { %>
-
-
-
- Recent activity
-
-
-
- <% if (plugins.activity.error) { %>
-
-
- <%= plugins.activity.error.message %>
-
- <% } else { %>
- <% if (!plugins.activity.events.length) { %>
-
- <% } %>
- <% for (const {type, repo, ...event} of plugins.activity.events) { %>
-
-
- <% if (/^ref/.test(type)) { %>
-
- <% if (event.ref.type === "branch") { %>
-
- <% } else { %>
-
- <% } %>
- <%= /create/.test(type) ? "Created new" : "Deleted" %>
- <%= event.ref.type %>
<%= event.ref.name %>
in
<%= repo %>
-
- <% } %>
- <% if (type === "comment") { %>
-
- <% if (event.on === "pr") { %>
-
- <% } else if ((event.on === "issue")||(event.on === "commit")) { %>
-
- <% } %>
- Commented on
#<%= event.number %> <%= event.title %>
-
-
-
<%= event.on === "commit" ? "committed" : "opened" %> by <%= event.user %> in
<%= repo %>
-
-
- <% } %>
- <% if (type === "wiki") { %>
-
-
- Updated <%= event.pages.length %> wiki page<%= s(event.pages.length) %> in
<%= repo %>
-
-
- <% for (const page of event.pages) { %>
-
- <%= page %>
-
- <% } %>
-
- <% } %>
- <% if (type === "pr") { %>
-
-
- <%= event.action === "opened" ? "Opened" : "Merged" %>
#<%= event.number %> <%= event.title %>
-
-
-
opened <%= user.login !== event.user ? `by ${event.user}` : "" %> in
<%= repo %>
-
<%= event.files.changed %> file<%= s(event.files.changed) %> changed
++<%= event.lines.added %> --<%= event.lines.deleted%>
-
- <% } %>
- <% if (type === "issue") { %>
-
-
- <%= event.action === "opened" ? "Opened" : event.action === "reopened" ? "Reopened" : "Closed" %>
#<%= event.number %> <%= event.title %>
-
-
-
opened <%= user.login !== event.user ? `by ${event.user}` : "" %> in
<%= repo %>
-
- <% } %>
- <% if (type === "fork") { %>
-
- <% } %>
- <% if (type === "public") { %>
-
-
- Made
<%= repo %>
public
-
- <% } %>
- <% if (type === "review") { %>
-
-
- Reviewed
#<%= event.number %> <%= event.title %>
-
-
-
opened <%= user.login !== event.user ? `by ${event.user}` : "" %> in
<%= repo %>
-
- <% } %>
- <% if (type === "push") { %>
-
-
- Pushed <%= event.size %> commit<%= s(event.size) %> in
<%= repo %>
-
-
- <% if (event.branch) { %>
-
on branch
<%= event.branch %>
- <% } %>
- <% for (const commit of event.commits) { %>
-
-
#<%= commit.sha %>
-
<%= commit.message %>
-
- <% } %>
-
- <% } %>
- <% if (type === "release") { %>
-
-
- <%= event.draft ? "Drafted release" : event.prerelease ? "Pre-released" : "Released" %>
-
<%= event.name %>
of
<%= repo %>
-
- <% } %>
- <% if (type === "star") { %>
-
-
- Starred
<%= repo %>
-
- <% } %>
- <% if (type === "member") { %>
-
-
- Added <%= event.user %> as collaborator in
<%= repo %>
-
- <% } %>
-
-
- <% } %>
- <% } %>
-
-
-
+ <% for (const partial of [...partials]) { %>
+ <%- await include(`partials/${partial}.ejs`); %>
<% } %>
<% if (base.metadata) { %>
@@ -1107,8 +15,9 @@
Last updated <%= new Date().toGMTString() %> with lowlighter/metrics@<%= meta.version %>
<% } %>
-
+
+
diff --git a/source/templates/classic/partials/_.json b/source/templates/classic/partials/_.json
new file mode 100644
index 00000000..a5992dab
--- /dev/null
+++ b/source/templates/classic/partials/_.json
@@ -0,0 +1,20 @@
+[
+ "base.header",
+ "base.activity+community",
+ "base.repositories",
+ "followup",
+ "languages",
+ "projects",
+ "gists",
+ "pagespeed",
+ "habits",
+ "topics",
+ "music",
+ "posts",
+ "tweets",
+ "isocalendar",
+ "stars",
+ "stargazers",
+ "people",
+ "activity"
+]
\ No newline at end of file
diff --git a/source/templates/classic/partials/activity.ejs b/source/templates/classic/partials/activity.ejs
new file mode 100644
index 00000000..610896ae
--- /dev/null
+++ b/source/templates/classic/partials/activity.ejs
@@ -0,0 +1,145 @@
+<% if (plugins.activity) { %>
+
+
+
+ Recent activity
+
+
+
+ <% if (plugins.activity.error) { %>
+
+
+ <%= plugins.activity.error.message %>
+
+ <% } else { %>
+ <% if (!plugins.activity.events.length) { %>
+
+ <% } %>
+ <% for (const {type, repo, ...event} of plugins.activity.events) { %>
+
+
+ <% if (/^ref/.test(type)) { %>
+
+ <% if (event.ref.type === "branch") { %>
+
+ <% } else { %>
+
+ <% } %>
+ <%= /create/.test(type) ? "Created new" : "Deleted" %>
+ <%= event.ref.type %>
<%= event.ref.name %>
in
<%= repo %>
+
+ <% } %>
+ <% if (type === "comment") { %>
+
+ <% if (event.on === "pr") { %>
+
+ <% } else if ((event.on === "issue")||(event.on === "commit")) { %>
+
+ <% } %>
+ Commented on
#<%= event.number %> <%= event.title %>
+
+
+
<%= event.on === "commit" ? "committed" : "opened" %> by <%= event.user %> in
<%= repo %>
+
+
+ <% } %>
+ <% if (type === "wiki") { %>
+
+
+ Updated <%= event.pages.length %> wiki page<%= s(event.pages.length) %> in
<%= repo %>
+
+
+ <% for (const page of event.pages) { %>
+
+ <%= page %>
+
+ <% } %>
+
+ <% } %>
+ <% if (type === "pr") { %>
+
+
+ <%= event.action === "opened" ? "Opened" : "Merged" %>
#<%= event.number %> <%= event.title %>
+
+
+
opened <%= user.login !== event.user ? `by ${event.user}` : "" %> in
<%= repo %>
+
<%= event.files.changed %> file<%= s(event.files.changed) %> changed
++<%= event.lines.added %> --<%= event.lines.deleted%>
+
+ <% } %>
+ <% if (type === "issue") { %>
+
+
+ <%= event.action === "opened" ? "Opened" : event.action === "reopened" ? "Reopened" : "Closed" %>
#<%= event.number %> <%= event.title %>
+
+
+
opened <%= user.login !== event.user ? `by ${event.user}` : "" %> in
<%= repo %>
+
+ <% } %>
+ <% if (type === "fork") { %>
+
+ <% } %>
+ <% if (type === "public") { %>
+
+
+ Made
<%= repo %>
public
+
+ <% } %>
+ <% if (type === "review") { %>
+
+
+ Reviewed
#<%= event.number %> <%= event.title %>
+
+
+
opened <%= user.login !== event.user ? `by ${event.user}` : "" %> in
<%= repo %>
+
+ <% } %>
+ <% if (type === "push") { %>
+
+
+ Pushed <%= event.size %> commit<%= s(event.size) %> in
<%= repo %>
+
+
+ <% if (event.branch) { %>
+
on branch
<%= event.branch %>
+ <% } %>
+ <% for (const commit of event.commits) { %>
+
+
#<%= commit.sha %>
+
<%= commit.message %>
+
+ <% } %>
+
+ <% } %>
+ <% if (type === "release") { %>
+
+
+ <%= event.draft ? "Drafted release" : event.prerelease ? "Pre-released" : "Released" %>
+
<%= event.name %>
of
<%= repo %>
+
+ <% } %>
+ <% if (type === "star") { %>
+
+
+ Starred
<%= repo %>
+
+ <% } %>
+ <% if (type === "member") { %>
+
+
+ Added <%= event.user %> as collaborator in
<%= repo %>
+
+ <% } %>
+
+
+ <% } %>
+ <% } %>
+
+
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/base.activity+community.ejs b/source/templates/classic/partials/base.activity+community.ejs
new file mode 100644
index 00000000..1fc505b9
--- /dev/null
+++ b/source/templates/classic/partials/base.activity+community.ejs
@@ -0,0 +1,57 @@
+
+ <% if (base.activity) { %>
+
+
+
+ Activity
+
+
+
+ <%= computed.commits %> Commit<%= s(computed.commits) %>
+
+
+
+ <%= user.contributionsCollection.totalPullRequestReviewContributions %> Pull request<%= s(user.contributionsCollection.totalPullRequestReviewContributions) %> reviewed
+
+
+
+ <%= user.contributionsCollection.totalPullRequestContributions %> Pull request<%= s(user.contributionsCollection.totalPullRequestContributions) %> opened
+
+
+
+ <%= user.contributionsCollection.totalIssueContributions %> Issue<%= s(user.contributionsCollection.totalIssueContributions) %> opened
+
+
+
+ <%= user.issueComments.totalCount %> issue comment<%= s(user.issueComments.totalCount) %>
+
+
+ <% } %>
+ <% if (base.community) { %>
+
+
+ Community stats
+
+
+
+ Member of <%= user.organizations.totalCount %> organization<%= s(user.organizations.totalCount) %>
+
+
+
+ Following <%= user.following.totalCount %> user<%= s(user.followers.totalCount) %>
+
+
+
+ Sponsoring <%= user.sponsorshipsAsSponsor.totalCount %> repositor<%= s(user.sponsorshipsAsSponsor.totalCount, "y") %>
+
+
+
+ Starred <%= user.starredRepositories.totalCount %> repositor<%= s(user.starredRepositories.totalCount, "y") %>
+
+
+
+ Watching <%= user.watching.totalCount %> repositor<%= s(user.watching.totalCount, "y") %>
+
+
+ <% } %>
+
\ No newline at end of file
diff --git a/source/templates/classic/partials/base.header.ejs b/source/templates/classic/partials/base.header.ejs
new file mode 100644
index 00000000..7405d6fc
--- /dev/null
+++ b/source/templates/classic/partials/base.header.ejs
@@ -0,0 +1,46 @@
+<% if (base.header) { %>
+
+
+
+ <%= user.name || user.login %>
+
+
+
+
+ <% if (computed.cakeday) { %>
+
+ Joined GitHub <%= computed.registration %>
+ <% } else { %>
+
+ Joined GitHub <%= computed.registration %>
+ <% } %>
+
+
+
+ Followed by <%= user.followers.totalCount %> user<%= s(user.followers.totalCount) %>
+
+ <% if (user.isHireable) { %>
+
+
+ Available for hire!
+
+ <% } %>
+
+
+
+
+
+ <% for (const [x, {color}] of Object.entries(computed.calendar)) { %>
+
+ <% } %>
+
+
+
+
+
+ Contributed to <%= user.repositoriesContributedTo.totalCount %> repositor<%= s(user.repositoriesContributedTo.totalCount, "y") %>
+
+
+
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/base.repositories.ejs b/source/templates/classic/partials/base.repositories.ejs
new file mode 100644
index 00000000..8bc79dce
--- /dev/null
+++ b/source/templates/classic/partials/base.repositories.ejs
@@ -0,0 +1,70 @@
+<% if (base.repositories) { %>
+
+
+
+ <%= user.repositories.totalCount %> Repositor<%= s(user.repositories.totalCount, "y") %>
+
+
+
+
+
+ <% if (computed.licenses.favorite.length) { %>
+ Prefers <%= computed.licenses.favorite %> license
+ <% } else { %>
+ No license preference
+ <% } %>
+
+
+
+ <%= computed.repositories.releases %> Release<%= s(computed.repositories.releases) %>
+
+
+
+ <%= user.packages.totalCount %> Package<%= s(user.packages.totalCount) %>
+
+
+
+ <%= computed.diskUsage %> used
+
+ <% if (plugins.lines) { %>
+
+
+ <% if (plugins.lines.error) { %>
+ <%= plugins.lines.error.message %>
+ <% } else { %>
+ <%= plugins.lines.added %> added, <%= plugins.lines.deleted %> removed
+ <% } %>
+
+ <% } %>
+
+
+
+
+ <%= user.sponsorshipsAsMaintainer.totalCount %> Sponsor<%= s(user.sponsorshipsAsMaintainer.totalCount) %>
+
+
+
+ <%= computed.repositories.stargazers %> Stargazer<%= s(computed.repositories.stargazers) %>
+
+
+
+ <%= computed.repositories.forks %> Fork<%= s(computed.repositories.forks) %>
+
+
+
+ <%= computed.repositories.watchers %> Watcher<%= s(computed.repositories.watchers) %>
+
+ <% if (plugins.traffic) { %>
+
+
+ <% if (plugins.traffic.error) { %>
+ <%= plugins.traffic.error.message %>
+ <% } else { %>
+ <%= plugins.traffic.views.count %> view<%= s(plugins.traffic.views.count) %> in last two weeks
+ <% } %>
+
+ <% } %>
+
+
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/followup.ejs b/source/templates/classic/partials/followup.ejs
new file mode 100644
index 00000000..100fd34c
--- /dev/null
+++ b/source/templates/classic/partials/followup.ejs
@@ -0,0 +1,67 @@
+<% if (plugins.followup) { %>
+
+
+
+ Issues
+ <% if (plugins.followup.error) { %>
+
+
+
+ <%= plugins.followup.error.message %>
+
+
+ <% } else { %>
+
+
+
+
+
+
+
+
+
+
+
+
<%= plugins.followup.issues.closed %> Closed
+
+
+
+
<%= plugins.followup.issues.open %> Open
+
+
+ <% } %>
+
+
+
+ Pull requests
+ <% if (plugins.followup.error) { %>
+
+
+
+ <%= plugins.followup.error.message %>
+
+
+ <% } else { %>
+
+
+
+
+
+
+
+
+
+
+
+
<%= plugins.followup.pr.merged %> Merged
+
+
+
+
<%= plugins.followup.pr.open %> Open
+
+
+ <% } %>
+
+
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/gists.ejs b/source/templates/classic/partials/gists.ejs
new file mode 100644
index 00000000..47d84c91
--- /dev/null
+++ b/source/templates/classic/partials/gists.ejs
@@ -0,0 +1,39 @@
+<% if (plugins.gists) { %>
+
+
+
+ <%= plugins.gists.totalCount %> Gist<%= s(plugins.gists.totalCount) %>
+
+
+ <% if (plugins.gists.error) { %>
+
+
+
+ <%= plugins.gists.error.message %>
+
+
+ <% } else { %>
+
+
+
+ <%= plugins.gists.files %> File<%= s(plugins.gists.files) %>
+
+
+
+ <%= plugins.gists.comments %> Comment<%= s(plugins.gists.comments) %>
+
+
+
+
+
+ <%= plugins.gists.stargazers %> Stargazer<%= s(plugins.gists.stargazers) %>
+
+
+
+ <%= plugins.gists.forks %> Fork<%= s(plugins.gists.forks) %>
+
+
+ <% } %>
+
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/habits.ejs b/source/templates/classic/partials/habits.ejs
new file mode 100644
index 00000000..401261e4
--- /dev/null
+++ b/source/templates/classic/partials/habits.ejs
@@ -0,0 +1,80 @@
+<% if (plugins.habits) { %>
+
+
+
+ Coding habits and recent activity
+
+ <% if (plugins.habits.facts) { %>
+
+ <% if (plugins.habits.error) { %>
+
+
+
+ <%= plugins.habits.error.message %>
+
+
+ <% } else { %>
+
+ <% if (plugins.habits.indents.style) { %>
+ Use <%= plugins.habits.indents.style %> for indents
+ <% } %>
+ <% if (!Number.isNaN(plugins.habits.commits.hour)) { %>
+ Mostly push code around <%= plugins.habits.commits.hour %>:00
+ <% } %>
+ <% if (plugins.habits.commits.day) { %>
+ Mostly active on <%= plugins.habits.commits.day.toLocaleLowerCase() %>
+ <% } %>
+
+ <% } %>
+
+ <% } %>
+
+
+ <% if (plugins.habits.charts) { %>
+ <% if (!Number.isNaN(plugins.habits.commits.hour)) { %>
+
+ Commit activity per time of the day
+
+ <% for (let h = 0; h < 24; h++) { const p = (plugins.habits.commits.hours[h]??0)/(plugins.habits.commits.hours.max??1); %>
+
+
<%= plugins.habits.commits.hours[h] %>
+
+ <%= `${h}`.padStart(2, 0) %>
+
+ <% } %>
+
+
+ <% } %>
+
+
+ <% if (!Number.isNaN(plugins.habits.commits.day)) { %>
+
+ Commit activity per day
+
+ <% for (let d = 0; d < 7; d++) { const p = (plugins.habits.commits.days[d]??0)/(plugins.habits.commits.days.max??1); %>
+
+
<%= plugins.habits.commits.days[d] %>
+
+ <%= ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][d] %>
+
+ <% } %>
+
+
+ <% } %>
+ <% if (plugins.habits.linguist.available) { %>
+
+ Language activity
+
+ <% for (const [language, p] of plugins.habits.linguist.ordered) { %>
+
+
<%= language %>
+
+
<%= Math.round(100*p) %>%
+
+ <% } %>
+
+
+ <% } %>
+
+ <% } %>
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/isocalendar.ejs b/source/templates/classic/partials/isocalendar.ejs
new file mode 100644
index 00000000..b9d4d7ba
--- /dev/null
+++ b/source/templates/classic/partials/isocalendar.ejs
@@ -0,0 +1,33 @@
+<% if (plugins.isocalendar) { %>
+
+
+
+ Contributions calendar
+
+
+
+ <% if (plugins.isocalendar.error) { %>
+
+
+ <%= plugins.isocalendar.error.message %>
+
+ <% } %>
+
+ <% if (!plugins.isocalendar.error) { %>
+
+
+
+ Current streak <%= plugins.isocalendar.streak.current %> day<%= s(plugins.isocalendar.streak.current) %>
+
+
+
+ ~<%= plugins.isocalendar.average %> commits per day
+
+
+ <% } %>
+
+ <% if (plugins.isocalendar.svg) { %>
+ <%- plugins.isocalendar.svg %>
+ <% } %>
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/languages.ejs b/source/templates/classic/partials/languages.ejs
new file mode 100644
index 00000000..244d41f1
--- /dev/null
+++ b/source/templates/classic/partials/languages.ejs
@@ -0,0 +1,31 @@
+<% if (plugins.languages) { %>
+
+ Most used languages
+ <% if (plugins.languages.error) { %>
+
+
+
+ <%= plugins.languages.error.message %>
+
+
+ <% } else { %>
+
+
+
+
+
+ <% for (const {name, value, color, x} of plugins.languages.favorites) { %>
+
+ <% } %>
+
+
+ <% for (const {name, value, color} of plugins.languages.favorites) { %>
+
+ <% } %>
+
+ <% } %>
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/music.ejs b/source/templates/classic/partials/music.ejs
new file mode 100644
index 00000000..76766d79
--- /dev/null
+++ b/source/templates/classic/partials/music.ejs
@@ -0,0 +1,43 @@
+<% if (plugins.music) { %>
+
+
+
+ <%= plugins.music.mode %>
+
+
+
+ <% if (plugins.music.provider) { %>
+
+
+ From <%= plugins.music.provider %>
+
+ <% } %>
+ <% if (plugins.music.error) { %>
+
+
+ <%= plugins.music.error.message %>
+
+ <% } else { %>
+ <% if (plugins.music.tracks.length) { %>
+
+ <% for (const {name = "", artist = "", artwork = ""} of plugins.music.tracks) { %>
+
+
+
+
<%= name %>
+
<%= artist %>
+
+
+ <% } %>
+
+ <% } else { %>
+
+
+ No music recently listened
+
+ <% } %>
+ <% } %>
+
+
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/pagespeed.ejs b/source/templates/classic/partials/pagespeed.ejs
new file mode 100644
index 00000000..370d601e
--- /dev/null
+++ b/source/templates/classic/partials/pagespeed.ejs
@@ -0,0 +1,111 @@
+<% if (plugins.pagespeed) { %>
+
+
+
+
+ PageSpeed Insights
+
+
+
+ <%= plugins.pagespeed.url %>
+
+
+
+ <% if (plugins.pagespeed.error) { %>
+
+
+
+
+ <%= plugins.pagespeed.error.message %>
+
+
+
+ <% } else { %>
+
+
+
+ <% for (const {score, title} of plugins.pagespeed.scores) { %>
+
+
+
+ <% if (!Number.isNaN(score)) { %>
+
+ <%= Math.round(score*100) %>
+ <% } else { %>
+ -
+ <% } %>
+
+ <%= title %>
+
+ <% } %>
+
+
+
+ <% if (plugins.pagespeed.detailed) { %>
+
+
+
+
+ Time to interactive
+
+
+
+
+ Total Blocking Time
+
+
+
+ First Contentful Paint
+
+
+
+ Largest Contentful Paint
+
+
+
+ Cumulative Layout Shift
+
+
+
+ <% for (const {score, suffix = "", threshold} of [
+ {score:plugins.pagespeed.metrics.interactive/1000, suffix:"s", threshold:[3.785, 7.3]},
+ {score:plugins.pagespeed.metrics.speedIndex/1000, suffix:"s", threshold:[3.387, 5.8]},
+ {score:plugins.pagespeed.metrics.totalBlockingTime/1000, suffix:"s", threshold:[.287, .6]},
+ {score:plugins.pagespeed.metrics.firstContentfulPaint/1000, suffix:"s", threshold:[2.336, 4]},
+ {score:plugins.pagespeed.metrics.largestContentfulPaint/1000, suffix:"s", threshold:[2.5, 4]},
+ {score:+plugins.pagespeed.metrics.cumulativeLayoutShift, threshold:[.1, .25]}
+ ]) { %>
+
+
+ <% if (!Number.isNaN(score)) { %>
+ <%= score.toFixed(2).replace(/[.]0+$/, "") %> <%= suffix %>
+ <% } else { %>
+ N/A
+ <% } %>
+
+ <% if (Number.isNaN(score)) { %>
+
+ <% } else if (score <= threshold[0]) { %>
+
+ <% } else if (score <= threshold[1]) { %>
+
+ <% } else { %>
+
+ <% } %>
+
+ <% } %>
+
+
+ <% } %>
+ <% if (plugins.pagespeed.screenshot) { %>
+
+
+
+
+
+ <% } %>
+ <% } %>
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/people.ejs b/source/templates/classic/partials/people.ejs
new file mode 100644
index 00000000..9686e488
--- /dev/null
+++ b/source/templates/classic/partials/people.ejs
@@ -0,0 +1,59 @@
+<% if (plugins.people) { %>
+ <% if (plugins.people.error) { %>
+
+
+
+
+
+
+
+
+
+ <%= plugins.people.error.message %>
+
+
+
+
+ <% } else { %>
+ <% if (plugins.people.types?.includes("followers")) { %>
+
+
+
+ <%= user.followers.totalCount %> follower<%= s(user.followers.totalCount) %>
+
+
+
+ <% if (plugins.people.error) { %>
+
+
+ <%= plugins.people.error.message %>
+
+ <% } else { %>
+ <% for (const user of plugins.people.followers) { %> <% } %>
+ <% } %>
+
+
+
+ <% } %>
+ <% if (plugins.people.types?.includes("following")) { %>
+
+
+
+ <%= user.following.totalCount %> followed
+
+
+
+ <% if (plugins.people.error) { %>
+
+
+ <%= plugins.people.error.message %>
+
+ <% } else { %>
+ <% for (const user of plugins.people.following) { %> <% } %>
+ <% } %>
+
+
+
+ <% } %>
+ <% } %>
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/posts.ejs b/source/templates/classic/partials/posts.ejs
new file mode 100644
index 00000000..46774d9b
--- /dev/null
+++ b/source/templates/classic/partials/posts.ejs
@@ -0,0 +1,39 @@
+<% if (plugins.posts) { %>
+
+
+
+ Recent articles
+
+
+
+ <% if (plugins.posts.error) { %>
+
+
+ <%= plugins.posts.error.message %>
+
+ <% } else { %>
+
+
+ From <%= plugins.posts.source %>
+
+ <% if (plugins.posts.list.length) { %>
+ <% for (const {title, date} of plugins.posts.list) { %>
+
+
+
+
<%= date %>
+
<%= title %>
+
+
+ <% } %>
+ <% } else { %>
+
+ <% } %>
+ <% } %>
+
+
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/projects.ejs b/source/templates/classic/partials/projects.ejs
new file mode 100644
index 00000000..75c91050
--- /dev/null
+++ b/source/templates/classic/partials/projects.ejs
@@ -0,0 +1,59 @@
+<% if (plugins.projects) { %>
+
+
+
+ <%= plugins.projects.totalCount %> Project<%= s(plugins.projects.totalCount) %>
+
+
+ <% if (plugins.projects.error) { %>
+
+
+
+ <%= plugins.projects.error.message %>
+
+
+ <% } else { %>
+
+ <% for (const {name, updated, progress} of plugins.projects.list) { %>
+
+
+
+
+
+ Updated <%= updated %>
+
+
+ <% if (progress.enabled) { %>
+
+
+
+ <%= [progress.done ? `${progress.done} done` : "", progress.doing ? `${progress.doing} doing` : "", progress.todo ? `${progress.todo} todo` : ""].filter(str => str).join(" · ") %>
+
+
+ <% } %>
+
+ <% if (progress.enabled) { %>
+
+
+
+
+
+
+
+
+
+
+ <% } %>
+ <% } %>
+
+ <% } %>
+
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/stargazers.ejs b/source/templates/classic/partials/stargazers.ejs
new file mode 100644
index 00000000..9f8eeafb
--- /dev/null
+++ b/source/templates/classic/partials/stargazers.ejs
@@ -0,0 +1,47 @@
+<% if (plugins.stargazers) { %>
+
+
+
+ Stargazers over the last two weeks
+
+ <% if (plugins.stargazers.error) { %>
+
+
+ <%= plugins.stargazers.error.message %>
+
+ <% } else { %>
+
+
+ Total stargazers
+
+ <% { let previous = null; for (const [date, value] of Object.entries(plugins.stargazers.total.dates)) { const p = 0.05+0.95*(value-plugins.stargazers.total.min)/(plugins.stargazers.total.max-plugins.stargazers.total.min); const [y, m, d] = date.split("-").map(Number) %>
+
+
<%= (value-(previous ?? 0)) ? value : "" %>
+
+ <%= d %>
+ <% if ((previous === null)||(d === 1)) { %>
+
<%= plugins.stargazers.months[m] %>
+ <% } %>
+
+ <% previous = value } } %>
+
+
+
+ New stargazers per day
+
+ <% { let previous = null; for (const [date, value] of Object.entries(plugins.stargazers.increments.dates)) { const p = value/plugins.stargazers.increments.max; const [y, m, d] = date.split("-").map(Number) %>
+
+
<%= value != 0 ? value : "" %>
+
+ <%= d %>
+ <% if ((previous === null)||(d === 1)) { %>
+
<%= plugins.stargazers.months[m] %>
+ <% } %>
+
+ <% previous = value } } %>
+
+
+
+ <% } %>
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/stars.ejs b/source/templates/classic/partials/stars.ejs
new file mode 100644
index 00000000..b0f1af9f
--- /dev/null
+++ b/source/templates/classic/partials/stars.ejs
@@ -0,0 +1,69 @@
+<% if (plugins.stars) { %>
+
+
+
+ Recently starred repositories
+
+
+
+ <% if (plugins.stars.error) { %>
+
+
+ <%= plugins.stars.error.message %>
+
+ <% } else { %>
+ <% for (const {starred, node:repository} of plugins.stars.repositories) { %>
+
+
+
+ <% if (repository.isFork) { %>
+
+ <% } else { %>
+
+ <% } %>
+
+ <%= repository.nameWithOwner %>
+ starred <%= starred %>
+
+
+
+ <%= repository.description %>
+
+
+ <% if (repository.primaryLanguage) { %>
+
+
+ <%= repository.primaryLanguage.name %>
+
+ <% } %>
+ <% if (repository.licenseInfo) { %>
+
+
+ <%= repository.licenseInfo.nickname ?? repository.licenseInfo.name %>
+
+ <% } %>
+
+
+ <%= repository.stargazers %>
+
+
+
+ <%= repository.forks %>
+
+
+
+ <%= repository.issues.totalCount %>
+
+
+
+ <%= repository.pullRequests.totalCount %>
+
+
+
+
+ <% } %>
+ <% } %>
+
+
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/topics.ejs b/source/templates/classic/partials/topics.ejs
new file mode 100644
index 00000000..6b2bf1ee
--- /dev/null
+++ b/source/templates/classic/partials/topics.ejs
@@ -0,0 +1,34 @@
+<% if (plugins.topics) { %>
+
+
+
+ <%= {starred:"Starred topics", mastered:"Mastered technologies and topics"}[plugins.topics.mode] %>
+
+
+ <% if (plugins.topics.error) { %>
+
+
+
+ <%= plugins.topics.error.message %>
+
+
+ <% } else { %>
+
+
+ <% if (plugins.topics.mode === "starred") { %>
+ <% for (const {name, description} of plugins.topics.list) { %>
+
<%= name.toLocaleLowerCase() %>
+ <% } %>
+ <% } else if (plugins.topics.mode === "mastered") { %>
+ <% for (const {name, icon} of plugins.topics.list) { %>
+ <% if (icon) { %>
+
+ <% } %>
+ <% } %>
+ <% } %>
+
+
+ <% } %>
+
+
+ <% } %>
\ No newline at end of file
diff --git a/source/templates/classic/partials/tweets.ejs b/source/templates/classic/partials/tweets.ejs
new file mode 100644
index 00000000..988ecd7a
--- /dev/null
+++ b/source/templates/classic/partials/tweets.ejs
@@ -0,0 +1,45 @@
+<% if (plugins.tweets) { %>
+
+
+
+ Latest tweets
+
+
+
+ <% if (plugins.tweets.error) { %>
+
+
+ <%= plugins.tweets.error.message %>
+
+ <% } else { %>
+
+ <% if (plugins.tweets.profile) { %>
+ <% if (plugins.tweets.list.length) { %>
+ <% for (const {text, created_at} of plugins.tweets.list) { %>
+
+ <% } %>
+ <% } else { %>
+
+ <% } %>
+ <% } %>
+ <% } %>
+
+
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/repository/image.svg b/source/templates/repository/image.svg
index 5fbc9248..6a330f8f 100644
--- a/source/templates/repository/image.svg
+++ b/source/templates/repository/image.svg
@@ -19,384 +19,9 @@
<% } else { %>
-
- <% if (base.header) { %>
-
-
-
- <%= user.name %>
-
-
-
-
- <% if (computed.cakeday) { %>
-
- Created <%= computed.registration %>
- <% } else { %>
-
- Created <%= computed.registration %>
- <% } %>
-
-
-
- <%= computed.diskUsage %> used
-
- <% if (plugins.traffic) { %>
-
-
- <% if (plugins.traffic.error) { %>
- <%= plugins.traffic.error.message %>
- <% } else { %>
- <%= plugins.traffic.views.count %> view<%= s(plugins.traffic.views.count) %> in last two weeks
- <% } %>
-
- <% } %>
-
-
-
-
-
- <% for (const [x, {color}] of Object.entries(computed.calendar)) { %>
-
- <% } %>
-
-
-
- <% if (plugins.lines) { %>
-
-
- <% if (plugins.lines.error) { %>
- <%= plugins.lines.error.message %>
- <% } else { %>
- <%= plugins.lines.added %> added, <%= plugins.lines.deleted %> removed
- <% } %>
-
- <% } %>
-
-
-
+ <% for (const partial of [...partials]) { %>
+ <%- await include(`partials/${partial}.ejs`); %>
<% } %>
-
- <% if (plugins.followup) { %>
-
-
-
- Issues
- <% if (plugins.followup.error) { %>
-
-
-
- <%= plugins.followup.error.message %>
-
-
- <% } else { %>
-
-
-
-
-
-
-
-
-
-
-
-
<%= plugins.followup.issues.closed %> Closed
-
-
-
-
<%= plugins.followup.issues.open %> Open
-
-
- <% } %>
-
-
-
- Pull requests
- <% if (plugins.followup.error) { %>
-
-
-
- <%= plugins.followup.error.message %>
-
-
- <% } else { %>
-
-
-
-
-
-
-
-
-
-
-
-
<%= plugins.followup.pr.merged %> Merged
-
-
-
-
<%= plugins.followup.pr.open %> Open
-
-
- <% } %>
-
-
-
- <% } %>
-
- <% if (plugins.languages) { %>
-
- Most used languages
- <% if (plugins.languages.error) { %>
-
-
-
- <%= plugins.languages.error.message %>
-
-
- <% } else { %>
-
-
-
-
-
- <% for (const {name, value, color, x} of plugins.languages.favorites) { %>
-
- <% } %>
-
-
- <% for (const {name, value, color} of plugins.languages.favorites) { %>
-
- <% } %>
-
- <% } %>
-
- <% } %>
-
- <% if (plugins.projects) { %>
-
-
-
- Active projects
-
-
- <% if (plugins.projects.error) { %>
-
-
-
- <%= plugins.projects.error.message %>
-
-
- <% } else { %>
-
- <% for (const {name, updated, progress} of plugins.projects.list) { %>
-
-
-
-
-
- Updated <%= updated %>
-
-
- <% if (progress.enabled) { %>
-
-
-
- <%= [progress.done ? `${progress.done} done` : "", progress.doing ? `${progress.doing} doing` : "", progress.todo ? `${progress.todo} todo` : ""].filter(str => str).join(" · ") %>
-
-
- <% } %>
-
- <% if (progress.enabled) { %>
-
-
-
-
-
-
-
-
-
-
- <% } %>
- <% } %>
-
- <% } %>
-
-
- <% } %>
-
- <% if (plugins.pagespeed) { %>
-
-
-
-
- PageSpeed Insights
-
-
-
- <%= plugins.pagespeed.url %>
-
-
-
- <% if (plugins.pagespeed.error) { %>
-
-
-
-
- <%= plugins.pagespeed.error.message %>
-
-
-
- <% } else { %>
-
-
-
- <% for (const {score, title} of plugins.pagespeed.scores) { %>
-
-
-
- <% if (!Number.isNaN(score)) { %>
-
- <%= Math.round(score*100) %>
- <% } else { %>
- -
- <% } %>
-
- <%= title %>
-
- <% } %>
-
-
-
- <% if (plugins.pagespeed.detailed) { %>
-
-
-
-
- Time to interactive
-
-
-
-
- Total Blocking Time
-
-
-
- First Contentful Paint
-
-
-
- Largest Contentful Paint
-
-
-
- Cumulative Layout Shift
-
-
-
- <% for (const {score, suffix = "", threshold} of [
- {score:plugins.pagespeed.metrics.interactive/1000, suffix:"s", threshold:[3.785, 7.3]},
- {score:plugins.pagespeed.metrics.speedIndex/1000, suffix:"s", threshold:[3.387, 5.8]},
- {score:plugins.pagespeed.metrics.totalBlockingTime/1000, suffix:"s", threshold:[.287, .6]},
- {score:plugins.pagespeed.metrics.firstContentfulPaint/1000, suffix:"s", threshold:[2.336, 4]},
- {score:plugins.pagespeed.metrics.largestContentfulPaint/1000, suffix:"s", threshold:[2.5, 4]},
- {score:+plugins.pagespeed.metrics.cumulativeLayoutShift, threshold:[.1, .25]}
- ]) { %>
-
-
- <% if (!Number.isNaN(score)) { %>
- <%= score.toFixed(2).replace(/[.]0+$/, "") %> <%= suffix %>
- <% } else { %>
- N/A
- <% } %>
-
- <% if (Number.isNaN(score)) { %>
-
- <% } else if (score <= threshold[0]) { %>
-
- <% } else if (score <= threshold[1]) { %>
-
- <% } else { %>
-
- <% } %>
-
- <% } %>
-
-
- <% } %>
- <% if (plugins.pagespeed.screenshot) { %>
-
-
-
-
-
- <% } %>
- <% } %>
- <% } %>
-
- <% if (plugins.stargazers) { %>
-
-
-
- Stargazers over the last two weeks
-
- <% if (plugins.stargazers.error) { %>
-
-
- <%= plugins.stargazers.error.message %>
-
- <% } else { %>
-
-
- Total stargazers
-
- <% { let previous = null; for (const [date, value] of Object.entries(plugins.stargazers.total.dates)) { const p = 0.05+0.95*(value-plugins.stargazers.total.min)/(plugins.stargazers.total.max-plugins.stargazers.total.min); const [y, m, d] = date.split("-").map(Number) %>
-
-
<%= (value-(previous ?? 0)) ? value : "" %>
-
- <%= d %>
- <% if ((previous === null)||(d === 1)) { %>
-
<%= plugins.stargazers.months[m] %>
- <% } %>
-
- <% previous = value } } %>
-
-
-
- New stargazers per day
-
- <% { let previous = true; for (const [date, value] of Object.entries(plugins.stargazers.increments.dates)) { const p = value/plugins.stargazers.increments.max; const [y, m, d] = date.split("-").map(Number) %>
-
-
<%= value != 0 ? value : "" %>
-
- <%= d %>
- <% if ((previous === null)||(d === 1)) { %>
-
<%= plugins.stargazers.months[m] %>
- <% } %>
-
- <% previous = value } } %>
-
-
-
- <% } %>
-
- <% } %>
-
<% } %>
<% if (base.metadata) { %>
diff --git a/source/templates/repository/partials/_.json b/source/templates/repository/partials/_.json
new file mode 100644
index 00000000..9952d97d
--- /dev/null
+++ b/source/templates/repository/partials/_.json
@@ -0,0 +1,8 @@
+[
+ "base.header",
+ "followup",
+ "languages",
+ "projects",
+ "pagespeed",
+ "stargazers"
+]
\ No newline at end of file
diff --git a/source/templates/repository/partials/base.header.ejs b/source/templates/repository/partials/base.header.ejs
new file mode 100644
index 00000000..24b94a1d
--- /dev/null
+++ b/source/templates/repository/partials/base.header.ejs
@@ -0,0 +1,56 @@
+<% if (base.header) { %>
+
+
+
+ <%= user.name %>
+
+
+
+
+ <% if (computed.cakeday) { %>
+
+ Created <%= computed.registration %>
+ <% } else { %>
+
+ Created <%= computed.registration %>
+ <% } %>
+
+
+
+ <%= computed.diskUsage %> used
+
+ <% if (plugins.traffic) { %>
+
+
+ <% if (plugins.traffic.error) { %>
+ <%= plugins.traffic.error.message %>
+ <% } else { %>
+ <%= plugins.traffic.views.count %> view<%= s(plugins.traffic.views.count) %> in last two weeks
+ <% } %>
+
+ <% } %>
+
+
+
+
+
+ <% for (const [x, {color}] of Object.entries(computed.calendar)) { %>
+
+ <% } %>
+
+
+
+ <% if (plugins.lines) { %>
+
+
+ <% if (plugins.lines.error) { %>
+ <%= plugins.lines.error.message %>
+ <% } else { %>
+ <%= plugins.lines.added %> added, <%= plugins.lines.deleted %> removed
+ <% } %>
+
+ <% } %>
+
+
+
+<% } %>
diff --git a/source/templates/repository/partials/followup.ejs b/source/templates/repository/partials/followup.ejs
new file mode 100644
index 00000000..100fd34c
--- /dev/null
+++ b/source/templates/repository/partials/followup.ejs
@@ -0,0 +1,67 @@
+<% if (plugins.followup) { %>
+
+
+
+ Issues
+ <% if (plugins.followup.error) { %>
+
+
+
+ <%= plugins.followup.error.message %>
+
+
+ <% } else { %>
+
+
+
+
+
+
+
+
+
+
+
+
<%= plugins.followup.issues.closed %> Closed
+
+
+
+
<%= plugins.followup.issues.open %> Open
+
+
+ <% } %>
+
+
+
+ Pull requests
+ <% if (plugins.followup.error) { %>
+
+
+
+ <%= plugins.followup.error.message %>
+
+
+ <% } else { %>
+
+
+
+
+
+
+
+
+
+
+
+
<%= plugins.followup.pr.merged %> Merged
+
+
+
+
<%= plugins.followup.pr.open %> Open
+
+
+ <% } %>
+
+
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/repository/partials/languages.ejs b/source/templates/repository/partials/languages.ejs
new file mode 100644
index 00000000..244d41f1
--- /dev/null
+++ b/source/templates/repository/partials/languages.ejs
@@ -0,0 +1,31 @@
+<% if (plugins.languages) { %>
+
+ Most used languages
+ <% if (plugins.languages.error) { %>
+
+
+
+ <%= plugins.languages.error.message %>
+
+
+ <% } else { %>
+
+
+
+
+
+ <% for (const {name, value, color, x} of plugins.languages.favorites) { %>
+
+ <% } %>
+
+
+ <% for (const {name, value, color} of plugins.languages.favorites) { %>
+
+ <% } %>
+
+ <% } %>
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/repository/partials/pagespeed.ejs b/source/templates/repository/partials/pagespeed.ejs
new file mode 100644
index 00000000..370d601e
--- /dev/null
+++ b/source/templates/repository/partials/pagespeed.ejs
@@ -0,0 +1,111 @@
+<% if (plugins.pagespeed) { %>
+
+
+
+
+ PageSpeed Insights
+
+
+
+ <%= plugins.pagespeed.url %>
+
+
+
+ <% if (plugins.pagespeed.error) { %>
+
+
+
+
+ <%= plugins.pagespeed.error.message %>
+
+
+
+ <% } else { %>
+
+
+
+ <% for (const {score, title} of plugins.pagespeed.scores) { %>
+
+
+
+ <% if (!Number.isNaN(score)) { %>
+
+ <%= Math.round(score*100) %>
+ <% } else { %>
+ -
+ <% } %>
+
+ <%= title %>
+
+ <% } %>
+
+
+
+ <% if (plugins.pagespeed.detailed) { %>
+
+
+
+
+ Time to interactive
+
+
+
+
+ Total Blocking Time
+
+
+
+ First Contentful Paint
+
+
+
+ Largest Contentful Paint
+
+
+
+ Cumulative Layout Shift
+
+
+
+ <% for (const {score, suffix = "", threshold} of [
+ {score:plugins.pagespeed.metrics.interactive/1000, suffix:"s", threshold:[3.785, 7.3]},
+ {score:plugins.pagespeed.metrics.speedIndex/1000, suffix:"s", threshold:[3.387, 5.8]},
+ {score:plugins.pagespeed.metrics.totalBlockingTime/1000, suffix:"s", threshold:[.287, .6]},
+ {score:plugins.pagespeed.metrics.firstContentfulPaint/1000, suffix:"s", threshold:[2.336, 4]},
+ {score:plugins.pagespeed.metrics.largestContentfulPaint/1000, suffix:"s", threshold:[2.5, 4]},
+ {score:+plugins.pagespeed.metrics.cumulativeLayoutShift, threshold:[.1, .25]}
+ ]) { %>
+
+
+ <% if (!Number.isNaN(score)) { %>
+ <%= score.toFixed(2).replace(/[.]0+$/, "") %> <%= suffix %>
+ <% } else { %>
+ N/A
+ <% } %>
+
+ <% if (Number.isNaN(score)) { %>
+
+ <% } else if (score <= threshold[0]) { %>
+
+ <% } else if (score <= threshold[1]) { %>
+
+ <% } else { %>
+
+ <% } %>
+
+ <% } %>
+
+
+ <% } %>
+ <% if (plugins.pagespeed.screenshot) { %>
+
+
+
+
+
+ <% } %>
+ <% } %>
+<% } %>
\ No newline at end of file
diff --git a/source/templates/repository/partials/projects.ejs b/source/templates/repository/partials/projects.ejs
new file mode 100644
index 00000000..1b2d4b32
--- /dev/null
+++ b/source/templates/repository/partials/projects.ejs
@@ -0,0 +1,59 @@
+<% if (plugins.projects) { %>
+
+
+
+ Active projects
+
+
+ <% if (plugins.projects.error) { %>
+
+
+
+ <%= plugins.projects.error.message %>
+
+
+ <% } else { %>
+
+ <% for (const {name, updated, progress} of plugins.projects.list) { %>
+
+
+
+
+
+ Updated <%= updated %>
+
+
+ <% if (progress.enabled) { %>
+
+
+
+ <%= [progress.done ? `${progress.done} done` : "", progress.doing ? `${progress.doing} doing` : "", progress.todo ? `${progress.todo} todo` : ""].filter(str => str).join(" · ") %>
+
+
+ <% } %>
+
+ <% if (progress.enabled) { %>
+
+
+
+
+
+
+
+
+
+
+ <% } %>
+ <% } %>
+
+ <% } %>
+
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/repository/partials/stargazers.ejs b/source/templates/repository/partials/stargazers.ejs
new file mode 100644
index 00000000..afb59437
--- /dev/null
+++ b/source/templates/repository/partials/stargazers.ejs
@@ -0,0 +1,47 @@
+<% if (plugins.stargazers) { %>
+
+
+
+ Stargazers over the last two weeks
+
+ <% if (plugins.stargazers.error) { %>
+
+
+ <%= plugins.stargazers.error.message %>
+
+ <% } else { %>
+
+
+ Total stargazers
+
+ <% { let previous = null; for (const [date, value] of Object.entries(plugins.stargazers.total.dates)) { const p = 0.05+0.95*(value-plugins.stargazers.total.min)/(plugins.stargazers.total.max-plugins.stargazers.total.min); const [y, m, d] = date.split("-").map(Number) %>
+
+
<%= (value-(previous ?? 0)) ? value : "" %>
+
+ <%= d %>
+ <% if ((previous === null)||(d === 1)) { %>
+
<%= plugins.stargazers.months[m] %>
+ <% } %>
+
+ <% previous = value } } %>
+
+
+
+ New stargazers per day
+
+ <% { let previous = true; for (const [date, value] of Object.entries(plugins.stargazers.increments.dates)) { const p = value/plugins.stargazers.increments.max; const [y, m, d] = date.split("-").map(Number) %>
+
+
<%= value != 0 ? value : "" %>
+
+ <%= d %>
+ <% if ((previous === null)||(d === 1)) { %>
+
<%= plugins.stargazers.months[m] %>
+ <% } %>
+
+ <% previous = value } } %>
+
+
+
+ <% } %>
+
+<% } %>
\ No newline at end of file
diff --git a/source/templates/terminal/image.svg b/source/templates/terminal/image.svg
index e20ba7ea..78cdd366 100644
--- a/source/templates/terminal/image.svg
+++ b/source/templates/terminal/image.svg
@@ -50,112 +50,7 @@ WARRANTY, to the extent permitted by applicable law.
Last generated: <%= new Date().toGMTString() %>
<% } -%>
-<%# ============================================================= -%>
-<% if (base.header) { %>
-<%- meta.$ %> whoami
<%# -%>
-<%# -%>
-<%= user.name || user.login %> registered=<%= computed.registration.match(/^.+? [ym]/)[0].replace(/ /g, "") %>, uid=<%= `${user.databaseId}`.substr(-4) %>, gid=<%= user.organizations.totalCount %>
- contributed to <%= user.repositoriesContributedTo.totalCount %> repositor<%= s(user.repositoriesContributedTo.totalCount, "y") %> <% for (const [x, {color}] of Object.entries(computed.calendar)) { -%># <% } %>
- followed by <%= user.followers.totalCount %> user<%= s(user.followers.totalCount) %>
-
<% } -%>
-<%# ============================================================= -%>
-<% if ((base.activity)||(base.community)) { %>
-<%- meta.$ %> git status
<%# -%>
-<%# -%>
-<% if (base.activity) { -%>
-Recent activity
- <%= `${computed.commits}`.padStart(5) %> commit<%= s(computed.commits) %>
- <%= `${user.contributionsCollection.totalPullRequestReviewContributions}`.padStart(5) %> pull request<%= s(user.contributionsCollection.totalPullRequestReviewContributions) %> reviewed
- <%= `${user.contributionsCollection.totalPullRequestContributions}`.padStart(5) %> pull request<%= s(user.contributionsCollection.totalPullRequestContributions) %> opened
- <%= `${user.contributionsCollection.totalIssueContributions}`.padStart(5) %> issue<%= s(user.contributionsCollection.totalIssueContributions) %> opened
- <%= `${user.issueComments.totalCount}`.padStart(5) %> issue comment<%= s(user.issueComments.totalCount) %>
-<% } -%>
-<% if ((base.activity)&&(base.community)) { -%>
-
-<% } -%>
-<% if (base.community) { -%>
-Tracked activity
- <%= `${user.following.totalCount}`.padStart(5) %> user<%= s(user.followers.totalCount) %> followed
- <%= `${user.sponsorshipsAsSponsor.totalCount}`.padStart(5) %> repositor<%= s(user.sponsorshipsAsSponsor.totalCount, "y") %> sponsored
- <%= `${user.starredRepositories.totalCount}`.padStart(5) %> repositor<%= s(user.starredRepositories.totalCount, "y") %> starred
- <%= `${user.watching.totalCount}`.padStart(5) %> repositor<%= s(user.watching.totalCount, "y") %> watched
-<% } -%>
-
<% } -%>
-<%# ============================================================= -%>
-<% if (base.repositories) { %>
-<%- meta.$ %> ls -lh github/repositories
<%# -%>
-<%# -%>
-Total <%= user.repositories.totalCount %> repositor<%= s(user.repositories.totalCount, "y") %> - <%= computed.diskUsage %>
-<% if (plugins.traffic) { if (plugins.traffic.error) { -%>
----- views (<%= plugins.traffic.error.message %>)
-<% } else { -%>
--r-- <%= `${plugins.traffic.views.count}`.padStart(5) %> views
-<% }} -%>
--r-- <%= `${computed.repositories.stargazers}`.padStart(5) %> stargazer<%= s(computed.repositories.stargazers) %>
--r-- <%= `${computed.repositories.forks}`.padStart(5) %> fork<%= s(computed.repositories.forks) %>
--r-- <%= `${computed.repositories.watchers}`.padStart(5) %> watcher<%= s(computed.repositories.watchers) %>
-dr-x <%= `${user.packages.totalCount}`.padStart(5) %> package<%= s(user.packages.totalCount) %>
-<% if (plugins.followup) { if (plugins.followup.error) { -%>
-d--- ISSUES (<%= plugins.followup.error.message %>)
-d--- PULL_REQUESTS (<%= plugins.followup.error.message %>)
-<% } else { -%>
-dr-x <%= `${plugins.followup.issues.count}`.padStart(5) %> ISSUES
--r-- <%= `${plugins.followup.issues.open}`.padStart(5) %> ├── open
--r-- <%= `${plugins.followup.issues.closed}`.padStart(5) %> └── closed
-dr-x <%= `${plugins.followup.issues.count}`.padStart(5) %> PULL_REQUESTS
--r-- <%= `${plugins.followup.pr.open}`.padStart(5) %> ├── open
--r-- <%= `${plugins.followup.pr.merged}`.padStart(5) %> └── merged
-<% }} -%>
-<% if (computed.licenses.favorite.length) { -%>
-dr-x LICENSE
--r-- └── <%= computed.licenses.favorite %>
-<% } -%>
-<% if (plugins.lines) { if (plugins.lines.error) { %>
-@@ <%= plugins.lines.error.message %> @@ <% } else { %>
-@@ -<%= plugins.lines.deleted %> +<%= plugins.lines.added %> @@
-<% }} -%>
-
<% } -%>
-<%# ============================================================= -%>
-<% if (plugins.gists) { %>
-<%- meta.$ %> ls -lh github/gists
<%# -%>
-<%# -%>
-Total <%= plugins.gists.totalCount %> gist<%= s(plugins.gists.totalCount) %>
--r-- <%= `${plugins.gists.stargazers}`.padStart(5) %> stargazer<%= s(plugins.gists.stargazers) %>
--r-- <%= `${plugins.gists.forks}`.padStart(5) %> fork<%= s(plugins.gists.forks) %>
-
<% } -%>
-<%# ============================================================= -%>
-<% if (plugins.languages) { %>
-<%- meta.$ %> locale
<%# -%>
-<%# -%>
-<% if (plugins.languages.error) { -%>
-<%= plugins.languages.error.message %> <%# -%>
-<% } else { for (const {name, value} of plugins.languages.favorites) { -%>
-<%= name.toLocaleUpperCase().padEnd(12) %> [<%= "#".repeat(Math.ceil(100*value/5)).padEnd(20) %>] <%= (100*value).toFixed(2).padEnd(5) %>%
-<% }} -%>
-
<% } -%>
-<%# ============================================================= -%>
-<% if (plugins.pagespeed) { %>
-<%- meta.$ %> curl -<%= plugins.pagespeed.detailed ? "v" : "" %>I <%= plugins.pagespeed.url %>
<%# -%>
-<%# -%>
-<% if (plugins.pagespeed.error) { -%>
-<%= plugins.pagespeed.error.message %> <% } else { -%>
-User-Agent : Google PageSpeed API
-Location : <%= plugins.pagespeed.url %>
-<% for (const {score, title} of plugins.pagespeed.scores) { -%>
-<%= `X-${title.replace(/ /g, "-")}` %> : <%= !Number.isNaN(score) ? Math.round(score*100) : "-" %>%
-<% } -%>
-<% if (plugins.pagespeed.detailed) { for (const {name, score, suffix = "", threshold} of [
- {name:"Time to interactive", score:plugins.pagespeed.metrics.interactive/1000, suffix:"s", threshold:[3.785, 7.3]},
- {name:"Speed Index", score:plugins.pagespeed.metrics.speedIndex/1000, suffix:"s", threshold:[3.387, 5.8]},
- {name:"Total Blocking Time", score:plugins.pagespeed.metrics.totalBlockingTime/1000, suffix:"s", threshold:[.287, .6]},
- {name:"First Contentful Paint", score:plugins.pagespeed.metrics.firstContentfulPaint/1000, suffix:"s", threshold:[2.336, 4]},
- {name:"Largest Contentful Paint", score:plugins.pagespeed.metrics.largestContentfulPaint/1000, suffix:"s", threshold:[2.5, 4]},
- {name:"Cumulative Layout Shift", score:+plugins.pagespeed.metrics.cumulativeLayoutShift, threshold:[.1, .25]}
-]) { -%>
-> <%= name %> : <%= !Number.isNaN(score) ? score : "-" %><%= suffix %> <% if (Number.isNaN(score)) { %><% } else if (score <= threshold[0]) { %>(ok +)<% } else if (score <= threshold[1]) { %>(ok)<% } else { %>(bad)<% } %>
-<% }}} -%>
-
<% } -%>
-<%# ============================================================= -%>
+<% for (const partial of [...partials]) { %><%- await include(`partials/${partial}.ejs`); %><% } -%>
<% if (base.metadata) { -%>
Connection reset by <%= Math.floor(256*Math.random()) %>.<%= Math.floor(256*Math.random()) %>.<%= Math.floor(256*Math.random()) %>.<%= Math.floor(256*Math.random()) %> <%# -%>
diff --git a/source/templates/terminal/partials/_.json b/source/templates/terminal/partials/_.json
new file mode 100644
index 00000000..ed85d40d
--- /dev/null
+++ b/source/templates/terminal/partials/_.json
@@ -0,0 +1,8 @@
+[
+ "base.header",
+ "base.activity+community",
+ "base.repositories",
+ "gists",
+ "languages",
+ "pagespeed"
+]
\ No newline at end of file
diff --git a/source/templates/terminal/partials/base.activity+community.ejs b/source/templates/terminal/partials/base.activity+community.ejs
new file mode 100644
index 00000000..04fb46e5
--- /dev/null
+++ b/source/templates/terminal/partials/base.activity+community.ejs
@@ -0,0 +1,22 @@
+<% if ((base.activity)||(base.community)) { %>
+<%- meta.$ %> git status
<%# -%>
+<%# -%>
+<% if (base.activity) { -%>
+Recent activity
+ <%= `${computed.commits}`.padStart(5) %> commit<%= s(computed.commits) %>
+ <%= `${user.contributionsCollection.totalPullRequestReviewContributions}`.padStart(5) %> pull request<%= s(user.contributionsCollection.totalPullRequestReviewContributions) %> reviewed
+ <%= `${user.contributionsCollection.totalPullRequestContributions}`.padStart(5) %> pull request<%= s(user.contributionsCollection.totalPullRequestContributions) %> opened
+ <%= `${user.contributionsCollection.totalIssueContributions}`.padStart(5) %> issue<%= s(user.contributionsCollection.totalIssueContributions) %> opened
+ <%= `${user.issueComments.totalCount}`.padStart(5) %> issue comment<%= s(user.issueComments.totalCount) %>
+<% } -%>
+<% if ((base.activity)&&(base.community)) { -%>
+
+<% } -%>
+<% if (base.community) { -%>
+Tracked activity
+ <%= `${user.following.totalCount}`.padStart(5) %> user<%= s(user.followers.totalCount) %> followed
+ <%= `${user.sponsorshipsAsSponsor.totalCount}`.padStart(5) %> repositor<%= s(user.sponsorshipsAsSponsor.totalCount, "y") %> sponsored
+ <%= `${user.starredRepositories.totalCount}`.padStart(5) %> repositor<%= s(user.starredRepositories.totalCount, "y") %> starred
+ <%= `${user.watching.totalCount}`.padStart(5) %> repositor<%= s(user.watching.totalCount, "y") %> watched
+<% } -%>
+
<% } -%>
\ No newline at end of file
diff --git a/source/templates/terminal/partials/base.header.ejs b/source/templates/terminal/partials/base.header.ejs
new file mode 100644
index 00000000..6ad8dd14
--- /dev/null
+++ b/source/templates/terminal/partials/base.header.ejs
@@ -0,0 +1,7 @@
+<% if (base.header) { %>
+<%- meta.$ %> whoami
<%# -%>
+<%# -%>
+<%= user.name || user.login %> registered=<%= computed.registration.match(/^.+? [ym]/)[0].replace(/ /g, "") %>, uid=<%= `${user.databaseId}`.substr(-4) %>, gid=<%= user.organizations.totalCount %>
+ contributed to <%= user.repositoriesContributedTo.totalCount %> repositor<%= s(user.repositoriesContributedTo.totalCount, "y") %> <% for (const [x, {color}] of Object.entries(computed.calendar)) { -%># <% } %>
+ followed by <%= user.followers.totalCount %> user<%= s(user.followers.totalCount) %>
+
<% } -%>
\ No newline at end of file
diff --git a/source/templates/terminal/partials/base.repositories.ejs b/source/templates/terminal/partials/base.repositories.ejs
new file mode 100644
index 00000000..3885c3d0
--- /dev/null
+++ b/source/templates/terminal/partials/base.repositories.ejs
@@ -0,0 +1,33 @@
+<% if (base.repositories) { %>
+<%- meta.$ %> ls -lh github/repositories
<%# -%>
+<%# -%>
+Total <%= user.repositories.totalCount %> repositor<%= s(user.repositories.totalCount, "y") %> - <%= computed.diskUsage %>
+<% if (plugins.traffic) { if (plugins.traffic.error) { -%>
+---- views (<%= plugins.traffic.error.message %>)
+<% } else { -%>
+-r-- <%= `${plugins.traffic.views.count}`.padStart(5) %> views
+<% }} -%>
+-r-- <%= `${computed.repositories.stargazers}`.padStart(5) %> stargazer<%= s(computed.repositories.stargazers) %>
+-r-- <%= `${computed.repositories.forks}`.padStart(5) %> fork<%= s(computed.repositories.forks) %>
+-r-- <%= `${computed.repositories.watchers}`.padStart(5) %> watcher<%= s(computed.repositories.watchers) %>
+dr-x <%= `${user.packages.totalCount}`.padStart(5) %> package<%= s(user.packages.totalCount) %>
+<% if (plugins.followup) { if (plugins.followup.error) { -%>
+d--- ISSUES (<%= plugins.followup.error.message %>)
+d--- PULL_REQUESTS (<%= plugins.followup.error.message %>)
+<% } else { -%>
+dr-x <%= `${plugins.followup.issues.count}`.padStart(5) %> ISSUES
+-r-- <%= `${plugins.followup.issues.open}`.padStart(5) %> ├── open
+-r-- <%= `${plugins.followup.issues.closed}`.padStart(5) %> └── closed
+dr-x <%= `${plugins.followup.issues.count}`.padStart(5) %> PULL_REQUESTS
+-r-- <%= `${plugins.followup.pr.open}`.padStart(5) %> ├── open
+-r-- <%= `${plugins.followup.pr.merged}`.padStart(5) %> └── merged
+<% }} -%>
+<% if (computed.licenses.favorite.length) { -%>
+dr-x LICENSE
+-r-- └── <%= computed.licenses.favorite %>
+<% } -%>
+<% if (plugins.lines) { if (plugins.lines.error) { %>
+@@ <%= plugins.lines.error.message %> @@ <% } else { %>
+@@ -<%= plugins.lines.deleted %> +<%= plugins.lines.added %> @@
+<% }} -%>
+
<% } -%>
\ No newline at end of file
diff --git a/source/templates/terminal/partials/gists.ejs b/source/templates/terminal/partials/gists.ejs
new file mode 100644
index 00000000..2941d2a1
--- /dev/null
+++ b/source/templates/terminal/partials/gists.ejs
@@ -0,0 +1,7 @@
+<% if (plugins.gists) { %>
+<%- meta.$ %> ls -lh github/gists
<%# -%>
+<%# -%>
+Total <%= plugins.gists.totalCount %> gist<%= s(plugins.gists.totalCount) %>
+-r-- <%= `${plugins.gists.stargazers}`.padStart(5) %> stargazer<%= s(plugins.gists.stargazers) %>
+-r-- <%= `${plugins.gists.forks}`.padStart(5) %> fork<%= s(plugins.gists.forks) %>
+
<% } -%>
\ No newline at end of file
diff --git a/source/templates/terminal/partials/languages.ejs b/source/templates/terminal/partials/languages.ejs
new file mode 100644
index 00000000..fb8186da
--- /dev/null
+++ b/source/templates/terminal/partials/languages.ejs
@@ -0,0 +1,9 @@
+<% if (plugins.languages) { %>
+<%- meta.$ %> locale
<%# -%>
+<%# -%>
+<% if (plugins.languages.error) { -%>
+<%= plugins.languages.error.message %> <%# -%>
+<% } else { for (const {name, value} of plugins.languages.favorites) { -%>
+<%= name.toLocaleUpperCase().padEnd(12) %> [<%= "#".repeat(Math.ceil(100*value/5)).padEnd(20) %>] <%= (100*value).toFixed(2).padEnd(5) %>%
+<% }} -%>
+
<% } -%>
\ No newline at end of file
diff --git a/source/templates/terminal/partials/pagespeed.ejs b/source/templates/terminal/partials/pagespeed.ejs
new file mode 100644
index 00000000..c435de55
--- /dev/null
+++ b/source/templates/terminal/partials/pagespeed.ejs
@@ -0,0 +1,21 @@
+<% if (plugins.pagespeed) { %>
+<%- meta.$ %> curl -<%= plugins.pagespeed.detailed ? "v" : "" %>I <%= plugins.pagespeed.url %>
<%# -%>
+<%# -%>
+<% if (plugins.pagespeed.error) { -%>
+<%= plugins.pagespeed.error.message %> <% } else { -%>
+User-Agent : Google PageSpeed API
+Location : <%= plugins.pagespeed.url %>
+<% for (const {score, title} of plugins.pagespeed.scores) { -%>
+<%= `X-${title.replace(/ /g, "-")}` %> : <%= !Number.isNaN(score) ? Math.round(score*100) : "-" %>%
+<% } -%>
+<% if (plugins.pagespeed.detailed) { for (const {name, score, suffix = "", threshold} of [
+ {name:"Time to interactive", score:plugins.pagespeed.metrics.interactive/1000, suffix:"s", threshold:[3.785, 7.3]},
+ {name:"Speed Index", score:plugins.pagespeed.metrics.speedIndex/1000, suffix:"s", threshold:[3.387, 5.8]},
+ {name:"Total Blocking Time", score:plugins.pagespeed.metrics.totalBlockingTime/1000, suffix:"s", threshold:[.287, .6]},
+ {name:"First Contentful Paint", score:plugins.pagespeed.metrics.firstContentfulPaint/1000, suffix:"s", threshold:[2.336, 4]},
+ {name:"Largest Contentful Paint", score:plugins.pagespeed.metrics.largestContentfulPaint/1000, suffix:"s", threshold:[2.5, 4]},
+ {name:"Cumulative Layout Shift", score:+plugins.pagespeed.metrics.cumulativeLayoutShift, threshold:[.1, .25]}
+]) { -%>
+> <%= name %> : <%= !Number.isNaN(score) ? score : "-" %><%= suffix %> <% if (Number.isNaN(score)) { %><% } else if (score <= threshold[0]) { %>(ok +)<% } else if (score <= threshold[1]) { %>(ok)<% } else { %>(bad)<% } %>
+<% }}} -%>
+
<% } -%>
\ No newline at end of file