Bauke/tildes-issue-log
Bauke
/
tildes-issue-log
Archived
1
Fork 0

Redesign the entire Issue Log and create the February 2020 post.

This commit is contained in:
Bauke 2020-02-29 23:48:09 +01:00
parent 1918acb117
commit 959c68125e
Signed by: Bauke
GPG Key ID: C1C0F29952BCF558
138 changed files with 5711 additions and 20869 deletions

54
.gitignore vendored
View File

@ -4,6 +4,10 @@ logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
@ -16,11 +20,12 @@ lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
@ -39,12 +44,21 @@ jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
@ -56,27 +70,45 @@ typings/
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
# Next.js build output
.next
# nuxt.js build output
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
.serverless/
# Config
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Output directories.
build/
public/
# Temporary files directory.
temp/
# Configuration file.
config.json
# Public
public
# Data
data

View File

@ -1,25 +1,25 @@
image: node:12
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- node --version
- yarn --version
- yarn
.common:
image: node:12
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- node --version
- yarn --version
- yarn
test:
extends: .common
stage: test
script:
- yarn test
pages:
extends: .common
stage: deploy
script:
- yarn build
artifacts:
paths:
- public
only:
- schedules
- public

View File

@ -1,16 +0,0 @@
## Highlights
- [ ] Placeholder until we get further into the month.
## Official Topics
| Date | Topic |
|------|-------|
| 2019-00-00 | [Placeholder.](https://tild.es) |
## Preflight Checklist
- [ ] Proofread the entire post.
- [ ] Run the post through a spellchecker.
- [ ] Update the statistics and issue table.
- [ ] Make sure the Table Of Contents links work properly.

View File

View File

@ -1,63 +0,0 @@
<img src="images/tildes-issue-log.png" height="128" align="right">
# Tildes Issue Log
> Monthly blog highlighting [Tildes.net](https://tildes.net) development
---
## 2020
* [January](https://til.bauke.xyz/posts/january-2020.html)
## 2019
* [December](https://til.bauke.xyz/posts/december-2019.html)
* [November](https://til.bauke.xyz/posts/november-2019.html)
* [October](https://til.bauke.xyz/posts/october-2019.html)
* [September](https://til.bauke.xyz/posts/september-2019.html)
* [August](https://til.bauke.xyz/posts/august-2019.html)
* [July](https://til.bauke.xyz/posts/july-2019.html)
* [June](https://til.bauke.xyz/posts/june-2019.html)
* [May](https://til.bauke.xyz/posts/may-2019.html)
* [April](https://til.bauke.xyz/posts/april-2019.html)
* [March](https://til.bauke.xyz/posts/march-2019.html)
* [February](https://til.bauke.xyz/posts/february-2019.html)
* [January](https://til.bauke.xyz/posts/january-2019.html)
## 2018
* [December](https://til.bauke.xyz/posts/december-2018.html)
* [November](https://til.bauke.xyz/posts/november-2018.html)
* [October](https://til.bauke.xyz/posts/october-2018.html)
* [September](https://til.bauke.xyz/posts/september-2018.html)
* [August](https://til.bauke.xyz/posts/august-2018.html)
* [July](https://til.bauke.xyz/posts/july-2018.html)
* [June](https://til.bauke.xyz/posts/june-2018.html)
* [May](https://til.bauke.xyz/posts/may-2018.html)
## Contributing
If you'd like to write a highlight section about a feature, please do! I'm not a writer by any means and would love to have other people write these instead. If you're not familiar with the Git workflow you can always [private message me](https://tildes.net/user/Bauke/new_message) with what you've written and I'll add your section for you, crediting your Tildes profile. If you'd like to learn Git, you can find tons of guides online. [Here's](https://try.github.io/) an excellent one.
## Building
If you'd like to build the issue table, statistics and/or website yourself you can follow these steps.
You'll need [Node JS](https://nodejs.org/) and [Yarn](https://yarnpkg.com/). You *can* use NPM however I don't. You probably know what you're doing though at that point.
* Clone the repository
* SSH: `git clone git@gitlab.com:Bauke/tildes-issue-log.git`
* HTTPS: `git clone https://gitlab.com/Bauke/tildes-issue-log.git`
* Change into the directory: `cd tildes-issue-log`
* Install the dependencies: `yarn`
* Create a `config.json` file from the sample config, including a [personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) from GitLab. **config.json is gitignored however make sure you don't publish your token somewhere on accident.**
* Run `yarn dl` to download the issues from the current month we're in and build the statistics and issue table, if you already have the issue files you can run `yarn nodl` to skip the downloading
* To easily develop the website itself you can run `yarn watch` to watch the `src/` folder for changes and automatically lint the sass and build the files. Running `yarn watch` will open your default browser at `localhost:3000`.
* To build the site only once you can do `yarn build`, the files will be built under `public/`, a requirement for GitLab Pages.
A new gitignored directory will be created called `data/` including all the files to generate the Statistics and the Issue Table files, which will appear in the "out" folder labelled as `MonthYear_table/statistics.html`. To generate them for a specific year and/or month you'll have to go into the Gulp file and change the `wantedYear` and/or the `wantedMonth`.
If you're working on the generation process you can run `yarn nodl` to skip the downloading phase, this will only generate `table.html` and `statistics.html`. This way you don't have to wait several seconds for the GitLab API to respond.
If you want to lint your sass (using [Stylelint](https://stylelint.io/)) manually you can run `yarn lint`, in the future when we have JavaScript (if ever) this will also include JS linting however as there's 0 JS ending up on the site at the moment there's no need for it.

9
ReadMe.md Normal file
View File

@ -0,0 +1,9 @@
# Tildes Issue Log
[Latest Post: **February 2020**](https://til.bauke.xyz/2020/february.html).
[Click here to view all posts](https://til.bauke.xyz/posts.html).
## License
Licensed under [AGPL-3.0-or-later](License).

3
config.sample.json Normal file
View File

@ -0,0 +1,3 @@
{
"gitlabToken": ""
}

View File

@ -1,65 +0,0 @@
# Issue Log Graphs
As a preliminary test, here are the topic activity graphs of every month so far (and the total one). Currently there are 2 different stats graphed, [topics per day](#topics-per-day): how many topics were posted for every day in the month and [topics each day](#topics-each-day): how many topics were posted on each day of the week in that month.
## Topics Per Day
This first graph is the total amount of topics of Tildes, divided into weeks instead of days. All the other graphs will be per day.
![Total topics per week](./topics_per_week.png)
Tildes launched on the 26th of April, so there's only a couple days in this graph. :P
![Topics per day: April 2018](./topics_per_day/2018_04.png)
![Topics per day: May 2018](./topics_per_day/2018_05.png)
![Topics per day: June 2018](./topics_per_day/2018_06.png)
![Topics per day: July 2018](./topics_per_day/2018_07.png)
![Topics per day: August 2018](./topics_per_day/2018_08.png)
![Topics per day: September 2018](./topics_per_day/2018_09.png)
![Topics per day: October 2018](./topics_per_day/2018_10.png)
![Topics per day: November 2018](./topics_per_day/2018_11.png)
![Topics per day: December 2018](./topics_per_day/2018_12.png)
![Topics per day: January 2019](./topics_per_day/2019_01.png)
![Topics per day: February 2019](./topics_per_day/2019_02.png)
![Topics per day: March 2019](./topics_per_day/2019_03.png)
## Topics Each Day
![Total topics per day](./topics_each_day.png)
Again the first month only has a select number of days to pull from.
![Topics per day: April 2018](./topics_each_day/2018_04.png)
![Topics per day: May 2018](./topics_each_day/2018_05.png)
![Topics per day: June 2018](./topics_each_day/2018_06.png)
![Topics per day: July 2018](./topics_each_day/2018_07.png)
![Topics per day: August 2018](./topics_each_day/2018_08.png)
![Topics per day: September 2018](./topics_each_day/2018_09.png)
![Topics per day: October 2018](./topics_each_day/2018_10.png)
![Topics per day: November 2018](./topics_each_day/2018_11.png)
![Topics per day: December 2018](./topics_each_day/2018_12.png)
![Topics per day: January 2019](./topics_each_day/2019_01.png)
![Topics per day: February 2019](./topics_each_day/2019_02.png)
![Topics per day: March 2019](./topics_each_day/2019_03.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

View File

@ -1,458 +0,0 @@
// Require dependencies
const path = require('path');
const cheerio = require('cheerio');
const df = require('date-format');
const {Feed} = require('feed');
const fs = require('fs-extra');
const GitLab = require('gitlab').Gitlab;
const gulp = require('gulp');
const htmlclean = require('gulp-htmlclean');
const klaw = require('klaw-sync');
const log = require('fancy-log');
const merge2 = require('merge2');
const scss = require('gulp-sass');
const sync = require('browser-sync');
// Require statistic functions
const {avgTime, freqUsers, labelsAlphabet, changedLines, uniqueContributors} = require('./statistics');
// Define paths that are gonna be used commonly
const paths = {
data: {
commits: path.join(__dirname, 'data/commits/'),
issues: {
open: path.join(__dirname, 'data/issues/open/'),
closed: path.join(__dirname, 'data/issues/closed/'),
out: path.join(__dirname, 'data/issues/out/')
}
},
extra: path.join(__dirname, 'src/favicons/**'),
html: {
index: path.join(__dirname, 'src/index.html'),
posts: path.join(__dirname, 'src/posts/*.html')
},
out: path.join(__dirname, 'public/'),
scss: path.join(__dirname, 'src/scss/*.scss')
};
// Define options for Node Sass and Browser Sync
const opts = {
scss: {
outputStyle: 'compressed'
},
sync: {
server: {
baseDir: paths.out
}
}
};
// The data to download from specified month, months are zero-based zo January would be 0
// Make sure both of these are **numbers**, if they are strings it won't work properly!
const wantedMonth = new Date().getMonth();
// Since we've passed from 2018 into 2019 we also have to start checking for year now
const wantedYear = new Date().getFullYear();
// Init the months array, probably a way to do this with Dates but this works too
const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
// Add the year and month to the open/closed/out path so they're easy to identify
const commitsPath = `${paths.data.commits}${wantedYear}/${months[wantedMonth]}/`;
const openIssuesPath = `${paths.data.issues.open}${wantedYear}/${months[wantedMonth]}/`; // Folder
const closedIssuesPath = `${paths.data.issues.closed}${wantedYear}/${months[wantedMonth]}/`; // Folder
const outIssuesPath = `${paths.data.issues.out}${months[wantedMonth]}${wantedYear}`; // Will become table and statistics files
// Make the directories using fs-extra's "mkdir -p" equivalent
// It will make any directory that doesn't yet exist in the path
fs.mkdirpSync(commitsPath);
fs.mkdirpSync(openIssuesPath);
fs.mkdirpSync(closedIssuesPath);
fs.mkdirpSync(paths.data.issues.out);
// Create the browser sync server, it only starts when using `gulp watch` however
const server = sync.create();
// Copy over the HTML, using merge2 to use Gulp's async completion and multiple src's
function buildHTML() {
return merge2([
gulp.src(paths.html.index).pipe(htmlclean()).pipe(gulp.dest(paths.out)),
gulp.src(paths.html.posts).pipe(htmlclean()).pipe(gulp.dest(paths.out + 'posts/'))
]);
}
// Build the CSS
function buildCSS() {
return gulp
.src(paths.scss)
.pipe(scss(opts.scss))
.pipe(gulp.dest(paths.out + 'css/'));
}
// Build the extra stuff, for now only the favicons
function buildExtra() {
return gulp
.src(paths.extra)
.pipe(gulp.dest(paths.out));
}
// Start the Browser Sync server and watch individual file types with appropriate build functions
function watch() {
server.init(opts.sync);
gulp.watch([paths.html.index, paths.html.posts], gulp.series(buildHTML, createFeeds, reload));
gulp.watch(paths.scss, gulp.series(buildCSS, reload));
gulp.watch(paths.extra, gulp.series(buildExtra, reload));
}
// To use Gulp's async completion system this has to be done, it's ugly but can't do without it
function reload(callback) {
server.reload();
callback();
}
function download() {
// Create the API with the token
const api = new GitLab({token: require('./config.json').token});
// Return a new Promise so we can take advantage of Gulp's async completion system
// We'll reject whenever there is an error and resolve when everything is completed
return new Promise((resolve, reject) => {
// The Node GitLab API is a bit weird, first we have to find the project Tildes/Tildes
api.Projects
.show('tildes/tildes')
.catch(error => reject(new Error('There was an error fetching the project:', error)))
.then(project => {
log('Found project, downloading issues...');
// Then once we find the project we can use it and its ID to download the issues
api.Issues
.all({projectId: project.id})
.catch(error => reject(new Error('There was an error downloading the issues:', error)))
.then(issues => {
// And then once we've downloaded all the issues we can write them to file appropriately
log(`Downloaded issues, saving opened and closed issues from ${months[wantedMonth]} ${wantedYear} to file...`);
for (const issue of issues) {
const createdDate = new Date(issue.created_at);
if (createdDate.getFullYear() === wantedYear &&
createdDate.getMonth() === wantedMonth) {
fs.writeFileSync(openIssuesPath + `${issue.iid}.json`, JSON.stringify(issue, null, 2));
}
const closedDate = new Date(issue.closed_at);
if (issue.closed_at !== null &&
closedDate.getFullYear() === wantedYear &&
closedDate.getMonth() === wantedMonth) {
fs.writeFileSync(closedIssuesPath + `${issue.iid}.json`, JSON.stringify(issue, null, 2));
}
}
log('Finished writing issues to file.');
log('Downloading commits...');
})
.then(() => {
api.Commits.all(project.id, {ref_name: 'master', with_stats: true})
.catch(error => reject(new Error('There was an error downloading the commits:', error)))
.then(commits => {
log(`Downloaded commits, saving commits from ${months[wantedMonth]} ${wantedYear} to file...`);
for (const commit of commits) {
const authoredDate = new Date(commit.authored_date);
if (authoredDate.getFullYear() === wantedYear &&
authoredDate.getMonth() === wantedMonth) {
fs.writeFileSync(commitsPath + `${commit.short_id}.json`, JSON.stringify(commit, null, 2));
}
}
log('Finished writing commits to file.');
resolve();
});
});
});
});
}
function createIssueTable() {
// Using a Promise again for Gulp's async completion
return new Promise(resolve => {
// Klaw returns all files in a directory recursively so we're getting all opened and closed issue files
const opened = klaw(openIssuesPath);
const closed = klaw(closedIssuesPath);
// Then we want to sort all of these issue files in their arrays
opened.sort((a, b) => {
const aFile = require(a.path);
const bFile = require(b.path);
return (aFile.iid > bFile.iid) ? 1 : ((bFile.iid > aFile.iid) ? -1 : 0);
});
closed.sort((a, b) => {
const aFile = require(a.path);
const bFile = require(b.path);
return (aFile.iid > bFile.iid) ? 1 : ((bFile.iid > aFile.iid) ? -1 : 0);
});
// And then generate the Issue Table HTML, which is kind of a mess to do
let table = '<article id="issue-table">\n';
table += ' <h2>Issue Table</h2>\n';
table += ' <h3 id="opened">Opened</h3>\n';
table += ' <table>\n';
table += ' <thead>\n';
table += ' <tr>\n';
table += ' <td>Issue</td>\n';
table += ' <td>Title</td>\n';
table += ' <td>Author</td>\n';
table += ' <td>Opened</td>\n';
table += ' <td>Closed</td>\n';
table += ' </tr>\n';
table += ' </thead>\n';
table += ' <tbody>\n';
for (const file of opened) {
const issue = require(file.path);
table += ' <tr>\n';
table += ` <td><a href="${issue.web_url}">${issue.iid}</a></td>\n`;
let title;
if (issue.title.length >= 50) {
// We're going to be replacing all instances of <> signs to make sure nobody can add
// <script></script> in their issue title and run JS on the site or mess up the layout or something
// I do check myself before I commit and push anything but I'd rather be completely sure.
title = issue.title.slice(0, 47).replace(/[<>]/g, '') + '...';
} else {
title = issue.title.replace(/[<>]/g, '');
}
table += ` <td>${title}</td>\n`;
table += ` <td><a href="${issue.author.web_url}">${issue.author.username}</a></td>\n`;
table += ` <td>${df.asString('yyyy/MM/dd hh:mm:ss', new Date(issue.created_at))}</td>\n`;
let closedAt;
if (issue.closed_at === null) {
closedAt = '';
} else {
closedAt = df.asString('yyyy/MM/dd hh:mm:ss', new Date(issue.closed_at));
}
table += ` <td>${closedAt}</td>\n`;
table += ' </tr>\n';
}
table += ' </tbody>\n';
table += ' </table>\n\n';
table += ' <h3 id="closed">Closed</h3>\n';
table += ' <table>\n';
table += ' <thead>\n';
table += ' <tr>\n';
table += ' <td>Issue</td>\n';
table += ' <td>Title</td>\n';
table += ' <td>Author</td>\n';
table += ' <td>Opened</td>\n';
table += ' <td>Closed</td>\n';
table += ' </tr>\n';
table += ' <thead>\n';
table += ' <tbody>\n';
for (const file of closed) {
const issue = require(file.path);
table += ' <tr>\n';
table += ` <td><a href="${issue.web_url}">${issue.iid}</a></td>\n`;
let title;
if (issue.title.length >= 50) {
title = issue.title.slice(0, 47).replace(/[<>]/g, '') + '...';
} else {
title = issue.title.replace(/[<>]/g, '');
}
table += ` <td>${title}\n`;
table += ` <td><a href="${issue.author.web_url}">${issue.author.username}</a>\n`;
table += ` <td>${df.asString('yyyy/MM/dd hh:mm:ss', new Date(issue.created_at))}</td>\n`;
let closedAt;
if (issue.closed_at === null) {
closedAt = '';
} else {
closedAt = df.asString('yyyy/MM/dd hh:mm:ss', new Date(issue.closed_at));
}
table += ` <td>${closedAt}</td>\n`;
table += ' </tr>\n';
}
table += ' </tbody>\n';
table += ' </table>\n';
table += '</article>\n';
// And finally when the HTML is done generating we can write it and resolve that Promise we made
fs.writeFileSync(outIssuesPath + '_table.html', table, {encoding: 'UTF-8'});
resolve();
});
}
function createStatistics() {
return new Promise(resolve => {
// Same process as the Issue Table generation
const commits = klaw(commitsPath);
const opened = klaw(openIssuesPath);
const closed = klaw(closedIssuesPath);
let statistics = '<article id="statistics">\n';
statistics += ' <h2>Statistics</h2>\n';
const commitStats = changedLines(commits);
const contributors = uniqueContributors(commits);
statistics += ` <p>In the month of ${months[wantedMonth]}, `;
statistics += `${commits.length} commits were made by ${contributors.length} contributors, `;
statistics += `changing a total of ${Math.abs(commitStats.total)} (+${commitStats.added}|-${commitStats.deleted}) lines. `;
statistics += `${opened.length} issues were opened and `;
statistics += `${closed.length} issues were closed.</p>\n`;
statistics += ` <p>An average of ${(opened.length / 30).toFixed(2)} issues were opened `;
statistics += `and ${(closed.length / 30).toFixed(2)} issues were closed each day.</p>\n`;
statistics += ` <p>The average time to close issues was ${avgTime(closed, 'days')} days `;
statistics += `or ${avgTime(closed, 'hours')} hours.</p>\n`;
const topUsers = freqUsers(opened, 3);
statistics += ' <p>Top 3 issue creators:</p>\n';
statistics += ' <ol>\n';
for (const user in topUsers) {
statistics += ' <li>\n';
statistics += ` <a href="https://gitlab.com/${user}">${user}</a>`;
statistics += ' with ';
statistics += `<a href="https://gitlab.com/tildes/tildes/issues?state=all&author_username=${user}">${topUsers[user]} issues created</a>.\n`;
statistics += ' </li>\n';
}
statistics += ' </ol>\n';
let labels = labelsAlphabet(opened, true);
statistics += ' <p>Amount of labels assigned to currently open issues:</p>\n';
statistics += ' <ul>\n';
for (const label in labels) {
statistics += ' <li>\n';
statistics += ` <a href="https://gitlab.com/tildes/tildes/issues?state=opened&label_name%5B%5D=${label.replace(' ', '+')}")>${label}</a>:`;
statistics += `${labels[label]} `;
if (labels[label] === 1) {
statistics += 'time.\n';
} else {
statistics += 'times.\n';
}
statistics += ' </li>\n';
}
statistics += ' </ul>\n';
labels = labelsAlphabet(closed, false);
statistics += ' <p>Amount of labels assigned to closed issues:</p>\n';
statistics += ' <ul>\n';
for (const label in labels) {
statistics += ' <li>\n';
statistics += ` <a href="https://gitlab.com/tildes/tildes/issues?state=opened&label_name%5B%5D=${label.replace(' ', '+')}")>${label}</a>:`;
statistics += `${labels[label]} `;
if (labels[label] === 1) {
statistics += 'time.\n';
} else {
statistics += 'times.\n';
}
statistics += ' </li>\n';
}
statistics += ' </ul>\n';
statistics += '</article>\n';
fs.writeFileSync(outIssuesPath + '_statistics.html', statistics, {encoding: 'UTF-8'});
resolve();
});
}
function createFeeds() {
const feed = new Feed({
title: 'Tildes Issue Log',
description: 'Monthly blog highlighting the changes of Tildes.net',
id: 'https://til.bauke.xyz',
link: 'https://til.bauke.xyz',
language: 'en',
image: 'https://til.bauke.xyz/android-chrome-192x192.png',
favicon: 'https://til.bauke.xyz/favicon.ico',
copyright: 'AGPL-3.0-or-later Tildes Issue Log Contributors https://gitlab.com/Bauke/tildes-issue-log',
generator: 'https://github.com/jpmonette/feed',
feedLinks: {
atom: 'https://til.bauke.xyz/feed.atom',
json: 'https://til.bauke.xyz/feed.json',
rss: 'https://til.bauke.xyz/feed.rss'
},
author: {
name: 'Bauke',
email: 'me@bauke.xyz',
link: 'https://bauke.xyz'
}
});
const posts = fs.readdirSync(path.join(paths.out, 'posts'));
// Remove the template, that doesn't need to be included
posts.splice(posts.indexOf('template.html'), 1);
// Sort the posts descending year and month
posts.sort((a, b) => {
const yearA = Number(a.replace(/\D/g, ''));
const yearB = Number(b.replace(/\D/g, ''));
if (yearA === yearB) {
const monthA = months.join(',').toLowerCase().split(',').indexOf(a.slice(0, a.indexOf('-'))) + 1;
const monthB = months.join(',').toLowerCase().split(',').indexOf(b.slice(0, b.indexOf('-'))) + 1;
return monthB - monthA;
}
return yearB - yearA;
});
for (let i = 0; i < 5; i++) {
const post = posts[i];
const html = fs.readFileSync(path.join(paths.out, 'posts', post), 'UTF8');
const $ = cheerio.load(html);
const title = $('#wrapper>h1').text();
const id = `https://til.bauke.xyz/posts/${post}`;
const date = new Date(Date.UTC(
Number(post.replace(/\D/g, '')),
// Add one to the month since UTC months are 0 based and since we set the
// day as 0 we'll get the Date back as the last day of the previous month
months.join(',').toLowerCase().split(',').indexOf(post.slice(0, post.indexOf('-'))) + 1,
0, 23, 59, 59
));
const content = $('#post')
.html()
.replace(/<article id="toc">.+?<\/article>/g, ''); // Remove the TOC
feed.addItem({
title,
id,
link: id,
date,
published: date,
description: `${title}'s Issue Log`,
content,
image: 'https://til.bauke.xyz/android-chrome-192x192.png'
});
}
fs.writeFileSync(path.join(paths.out, 'feed.atom'), feed.atom1());
fs.writeFileSync(path.join(paths.out, 'feed.json'), feed.json1());
fs.writeFileSync(path.join(paths.out, 'feed.rss'), feed.rss2());
return Promise.resolve();
}
exports.build = gulp.series(gulp.parallel(buildHTML, buildCSS, buildExtra), createFeeds);
exports.download = gulp.series(download, gulp.parallel(createIssueTable, createStatistics));
exports.no_download = gulp.parallel(createIssueTable, createStatistics);
exports.watch = gulp.series(gulp.parallel(buildHTML, buildCSS, buildExtra), createFeeds, watch);

View File

@ -1,34 +1,50 @@
{
"name": "tildes-issue-log",
"description": "Monthly blog highlighting the changes of Tildes.net",
"description": "The Tildes Issue Log is a monthly blog about the development of Tildes.",
"author": "Bauke <me@bauke.xyz>",
"version": "2.0.0",
"license": "AGPL-3.0-or-later",
"scripts": {
"watch": "gulp watch",
"build": "gulp build",
"dl": "gulp download",
"nodl": "gulp no_download",
"test": "xo ; stylelint src/scss/"
"build": "mkdir 'public/images/' 'public/fonts/' -p && yarn build:assets && yarn build:html && yarn build:images && yarn build:js && yarn build:redirects && yarn build:sass",
"build:assets": "TZ=UTC ts-node 'source/scripts/assets.ts'",
"build:html": "TZ=UTC ts-node 'source/scripts/html.ts'",
"build:images": "cpy 'source/pages/images/**' 'public/images/'",
"build:js": "cpy 'source/pages/js/**' 'public/js/'",
"build:redirects": "TZ=UTC ts-node 'source/scripts/redirects.ts'",
"build:sass": "sass 'source/pages/scss/style.scss':'public/css/style.css' --style=compressed",
"download": "TZ=UTC ts-node 'source/scripts/download.ts'",
"official-topics": "TZ=UTC ts-node 'source/scripts/official-topics.ts'",
"statistics": "TZ=UTC ts-node 'source/scripts/statistics.ts'",
"test": "xo && stylelint 'source/pages/scss/**'"
},
"dependencies": {
"modern-normalize": "^0.6.0"
},
"dependencies": {},
"devDependencies": {
"browser-sync": "^2.24.5",
"@types/cheerio": "^0.22.16",
"@types/got": "^9.6.9",
"@types/marked": "^0.7.2",
"@types/nunjucks": "^3.1.3",
"@types/tar": "^4.0.3",
"@types/wordwrap": "^1.0.0",
"cheerio": "^1.0.0-rc.3",
"date-format": "^3.0.0",
"fancy-log": "^1.3.3",
"feed": "^4.0.0",
"fs-extra": "^8.1.0",
"gitlab": "^12.0.1",
"gulp": "^4.0.0",
"gulp-htmlclean": "^2.7.22",
"gulp-pug": "^4.0.1",
"gulp-sass": "^4.0.1",
"klaw-sync": "^6.0.0",
"merge2": "^1.3.0",
"stylelint": "^12.0.0",
"stylelint-config-xo-scss": "^0.9.0",
"stylelint-config-xo-space": "^0.13.0",
"xo": "^0.25.3"
"cpy-cli": "^3.1.0",
"fecha": "^4.1.0",
"feed": "^4.1.0",
"gitlab": "^14.2.2",
"got": "^10.6.0",
"htmlclean": "^3.0.8",
"marked": "^0.8.0",
"nunjucks": "^3.2.0",
"sass": "^1.26.1",
"stylelint": "^13.2.0",
"stylelint-config-xo-scss": "^0.12.0",
"stylelint-config-xo-space": "^0.14.0",
"tar": "^6.0.1",
"ts-node": "^8.6.2",
"typescript": "^3.8.2",
"wordwrap": "^1.0.0",
"xo": "^0.27.2"
},
"stylelint": {
"extends": [
@ -36,14 +52,28 @@
"stylelint-config-xo-space"
],
"rules": {
"block-no-empty": null
"scss/at-import-partial-extension": null,
"scss/no-duplicate-dollar-variables": null,
"at-rule-empty-line-before": null,
"at-rule-no-unknown": null,
"no-descending-specificity": null
}
},
"xo": {
"space": true,
"overrides": [
{
"files": "source/pages/**/*.js",
"globals": [
"document",
"window"
]
}
],
"prettier": true,
"rules": {
"camelcase": "off",
"guard-for-in": "off"
}
"max-params": "off",
"no-await-in-loop": "off"
},
"space": true
}
}

View File

@ -1,3 +0,0 @@
{
"token": "your-private-token"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,462 @@
[
{
"contributions": {
"authors": 0,
"commits": 0
},
"issues": {
"closed": 0,
"opened": 7
},
"lines": {
"added": 0,
"removed": 0
},
"mergeRequests": {
"closed": 0,
"opened": 0
},
"month": 4,
"year": 2018
},
{
"contributions": {
"authors": 0,
"commits": 0
},
"issues": {
"closed": 27,
"opened": 91
},
"lines": {
"added": 0,
"removed": 0
},
"mergeRequests": {
"closed": 0,
"opened": 0
},
"month": 5,
"year": 2018
},
{
"contributions": {
"authors": 0,
"commits": 0
},
"issues": {
"closed": 19,
"opened": 51
},
"lines": {
"added": 0,
"removed": 0
},
"mergeRequests": {
"closed": 0,
"opened": 0
},
"month": 6,
"year": 2018
},
{
"contributions": {
"authors": 6,
"commits": 25
},
"issues": {
"closed": 29,
"opened": 31
},
"lines": {
"added": 26686,
"removed": 160
},
"mergeRequests": {
"closed": 11,
"opened": 18
},
"month": 7,
"year": 2018
},
{
"contributions": {
"authors": 10,
"commits": 78
},
"issues": {
"closed": 38,
"opened": 60
},
"lines": {
"added": 6296,
"removed": 4059
},
"mergeRequests": {
"closed": 13,
"opened": 15
},
"month": 8,
"year": 2018
},
{
"contributions": {
"authors": 4,
"commits": 66
},
"issues": {
"closed": 15,
"opened": 32
},
"lines": {
"added": 3455,
"removed": 1459
},
"mergeRequests": {
"closed": 3,
"opened": 5
},
"month": 9,
"year": 2018
},
{
"contributions": {
"authors": 6,
"commits": 47
},
"issues": {
"closed": 24,
"opened": 38
},
"lines": {
"added": 2079,
"removed": 901
},
"mergeRequests": {
"closed": 12,
"opened": 11
},
"month": 10,
"year": 2018
},
{
"contributions": {
"authors": 2,
"commits": 16
},
"issues": {
"closed": 6,
"opened": 21
},
"lines": {
"added": 434,
"removed": 259
},
"mergeRequests": {
"closed": 1,
"opened": 3
},
"month": 11,
"year": 2018
},
{
"contributions": {
"authors": 2,
"commits": 16
},
"issues": {
"closed": 6,
"opened": 14
},
"lines": {
"added": 220,
"removed": 201
},
"mergeRequests": {
"closed": 0,
"opened": 1
},
"month": 12,
"year": 2018
},
{
"contributions": {
"authors": 3,
"commits": 48
},
"issues": {
"closed": 6,
"opened": 6
},
"lines": {
"added": 1324,
"removed": 297
},
"mergeRequests": {
"closed": 1,
"opened": 1
},
"month": 1,
"year": 2019
},
{
"contributions": {
"authors": 3,
"commits": 36
},
"issues": {
"closed": 11,
"opened": 20
},
"lines": {
"added": 717,
"removed": 393
},
"mergeRequests": {
"closed": 9,
"opened": 7
},
"month": 2,
"year": 2019
},
{
"contributions": {
"authors": 5,
"commits": 51
},
"issues": {
"closed": 17,
"opened": 25
},
"lines": {
"added": 1417,
"removed": 445
},
"mergeRequests": {
"closed": 6,
"opened": 3
},
"month": 3,
"year": 2019
},
{
"contributions": {
"authors": 7,
"commits": 48
},
"issues": {
"closed": 15,
"opened": 47
},
"lines": {
"added": 1582,
"removed": 553
},
"mergeRequests": {
"closed": 6,
"opened": 2
},
"month": 4,
"year": 2019
},
{
"contributions": {
"authors": 3,
"commits": 45
},
"issues": {
"closed": 26,
"opened": 38
},
"lines": {
"added": 1296,
"removed": 398
},
"mergeRequests": {
"closed": 2,
"opened": 3
},
"month": 5,
"year": 2019
},
{
"contributions": {
"authors": 3,
"commits": 47
},
"issues": {
"closed": 10,
"opened": 20
},
"lines": {
"added": 5595,
"removed": 784
},
"mergeRequests": {
"closed": 2,
"opened": 2
},
"month": 6,
"year": 2019
},
{
"contributions": {
"authors": 4,
"commits": 32
},
"issues": {
"closed": 20,
"opened": 21
},
"lines": {
"added": 724,
"removed": 414
},
"mergeRequests": {
"closed": 3,
"opened": 4
},
"month": 7,
"year": 2019
},
{
"contributions": {
"authors": 3,
"commits": 42
},
"issues": {
"closed": 70,
"opened": 34
},
"lines": {
"added": 595,
"removed": 239
},
"mergeRequests": {
"closed": 2,
"opened": 6
},
"month": 8,
"year": 2019
},
{
"contributions": {
"authors": 5,
"commits": 51
},
"issues": {
"closed": 7,
"opened": 13
},
"lines": {
"added": 1094,
"removed": 439
},
"mergeRequests": {
"closed": 5,
"opened": 1
},
"month": 9,
"year": 2019
},
{
"contributions": {
"authors": 3,
"commits": 47
},
"issues": {
"closed": 9,
"opened": 18
},
"lines": {
"added": 2082,
"removed": 833
},
"mergeRequests": {
"closed": 1,
"opened": 0
},
"month": 10,
"year": 2019
},
{
"contributions": {
"authors": 2,
"commits": 42
},
"issues": {
"closed": 7,
"opened": 19
},
"lines": {
"added": 1407,
"removed": 324
},
"mergeRequests": {
"closed": 1,
"opened": 1
},
"month": 11,
"year": 2019
},
{
"contributions": {
"authors": 3,
"commits": 30
},
"issues": {
"closed": 9,
"opened": 9
},
"lines": {
"added": 614,
"removed": 362
},
"mergeRequests": {
"closed": 3,
"opened": 4
},
"month": 12,
"year": 2019
},
{
"contributions": {
"authors": 4,
"commits": 35
},
"issues": {
"closed": 15,
"opened": 15
},
"lines": {
"added": 2263,
"removed": 996
},
"mergeRequests": {
"closed": 4,
"opened": 4
},
"month": 1,
"year": 2020
},
{
"contributions": {
"authors": 4,
"commits": 27
},
"issues": {
"closed": 11,
"opened": 14
},
"lines": {
"added": 980,
"removed": 190
},
"mergeRequests": {
"closed": 9,
"opened": 10
},
"month": 2,
"year": 2020
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
source/pages/images/sun.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,24 @@
window.addEventListener('load', () => {
// On page load, change the theme to light if they have that selected.
// Dark is selected by default.
const themeToggleButton = document.querySelector('button#theme-toggle');
if (window.localStorage.getItem('theme') === 'light-theme') {
document.body.setAttribute('id', 'light-theme');
themeToggleButton.classList.add('light');
}
// When the theme button is clicked, switch the theme to the other one.
themeToggleButton.addEventListener('click', event => {
event.preventDefault();
if (document.body.getAttribute('id') === 'dark-theme') {
document.body.setAttribute('id', 'light-theme');
themeToggleButton.classList.add('light');
} else {
document.body.setAttribute('id', 'dark-theme');
themeToggleButton.classList.remove('light');
}
window.localStorage.setItem('theme', document.body.getAttribute('id'));
});
});

View File

@ -0,0 +1,36 @@
<h2 id="august-2018">
August 2018
<span id="authors">by @Bauke and @Kat</span>
</h2>
### Contributions
Since [last month's open-sourcing](https://til.bauke.xyz/2018/july.html#open-source) plenty of contributions have come in by various users, so let's go through some of those. You can find the official topics about these listed below in [the Notable Official Topics table](#notable-official-topics).
* Special styles for the [NSFW](https://tildes.net/?tag=nsfw) and [Spoiler](https://tildes.net/?tag=spoiler) tags have been added, as well as making the always appear first in the tag list. This was added by [Ivan Fonseca](https://gitlab.com/ivanfon). You can find the merge request here.
* A "Mark All As Read" button has been added, so you don't have to manually mark all of your notifications yourself. If you have any unread notifications you can find the button under [notifications/unread](https://tildes.net/notifications/unread). This was added by [James Southern](https://gitlab.com/jms301). You can find the merge request [here](https://gitlab.com/tildes/tildes/merge_requests/21).
* 2 factor authentication! The long awaited, much requested security feature has been added and you can find it in [your settings](https://tildes.net/settings/two_factor) as usual. Don't forget to write down your backup codes somewhere, they're important! The feature was added by [thesbros](https://gitlab.com/thesbros) and you can find the merge request [here](https://gitlab.com/tildes/tildes/merge_requests/18).
* Username mentions, now when someone posts a comment with "@username" that person will receive a notification. It was added by [Celeo](https://gitlab.com/Celeo) and you can find the merge request [here](https://gitlab.com/tildes/tildes/merge_requests/6).
* Default theme for your account, in [your settings](https://tildes.net/settings) you can now set a default theme for your account so it will always use that one on new devices. However if you change the theme without setting the account default, that'll be the one used in your session using your browser's cookies. So you can still change to White in the middle of the night manually, if you don't like your eyes. This was another contribution by [Celeo](https://gitlab.com/Celeo) and the merge request is [here](https://gitlab.com/tildes/tildes/merge_requests/25).
* Collapsing and expanding all non-top-level comments buttons have been added just above the comments section. Which will definitely come in handy in some larger threads. This was added by [Jeff Kayser](https://gitlab.com/jeffkayser) and the merge request is [here](https://gitlab.com/tildes/tildes/merge_requests/26).
### Changes
Of course there hasn't been just contributions by other users, so let's give @Deimos some love too for what he's changed this month. Official topics can again be found below.
* The editing grace period has been increased to 5 minutes, so you'll have 5 minutes to edit your comment/topic before the "edited ... ago" will show up. The previous grace period was 2 minutes, a little too short.
* Your profile now has pagination, meaning you can go aaaalll the way back to your first comment and/or topic and see how you embarrassed yourself without even realizing it. Topics can be looked through by going to your profile and clicking on the buttons at the top or by adding `?type=topic` after your profile URL, like this for my profile: `https://tildes.net/user/Bauke?type=topic` and of course the same with comments except you do `?type=comment` for that.
* Some new Markdown has also been enabled, namely Tables and Strikethrough. So now you can aesliy easily remove that incorrect thing you typed and make tables much quicker with it's simple pipe syntax. The [Text Formatting](https://docs.tildes.net/text-formatting#strikethrough) page has been updated with the new additions.
* And, auto-collapsing of old comments has been added to the ["mark new comments" feature](https://tildes.net/settings/comment_visits), making it easier to follow threads if you have it enabled.
### Permissions
Halfway through August [a topic was posted](https://tild.es/53r) that introduced several permissions that could be granted manually to users. Namely, editing a topic's tags, title and which group it's in.
So far tag editing permissions have been given to quite a number of people. Moving topics to different groups to less people and title editing to nobody yet, as far as I know.
### Wiki & Chats
In response to user request, the 18th saw the creation of [the unofficial Tildes wiki](https://tildeswiki.katsuricata.com/), born for the purpose of helping groups create morepermanent archives of content and catalogue some community best practices until Tildes gets an official one onsite. Some good examples are [the \~hobbies page](https://tildeswiki.katsuricata.com/doku.php/hobbies), which lists the hobbies that currently have dedicated megathreads—alongside some suggestions for new ones—and [\~techs list of FOSS alternatives for closedsource software](https://tildeswiki.katsuricata.com/doku.php/tech:foss_alternatives_to_popular_services), for those just getting into a more privacyfocused mindset without knowing where to start.
Its also home to the list of [all currentlyknown unofficial community chats](https://tildeswiki.katsuricata.com/doku.php/chats), for when you want to talk to fellow Tildes users in a more casual setting, as well as the [offsite user directory](https://tildeswiki.katsuricata.com/doku.php/users), should you wish to detail a profile for yourself more comprehensive than what the site allows. It is currently editable by anyone without registration (though registration will hide your IP address on public edit logs), so if youd like to help build it up, take a look at [the manual](https://tildeswiki.katsuricata.com/doku.php/wiki:dokuwiki) and [syntax guide](https://tildeswiki.katsuricata.com/doku.php/wiki:syntax) and make a few contributions! Right now, most of the edits are from some power user, and we dont want to let them get undue influence over the wiki.

View File

@ -0,0 +1,16 @@
<h2 id="december-2018">
December 2018
<span id="authors">by @Bauke</span>
</h2>
### Topic Ownership
On December 18th, Deimos posted a topic to discuss potential site mechanics of topics and who owns them that gained a wild variety of responses that's worth checking out if you haven't already: [click here to read it](https://tild.es/989).
### Shortlinks
Something that has been planned [for quite a while](https://tild.es/15m) has been added on December 22nd. Shortlinks!
You can now share topics via the shortlink that is available in the sidebar of topics, [over here](https://i.imgur.com/upYnLhY.png). The shortlinks are just `https://tild.es/topic ID` where `topic ID` are the 3 characters of a topic's URL. So `https://tildes.net/~tildes.official/9au` becomes `https://tild.es/9au`.
More options like linking to specific comments, users and other stuff is planned, don't worry!

View File

@ -0,0 +1,39 @@
<h2 id="july-2018">
July 2018
<span id="authors">by @Bauke</span>
</h2>
### Open Source
Arguably the most notable highlight of July was the open-sourcing of the code. You can now find the source code in [the GitLab repository](https://gitlab.com/tildes/tildes) and contribute, keep up with the development or create your own version of Tildes entirely. The project is licensed under [the GNU AGPLv3 license](https://gitlab.com/tildes/tildes/blob/master/LICENSE.md), you can find a good summary on [GitHub's choosealicense.com](https://choosealicense.com/licenses/agpl-3.0/).
If you're interested in contributing or just messing around, 2 new documentation pages were added for developing, namely: [Development Setup](https://docs.tildes.net/development-setup) and [Development](https://docs.tildes.net/development). These will get you up and running in no time.
Read more about it [here](https://tild.es/3i4) and [here](https://blog.tildes.net/open-source).
### Links In New Tabs
On July 21st, the first feature contribution was merged, developed by [Ivan Fonseca](https://gitlab.com/ivanfon) better known as @what on Tildes. Allowing you to open external and/or internal links in new tabs. You can find the toggles inside [your settings](https://tildes.net/settings), above the "Change your password" section.
Read more about it [here](https://tild.es/3oi).
### New Groups
July 23rd saw the introduction of 4 new groups, listed below, following [the group requests topic](https://tild.es/342) in the beginning of July.
* ~anime
* ~enviro
* ~humanities
* ~life
Summaries of each group are available in their respective sidebars and in the announcement topic.
Read more about it [here](https://tild.es/3qv).
### Not So Daily Discussions
On the 26th Deimos posted a topic in ~tildes.official titled [Not-so-daily Tildes discussion](https://tild.es/3x9). Outlining what the future of the Daily Discussions will hold. To sum it up, daily discussions will now appear on a more "on-demand" basis. Initially the daily discussions were to boost the site's activity however it's not really needed anymore. Along with the current backlog of plans and previous discussions that need attention first.
Also noted is that there will be a new post each Monday including the general plans for the week, as well as a general feedback/questions/suggestions topic every couple of weeks.
I highly recommend reading [the original topic](https://tild.es/3x9), if you haven't already.

View File

@ -0,0 +1,46 @@
<h2 id="june-2018">
June 2018
<span id="authors">by @Bauke</span>
</h2>
### New Groups
The first change to Tildes in June was the addition of 5 new groups:
* ~books
* ~food
* ~hobbies
* ~lgbt
* ~health
And also the creation of the first sub-group: ~tildes.official, which now features any official announcements as well as [the Daily Discussions](https://tildes.net/?tag=daily_discussion). This sub-group also sparked the ability for only admins to create topics. You can read more about it in [the announcement topic](https://tild.es/1e1).
### Default Sorting
The default sort order has been changed from "Activity, All Time" to "Activity, 24 Hours". You can also set your own default sort order which will apply to your general topic listing of groups you're subscribed to and the topic listing of any group you visit.
To change your default sort order you can select any order you'd like to save and click on the "Set as default" button.
Read more about it [here](https://tild.es/1sn) and [here](https://tild.es/1vx).
### Mark Notifications As Read
A new setting was added to [the settings page](https://tildes.net/settings) that will allow you to automatically mark all your notifications as read once you visit the notifications pages. It's off by default, so check it out if you haven't already. In case you accidentally mark any of your notifications as read, you can always find all of them [here](https://tildes.net/notifications).
Read more about it [here](https://tild.es/1z4).
### Tags
A bunch of features involving tags have been including filtering to show or hide posts with some tags, [a discussion on standardization and guidelines](https://tild.es/2ab).
You can access your filter to hide by clicking the "Edit filtered tags" in the sidebar or by navigating to [your settings](https://tildes.net/settings/filters).
To filter out topics to only show with specific tags you can click on any tag from a topic or append `?tag=tag` to the URL. Like this: `https://tildes.net/~tech?tag=security`, it'll only show you topics tagged with `security` in the ~tech group.
Read more about it [here](https://tild.es/28n) and [here](https://tild.es/2a9).
### Topic Log
June 18th saw the introduction of the topic log. A log of changes made to a topic, located in the sidebar. Currently the only things logged are if a topic is (un)locked, if someone has changed the tags and/or [edited the title](https://tild.es/2p6).
Read more about it [here](https://tild.es/2f1).

View File

@ -0,0 +1,8 @@
<h2 id="november-2018">
November 2018
<span id="authors">by @Bauke</span>
</h2>
### A Slow Month
November has been a slow and uneventful month and [the only official topic from this month](https://tild.es/8oi) talks about why that is and how December is gonna be noticeably different, since the plan is to make Tildes publicly viewable! There's more details in the official topic so definitely read that if you haven't already.

View File

@ -0,0 +1,22 @@
<h2 id="october-2018">
October 2018
<span id="authors">by @Bauke</span>
</h2>
### Search Tags
On the 16th, search was expanded to also include tags in the results so you can find that one obscure topic a little faster. You can find [the official topic here](https://tild.es/7j9).
### Brave Donations
On the 25th, Tildes got the ability for users to donate via [Brave's BAT system](https://basicattentiontoken.org/), if you don't know what it is I recommend reading [their FAQ](https://basicattentiontoken.org/faq/#meaning) and the comments on [the announcement topic](https://tild.es/7vm).
### Six-Month Anniversary
On the 26th, Tildes became 6 months old. And [a new demographics survey, the "Year 0.5 Survey"](https://tild.es/7x5), popped up from user @Kat. So if you haven't already (and the survey is still open), fill it out! Remember that you can skip any questions you don't want to answer.
### Bookmarks
And finally, [on the spookiest day of the month](https://i.imgur.com/YZJt8WD.gif) a long-awaited feature, ["Bookmarking" was added](https://tild.es/83l)! Another open-source contribution, once again by @what. A merge request that's been in the works for [2 months](https://gitlab.com/tildes/tildes/merge_requests/27)!
You can bookmark any topics and comments you like and they will show up on a new user page [called Bookmarks](https://tildes.net/bookmarks), which you can find in the sidebar on your profile, just above your invites.

View File

@ -0,0 +1,22 @@
<h2 id="september-2018">
September 2018
<span id="authors">by @Bauke</span>
</h2>
### Comment Tag... Labels!
Early on in September, [comment tags](https://tild.es/63s) were re-enabled. Primarily for experimentation and to see how people would use them. During the coming 10 days, comment tags received a few changes here and there to see what works and what doesn't. Eventually leading up to [the introduction of the "relevance" comment sort order](https://tild.es/6hn).
The relevance sort order was made the default and it's basically the "most votes" order however the comment tags also affect a comment's placement. So if people tagged your comment as "noise" it would become automatically collapsed, off-topic will be lowered compared to on-topic comments, etc.
Then, after another number of days, [comment tags were renamed to labels](https://tild.es/6ue). As well as a number of other changes and even a new, positive comment label entirely. Definitely a topic to read if you haven't already.
### Scraping Data
On the 11th, [scraping data was added](https://tild.es/696) using [Embedly's "Extract" API](https://embed.ly/extract). A plethora of things can be done with it but for now there's only been a handful of additions. Namely, embedding a tweet's content in a topic and showing the published date of an article if the article is 3 days older than the topic.
### Syntax Highlighting
On the 21st, @Soptik's syntax highlighting contribution was implemented and will be a great addition to the style of the site. Especially over in ~comp.
You can find [the merge request here](https://gitlab.com/tildes/tildes/merge_requests/31) and [the announcement topic here](https://tild.es/6o6).

View File

@ -0,0 +1,34 @@
<h2 id="april-2019">
April 2019
<span id="authors">by @Bauke</span>
</h2>
### Notification Behaviour
The [first change this month](https://tild.es/c4t) came on April 7th, making it so that when you interact with a notification (by voting on the comment, for example) it can be automatically marked as read. If you'd rather manually mark all your notifications yourself, this option can be turned off in [your settings](https://tildes.net/settings).
### More Metadata
On April 17th, topics that linked to Youtube videos or Tweets will now show the channel's name or the Twitter username where the domain usually goes. There have been some suggestions for other sites [in the announcement topic](https://tild.es/cfu) that should be included, if you know of any more feel free to leave a comment there.
### Tagging Autocomplete
On the 25th, a [long-awaited feature](https://gitlab.com/tildes/tildes/issues/142) was added and developed by @smores: autocomplete for tags! Now when you're creating a new topic or editing the tags of an existing one, the 100 most commonly used tags (for the group the topic is in) will show up when typing any new tags. You can read more about it [here](https://tild.es/cpl).
### Comment Threading
A little over halfway through April, [the way comments were threaded](https://tild.es/cjg) got changed as an experiment to reduce the amount of indenting any given chain of comments would have, which becomes a problem (especially on mobile) very quickly if you indent once every reply. Direct replies after a certain amount of indentations would receive a "Reply to above comment" just below their header and not be indented. This turned out to be slightly confusing for a lot of people since child comments and siblings would appear on the same indent, so on the 29th [this was changed again](https://tild.es/cv4) to be more responsive and work better. A nice detailed explanation is available in [the announcement topic](https://tild.es/cv4).
### Markdown Preview
A long awaited feature also got merged on the 30th, markdown preview! This came in as [a contribution](https://tild.es/cwh) by @wirelyre. From now on there will be 2 buttons available just above the input box, allowing you to switch between "Edit" and "Preview" mode.
### Small Changes
There were also a number of small changes that I think are worth noting:
* Tags that are the same as the group name will automatically be removed from topics. ([more](https://gitlab.com/tildes/tildes/commit/cefd5a0c5167370b32013e84b7272f4f23ab3b56))
* The topics in the topic listing were made more space-efficient. ([more](https://gitlab.com/tildes/tildes/commit/fb5323e51fc7bb86fbc8df0d7dd856d4e24208ca), [more](https://gitlab.com/tildes/tildes/commit/8572276fc7d8c7eb6f14891451ea636a87c146c1))
* Your filtered tag settings are now separated by newline instead of by comma. ([more](https://gitlab.com/tildes/tildes/commit/61558feea1673606db72229694c13eee482f2eac))
* Exemplary count and badge are now hidden again. ([more](https://gitlab.com/tildes/tildes/commit/6dbaf6a9721ea88ff6ee4c5fdf33a6fb0d87930e))
* You can now sort the comments and topics on your profile. ([more](https://tild.es/cv4))

View File

@ -0,0 +1,25 @@
<h2 id="august-2019">
August 2019
<span id="authors">by @Bauke</span>
</h2>
### Various Updates
Over the course of August there have been various updates/improvements made, some of which you can read more about [here](https://tild.es/gla) or briefly summarized in this list:
* [The Docs](https://docs.tildes.net/) have had a major rewrite/reorganization/rework using [the Wiki system](https://tildes.net/~tildes.official/wiki). (Contributed by @Algernon_Asimov, thank you!)
* You can now write small text using the `<small>` HTML tag.
* You can now also write expandable boxes/"spoilerboxes" using the `<details>` and `<summary>` HTML tags. [Check out the Text Formatting docs](https://tildes.net/~tildes.official/wiki/instructions/text_formatting#expandable_sections) for an example.
* A new theme "Zenburn" has been added, you can change themes in [your settings](https://tildes.net/settings).
* A small warning will now display above the comment box if the topic or comment you're replying to is over a week old. (Open-source contribution by @deing, thank you!)
* If you have the [mark new comments](https://tildes.net/settings/comment_visits) feature enabled, visiting topics you've previously read will now automatically jump you to the comments section.
### GitLab Reorganization
A big reorganization of the GitLab issue tracker has been underway during the end of August, although it is not yet finished here's a small list of some things that have changed so far:
* Labels have been completely redone, [view them all here](https://gitlab.com/tildes/tildes/-/labels).
* @Deimos has gone through the entire tracker and either accepted or denied all the issues, if you've ever wanted to contribute to the code but couldn't find anything you could work on that was confirmed to be a wanted feature now is the time to check again. You can filter for [Stage::Accepted](https://gitlab.com/tildes/tildes/issues?scope=all&utf8=%E2%9C%93&state=opened&label_name[]=Stage%3A%3AAccepted) to find them easily.
* [Several "Epics" have been made](https://gitlab.com/groups/tildes/-/epics) to track individual issues for broad features of Tildes such as Search and the Wiki system, more Epics will be made in the coming weeks/months.
And a bunch more things I will cover in next month's post once the reorganization is finished.

View File

@ -0,0 +1,13 @@
<h2 id="december-2019">
December 2019
<span id="authors">by @Bauke</span>
</h2>
### The Slow Month
As the year comes to a close and everybody slows down a little during the holiday season, so does Tildes. Most of the work done this month has been [upgrading versions](https://gitlab.com/tildes/tildes/compare/5c1cf3975d109fa742608c169279675fc6850e3f...fb7b0cb47319ed174a5a9a3b347b7b06530a49d1), [adding](https://gitlab.com/tildes/tildes/commit/39449dd776239c50e93b247ddb7d5e762216c369) [more](https://gitlab.com/tildes/tildes/commit/59699427fded6a9dd1ee056304be6de64b6eb42d) [metadata](https://gitlab.com/tildes/tildes/commit/7e5aa833aecddceee798771c93b7d5c927b08616) and some small [UI changes](https://gitlab.com/tildes/tildes/compare/005684a8db546180ed776c61c52422658aac8435...ef9a3622973a19e41be022a9da1d1be0be814f58).
However, that's not all as there were also 2 open-source contributions:
* User mentions (like @Tildes) can now be escaped using `\` so you can do `\@Tildes` and it won't create a link + notification for that. And instead just leave it as regular text. (Although it appears to [not be working](https://gitlab.com/tildes/tildes/issues/622) at the moment). This change was done by [Timo on GitLab](https://gitlab.com/tildes/tildes/merge_requests/85).
* `/r/...` will now get linkified and point to the specified subreddit, so `/r/tildes` will become [/r/tildes](https://reddit.com/r/tildes). This feature was implemented by [yabai on GitLab](https://gitlab.com/tildes/tildes/merge_requests/87).

View File

@ -0,0 +1,16 @@
<h2 id="february-2019">
February 2019
<span id="authors">by @Bauke</span>
</h2>
### Experiments
On the 1st of February, @Deimos [posted a topic](https://tild.es/a2e) announcing some changes and tweaks to experiment with and what people would think of them. Like removing the username of link-topics on the topic listing and putting the domain where the username went instead, amongst others. There was a lot of discussion on that topic, and after a couple weeks @hungariantoast also posted [a topic talking](https://tild.es/alj) about the changes which also got some great discussion.
### Publicly-Viewable
On the 16th of February, Tildes hit a big milestone and [became publicly-viewable](https://tild.es/ahb). Allowing anyone without an account to look at Tildes and browse around, participating in the topics and comments still requires an account (which still requires an invite) but at least people can peek inside now! If you know of anyone that was on the fence about Tildes because they couldn't see what it was like, maybe talk to them again. ;)
### Dracula
About half a year ago, [I opened an issue](https://gitlab.com/tildes/tildes/issues/193) regarding the themes, as (at the time) the way themes were done was a little all over the place so I wanted to clean it up a little, making it so new ones could be added in an easier fashion. Fast forward [a few months](https://gitlab.com/tildes/tildes/merge_requests/39/diffs) and the new system is merged in. And now, in [the same announcement as becoming publicly-viewable](https://tild.es/ahb), Tildes also received its first new theme using the new theme system. [Dracula](https://github.com/dracula/dracula-theme#color-palette)! :D A colorscheme I'm particularly fond of (if you haven't already noticed this site also uses Dracula). So I definitely recommend you check it out on Tildes, you can change your theme in [your settings at the top](https://tildes.net/settings) or if you're not logged in, at the footer of [the main site](https://tildes.net/#site-footer).

View File

@ -0,0 +1,14 @@
<h2 id="january-2019">
January 2019
<span id="authors">by @Bauke</span>
</h2>
### Profile Pagination
On the 25th of January it was announced that [full profile pagination was added to everyone's profiles](https://tild.es/9wf) following that next Monday, the 28th. Since we're now past that, you (and anyone logged in viewing your profile) can scroll through all your posted topics and comments. No longer will you have to switch between the Topics or Comments only views!
### Issue Log Changes
Last week I've also spent some time on adding more statistics to the issue log, such as: number of commits made, how many unique contributors authored commits and how many lines of code were changed/added/deleted. I regenerated and added these new statistics also to the previous months where applicable. There are still some numbers I want to add but if you think of any feel free to [leave a comment in this issue](https://gitlab.com/Bauke/tildes-issue-log/issues/1) or on the Tildes or Reddit topics.
As announced in [the latest official topic Tildes will become publicly-viewable very soon. Once that happens I also want to add [the statistics I can now gather by scraping](https://tild.es/9qf) in a new section so there's some numbers on Tildes itself, and not just the activity on GitLab. I think this will be a very good addition for people that haven't been on Tildes long to get some insight on what the activity is like. [I've made an issue for these statistics here](https://gitlab.com/Bauke/tildes-issue-log/issues/5), if you have any suggestions let me know.

View File

@ -0,0 +1,23 @@
<h2 id="july-2019">
July 2019
<span id="authors">by @Bauke</span>
</h2>
### Group Proposals 2
A [new round of proposals for groups](https://tild.es/g2z) was posted on the 30th. So if you have any proposals submit them (or reply to the ones already submitted) and maybe it will get added, when the new groups get added from this round I'll add them here:
* ~arts
* ~design
* ~games.game_design
* ~games.tabletop
* ~finance
* ~hobbies.automotive
* ~science.formal
* ~science.social
* ~science.natural
* ~space
### Recent Changes
@Deimos posted a topic on the 1st of August listing the recent changes made in July, so instead of me making my own list I'll just [link that here](https://tild.es/g65). :P

View File

@ -0,0 +1,8 @@
<h2 id="june-2019">
June 2019
<span id="authors">by @Bauke</span>
</h2>
### A Slow Month
I'm sorry to say there are no highlights this month, mainly because I didn't pay much attention to one of 2 major changes this month, [the hidden votes](https://tild.es/e7i) [experiment](https://tild.es/eh3) so I didn't really know how to talk about it. While the other major change, [the sorting mechanics](https://tild.es/egq), I couldn't think of anything coherent enough that would add onto what's already available in the announcement topic itself (is this writer's block? is it the >30°C weather? who knows). I contemplated just not posting it and combining it into next month's post but decided because the statistics and tables might still be useful it might as well go up now. I can always go back and edit a highlight in if I think of it (or if someone else can write about anything relevant I'd happily add that too). Anyways, here's this month's Issue Log! <small>again, sorry it's not very good this month</small>

View File

@ -0,0 +1,20 @@
<h2 id="march-2019">
March 2019
<span id="authors">by @Bauke</span>
</h2>
### Small Updates
Over the course of March various smaller changes were made, [most of which you can see here](https://tild.es/b6n).
* A new theme was added by @what, the popular theme from [the Atom text editor](https://atom.io/): "[Atom One Dark](https://gitlab.com/tildes/tildes/merge_requests/60)".
* You can now "quick quote" when replying to someone. Select the text you want to quote, click on Reply and the comment box will automatically have the selected area copied over. A feature [that was suggested](https://tild.es/ayt) by @asoftbird.
* Usernames will show again instead of domains for link topics in a few specific groups: ~creative and ~music.
* Your chosen theme will now be remembered when going to [the Docs](https://docs.tildes.net/) and [the Blog](https://blog.tildes.net/).
* On desktop, notifications will no longer show below your username but to the left instead. This was done to prevent the body of the site from being pushed down when you receive a new notification.
* A link was added to all the settings pages to go back to the main settings page, [as requested by](https://gitlab.com/tildes/tildes/issues/387) GitLab user [asmLANG](https://gitlab.com/Amndeep7).
* Using `mailto:` with links is now possible. Also when typing out an email it will no longer leave a blank link.
### User Bios
On the 21st of March, @what implemented yet another [long-awaited feature](https://gitlab.com/tildes/tildes/issues/206): User Bios! You can now write a little about yourself and have it display on your profile. To change your bio, head to [the settings page](https://tildes.net/settings/bio) and get writing!

View File

@ -0,0 +1,18 @@
<h2 id="may-2019">
May 2019
<span id="authors">by @Bauke</span>
</h2>
### Group Wikis
On the 24th, [group wikis were added](https://tild.es/drm). These wikis can be accessed through any group's sidebar, for example in ~tildes you can find [the Introduction wiki page](https://tildes.net/~tildes/wiki/introduction). Anyone can view the wikis but if you want to be able to edit them you'll have to [send @Deimos a message](https://tildes.net/user/Deimos/new_message) or comment on [the announcement topic](https://tildes.net/drm) and ask for the permission.
### Various Fixes
Across May there were also various issues that got fixed that are noteworthy:
* When bookmarking you can now easily unbookmark again ([#377](https://gitlab.com/tildes/tildes/issues/377))
* Headers on userpages and notifications will now display properly ([#349](https://gitlab.com/tildes/tildes/issues/349))
* Certain errors will now no longer dump HTML but display the proper error message ([#82](https://gitlab.com/tildes/tildes/issues/82), [#85](https://gitlab.com/tildes/tildes/issues/85), [#130](https://gitlab.com/tildes/tildes/issues/130) [#164](https://gitlab.com/tildes/tildes/issues/164), [#476](https://gitlab.com/tildes/tildes/issues/476))
* You can now verify your Tildes account on Mastodon ([#415](https://gitlab.com/tildes/tildes/issues/415))
* A problem with launching a local development environment was identified and fixed ([#485](https://gitlab.com/tildes/tildes/issues/485))

View File

@ -0,0 +1,20 @@
<h2 id="november-2019">
November 2019
<span id="authors">by @Bauke</span>
</h2>
### Scheduled Topics
On the 7th, a new section [was added](https://gitlab.com/tildes/tildes/commit/7c2d95709a2b5c775cf76940ac3fbf95c7394974) to the sidebar of groups to list all of the automatically posted recurring topics that happen in that group.
### Financials
Another feature that was added on the 7th was the [Financials page](https://tildes.net/financials). Detailing the income and expenses Tildes has, as well as a monthly donation goal and the progress towards it.
If you're interested in donating, please check out [the Donate page](https://docs.tildes.net/donate). Any and all donations help, thank you!
### Voting Records
On the 21st, [a change was made](https://tild.es/jhm) to the way Tildes stores the information on the topics and comments you've voted on. Instead of storing that data forever, it will now be deleted after that particular topic or comment you've voted on is 30 days old. Alongside this, voting on these 30-day-old posts will also be locked and afterwards only show how many votes it received during those 30 days.
A few days later on the 27th, [a new page](https://tildes.net/votes) was [also added](https://tild.es/jn2) that lists all the things you've voted on. Because voting records are now being deleted, this list will only show what you've voted on the past 30 days. For keeping track of any topics and comments for an indefinite amount of time, you can use [the bookmarks feature](https://tildes.net/bookmarks).

View File

@ -0,0 +1,37 @@
<h2 id="october-2019">
October 2019
<span id="authors">by @Bauke</span>
</h2>
Highlights
----------
### GitHub Sponsors
Back in May of 2019, GitHub announced ["GitHub Sponsors: a new way to contribute to open source"](https://github.blog/2019-05-23-announcing-github-sponsors-a-new-way-to-contribute-to-open-source/), allowing people to donate to open-source projects directly via GitHub. And recently on the 28th, @Deimos [was accepted into the program](https://tild.es/it2). The main interesting parts about it for Tildes though are: for the first 12 months of the program 100% of any donations will go to the developer (no transaction fees or GitHub taking a cut) _and_, GitHub will match up to $5000 during the project's first year in the program (note: there have been some people that didn't get their donations matched, [see this comment chain for more info](https://tildes.net/~tildes.official/it2/ive_been_accepted_into_github_sponsors_if_you_have_a_patreon_pledge_or_other_recurring_donation_to#comment-4408)). So knowing all that, if you can, please consider donating (preferably through GitHub Sponsors)! Thank you!
### Layout Changes
At the start of October, some changes were made to the layout of topics and tags to rearrange them and display them in a better way:
* You can now hide tags in the topic listing, available as a [setting in your profile](https://tildes.net/settings).
* Certain "important tags" will always be shown regardless of the setting, such as "nsfw" and "spoiler", as well as any group-specific ones. The ability to add those was also added, although there's no interface for it yet.
* Tags are now displayed below the topic title when in a topic's comments page, these used to be in the sidebar.
* When posting a new topic, the "add tags" section is now collapsed by default. If you express interest in adding tags when posting or editing an existing topic's tags this section will be visible by default.
* Content metadata such as word count, published date, video length, etc. is now displayed next to the group name.
* Favicons for link topics were moved to the "topic source" section where you usually see the link's domain, Youtube channel, or similar.
* A "content type" [was also added](https://tild.es/ibp) to the topic listing used to identify topics like articles, "ask" topics, videos, etc.
### Scheduled Recurring Topics
On the 10th, [a new feature was added](https://tild.es/ibp) for automatic posting of recurring topics. Previously several members of Tildes would post these recurring topics themselves, but now there's support for these to be posted automatically through [a "system" account](https://tildes.net/user/Tildes).
### Upgraded Search
On October 14th, [search was added to user's profiles](https://tild.es/iha). For now you can only search through your own topics and comments.
On October 21st, PostgreSQL (the database system Tildes uses) was upgraded to version 12 that introduced a whole variety of search features, check out [the official announcement topic](https://tild.es/imp) to get some examples of the new features. It includes stuff like quoted searches, excluding terms, and more.
### Open Source Contributions
* [Jason S.](https://gitlab.com/master5o1) added [the ability](https://gitlab.com/tildes/tildes/commit/2546f831dcc7243ed9d5ebb8e20ad3035e83b63f) to click on your filtered tags in the sidebar.
* @Deing added [theme previews](https://gitlab.com/tildes/tildes/commit/810a54076e054829281f7cc468c7cf48a0c7518e) to easily see the differences and test out themes, [available here](https://tildes.net/settings/theme_previews).

View File

@ -0,0 +1,45 @@
<h2 id="september-2019">
September 2019
<span id="authors">by @Bauke</span>
</h2>
### New Groups
Early in September on the 5th, @Deimos added the new groups that [were proposed in July](https://tild.es/g2z) and further discussed [at the end of August](https://tild.es/h23). For a list of all the groups you can go to [this page](https://tildes.net/groups), the new groups are:
* ~arts
* ~design
* ~games.game_design
* ~games.tabletop
* ~finance
* ~hobbies.automotive
* ~science.formal
* ~science.social
* ~science.natural
* ~space
### Stripe Updates
Over the course of several days, [the Stripe donation page](https://tildes.net/donate_stripe) got reworked to use the new version of [Stripe Checkout](https://stripe.com/payments/checkout). This new version will redirect you to a page on the Stripe website instead of using a pop-up modal where you enter your details and donate. This also came with the opportunity to add recurring/subscription-based donations so that's available too now. The available options are "One time", "Monthly" or "Yearly".
I'd also like to take this opportunity to remind you that Tildes has [no advertising](https://blog.tildes.net/announcing-tildes#no-advertising-user-supported), [no investors](https://blog.tildes.net/announcing-tildes#non-profit-no-investors), and is supported by **your donations**. So, if you can, please donate to Tildes [through any of the available methods](https://docs.tildes.net/donate). Thank you!
### Various Updates
As with every other month, there were also several changes made:
* Errors will now be displayed when using buttons. These were previously only logged in the web console.
* Comments that are 5 or more levels deep will no longer bump the Activity sort.
* Topic titles can now be edited by the topic author for 5 minutes after posting.
* Sub-groups you are not subscribed to will now no longer show up in your home feed if you're subscribed to the parent (the sub-groups will still show up in their parent's feeds).
* A new theme "Gruvbox" (with a light and dark variation) was added, head to your [account settings](https://tildes.net/settings) to check them out.
### GitLab Reorganization
As alluded to [in last month's post](https://til.bauke.xyz/2019/august.html#gitlab-reorganization) a big reorganization of the issue tracker and several other components was done to make it easier to use the issue tracker, so let's go over them:
* All the label names and descriptions were gone over and updated to be clearer in what they mean and what purpose they serve. For example, previously there was a "suggestion" label that indicated a feature request that wasn't accepted yet and a "feature request" label that indicated it was accepted. These 2 have now been replaced by [the Stage labels](https://gitlab.com/tildes/tildes/-/labels?search=Stage) to indicate if it's an issue that should be fixed (or not) and [the Feature Request label](https://gitlab.com/tildes/tildes/-/labels?search=Feature%20Request). This will allow us to designate if issues like bugs and other non-feature requests have been confirmed or not.
* [A "Bug Report" template](https://gitlab.com/tildes/tildes/issues/new?issuable_template=Bug%20Report) was created to guide people through the steps of creating an issue for a bug, including what information they should provide to make it easier for the developers to fix.
* [The Contributing document](https://gitlab.com/tildes/tildes/blob/master/CONTRIBUTING.md) that outlines how to go about contributing to the Tildes code was also updated, including more examples of how to use the new labels.
* We'll now start using [GitLab's Weight functionality](https://docs.gitlab.com/ee/workflow/issue_weight.html) to indicate how complex issues are, ranging from 1 through 5. For a brief rundown on what each weight means, @deing wrote a little something that's also in [the Contributing document](https://gitlab.com/tildes/tildes/blob/master/CONTRIBUTING.md#choosing-what-to-work-on).
* An [issue board](https://gitlab.com/tildes/tildes/-/boards/1276089) was also created to accommodate the new labels, there you can get a quick overview on the issues and filter them easily without doing it directly in the tracker.

View File

@ -0,0 +1,42 @@
<h2 id="february-2020">
February 2020
<span id="authors">by @Bauke</span>
</h2>
### Open-Source Contributions
Early on in February a few contributions came in that have been implemented throughout the month:
* You can now view your 2FA backup codes, [contributed by](https://gitlab.com/tildes/tildes/-/merge_requests/95) @Bauke. Previously you could only view these once after you originally enabled 2FA.
* Private message fields can now be auto-filled with some query parameters, [contributed by](https://gitlab.com/tildes/tildes/-/merge_requests/92) @deing.
* Topics that link to IP addresses are now displayed correctly, [contributed by](https://gitlab.com/tildes/tildes/-/merge_requests/96) @blitz.
* The "Post a new topic" button has been added to group searches, [contributed by](https://gitlab.com/tildes/tildes/-/merge_requests/98) @Bauke.
* Group and user links were added to the "Open links in a new tab" feature, [contributed by](https://gitlab.com/tildes/tildes/-/merge_requests/97) @Bauke.
* The "Set as account default" button for themes will now show on page load, [contributed by](https://gitlab.com/tildes/tildes/-/merge_requests/101) @Bauke.
* You can now view the Markdown of everyone else's topics and comments by using the `More...` dropdown and then clicking `View Markdown`, [contributed by](https://gitlab.com/tildes/tildes/-/merge_requests/93) @Bauke.
* You can now change the default comment sort order in [your settings](https://tildes.net/settings), [contributed by](https://gitlab.com/tildes/tildes/-/merge_requests/99) @Bauke.
If you're interested in contributing to Tildes, check out the [Contributing document](https://gitlab.com/tildes/tildes/-/blob/master/CONTRIBUTING.md). It contains all the information you'll need to get up and running, thank you!
### Issue Log Changes
If you're reading this on the website, you may have noticed by now that the Issue Log changed a little bit. Last month I [mentioned in a comment](https://tildes.net/~tildes/lh0/tildes_issue_log_january_2020#comment-4jck) that I had been wanting to make some changes for a while, so after a little dedication to getting it done, the new Issue Log is now up and running.
Below are some of the specific changes I've made that should (hopefully) make the Issue Log better for all you lovely people that read it:
* [The home page](../) has been slimmed down. It now only includes the About section and the various feeds you can read the Issue Log from.
* The full list of posts [was moved to here](../posts.html) and redesigned to be a bit more practical. The most recent post is boldened in that list.
* There is now [a mailing list](https://lists.sr.ht/~bauke/tildes-issue-log) you can subscribe to to receive the Issue Log directly in your email.
* A button to toggle between a light and dark theme was added, it is located in the top right of the header. <small>I'm still playing around with some colors to meet the WCAG contrast ratios, so they'll change a little in the coming month(s), but I've decided they're fine for now.</small>
* The [Statistics section](#statistics) was drastically shortened. Now it only includes concrete numbers from GitLab, no more averages and lists of things. If you have any suggestions for more GitLab-specific statistics you'd like to see like the ones available, do let me know and I'll see what I can do.
* The issue table was removed, I figured if you're really interested in keeping up with *all* the things that happen you should just follow [the GitLab project](https://gitlab.com/tildes/tildes) through any of the various ways GitLab provides. <small>I might bring it back in a condensed form in some way though, if I can figure something out.</small>
* The URL schema was changed from `/posts/:month-:year.html` to `/:year/:month.html`. [Old links](../posts/january-2020.html) will still work but will just redirect you to the new one.
* The "Details" section of each highlight has been removed, this included when and by who the highlight was written. The author(s) of the post can now be found in the main header of the post.
Besides making the end result better, I've also made some changes that will make the developing and writing side easier:
* Posts are now written in Markdown.
* The HTML is now generated using various templates, previously a lot of copy-pasting was involved.
* The [Statistics section](#statistics) now automatically gets added to the post. Previously I had to copy it from a different file and paste it into the post manually.
* The [Official Topics section](#official-topics) is now automatically generated.
* User mentions and group links (ie. @Tildes and ~tildes) are now automatically linkified (and also have different colors).

View File

@ -0,0 +1,16 @@
<h2 id="january-2020">
January 2020
<span id="authors">by @Bauke</span>
</h2>
### Ignoring Topics
On the 13th, a (yet another) feature contribution by @what was implemented. This time allowing people to ignore topics. Ignoring a topic will hide it from the topic listing and (as of [the 23rd](https://gitlab.com/tildes/tildes/commit/9eec00cc6a16eb5ccf31118c935c3ddf1b946c3f)) also stop notifications from being sent to you. So if someone mentions you or replies to one of your comments in that ignored topic you won't receive the notifications for that.
In [the announcement topic](https://tild.es/kvo) there were also some further questions @Deimos had about the functionality, so definitely check that out if you have any ideas or thoughts on how it currently works.
### Mark New Comments
On the 22nd, @Deimos posted a topic asking the community if they had concerns with enabling the "mark new comments" for everyone. This is a very useful feature that when you return to a topic and there's new comments since your latest visit, it will mark those with an orange/red-ish (depending on your theme of choice) stripe on the left of the comment. Topics in the topic listing will also display a "(x new)" in the same orange/red-ish color next to the total amount of comments, so it's easy to follow up on any activity. This was previously an opt-in feature due to privacy concerns.
After a few days and having feedback from the community, @Deimos went ahead and [enabled it for everyone](https://tild.es/lcd). If you'd like for old comments to be automatically collapsed so only the new ones are readily visible, there's an option for that in [the "Site behavior settings"](https://tildes.net/settings).

View File

@ -0,0 +1,13 @@
#attributions {
ul {
margin-bottom: 2rem;
&:last-of-type {
margin-bottom: 0;
}
}
#hasklig {
font-family: Hasklig, monospace;
}
}

View File

@ -0,0 +1,321 @@
html {
font-size: 62.5%;
}
body {
font-size: 2rem;
}
h1 {
font-size: 4rem;
}
h2 {
font-size: 3.5rem;
}
h3 {
font-size: 3rem;
}
h4 {
font-size: 2.5rem;
}
h5 {
font-size: 2rem;
}
// Inter
@font-face {
font-family: Inter;
font-style: normal;
font-weight: 100;
font-display: swap;
src:
url('../fonts/Inter/Inter-Thin-BETA.woff2') format('woff2'),
url('../fonts/Inter/Inter-Thin-BETA.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: italic;
font-weight: 100;
font-display: swap;
src:
url('../fonts/Inter/Inter-ThinItalic-BETA.woff2') format('woff2'),
url('../fonts/Inter/Inter-ThinItalic-BETA.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: normal;
font-weight: 200;
font-display: swap;
src:
url('../fonts/Inter/Inter-ExtraLight-BETA.woff2') format('woff2'),
url('../fonts/Inter/Inter-ExtraLight-BETA.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: italic;
font-weight: 200;
font-display: swap;
src:
url('../fonts/Inter/Inter-ExtraLightItalic-BETA.woff2') format('woff2'),
url('../fonts/Inter/Inter-ExtraLightItalic-BETA.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: normal;
font-weight: 300;
font-display: swap;
src:
url('../fonts/Inter/Inter-Light-BETA.woff2') format('woff2'),
url('../fonts/Inter/Inter-Light-BETA.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: italic;
font-weight: 300;
font-display: swap;
src:
url('../fonts/Inter/Inter-LightItalic-BETA.woff2') format('woff2'),
url('../fonts/Inter/Inter-LightItalic-BETA.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: normal;
font-weight: normal;
font-display: swap;
src:
url('../fonts/Inter/Inter-Regular.woff2') format('woff2'),
url('../fonts/Inter/Inter-Regular.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: italic;
font-weight: normal;
font-display: swap;
src:
url('../fonts/Inter/Inter-Italic.woff2') format('woff2'),
url('../fonts/Inter/Inter-Italic.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: normal;
font-weight: 500;
font-display: swap;
src:
url('../fonts/Inter/Inter-Medium.woff2') format('woff2'),
url('../fonts/Inter/Inter-Medium.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: italic;
font-weight: 500;
font-display: swap;
src:
url('../fonts/Inter/Inter-MediumItalic.woff2') format('woff2'),
url('../fonts/Inter/Inter-MediumItalic.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: normal;
font-weight: 600;
font-display: swap;
src:
url('../fonts/Inter/Inter-SemiBold.woff2') format('woff2'),
url('../fonts/Inter/Inter-SemiBold.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: italic;
font-weight: 600;
font-display: swap;
src:
url('../fonts/Inter/Inter-SemiBoldItalic.woff2') format('woff2'),
url('../fonts/Inter/Inter-SemiBoldItalic.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: normal;
font-weight: bold;
font-display: swap;
src:
url('../fonts/Inter/Inter-Bold.woff2') format('woff2'),
url('../fonts/Inter/Inter-Bold.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: italic;
font-weight: bold;
font-display: swap;
src:
url('../fonts/Inter/Inter-BoldItalic.woff2') format('woff2'),
url('../fonts/Inter/Inter-BoldItalic.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: normal;
font-weight: 800;
font-display: swap;
src:
url('../fonts/Inter/Inter-ExtraBold.woff2') format('woff2'),
url('../fonts/Inter/Inter-ExtraBold.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: italic;
font-weight: 800;
font-display: swap;
src:
url('../fonts/Inter/Inter-ExtraBoldItalic.woff2') format('woff2'),
url('../fonts/Inter/Inter-ExtraBoldItalic.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: normal;
font-weight: 900;
font-display: swap;
src:
url('../fonts/Inter/Inter-Black.woff2') format('woff2'),
url('../fonts/Inter/Inter-Black.woff') format('woff');
}
@font-face {
font-family: Inter;
font-style: italic;
font-weight: 900;
font-display: swap;
src:
url('../fonts/Inter/Inter-BlackItalic.woff2') format('woff2'),
url('../fonts/Inter/Inter-BlackItalic.woff') format('woff');
}
// Hasklig
@font-face {
font-family: Hasklig;
font-style: normal;
font-weight: 200;
font-display: swap;
src: url('../fonts/Hasklig/Hasklig-ExtraLight.otf') format('opentype');
}
@font-face {
font-family: Hasklig;
font-style: italic;
font-weight: 200;
font-display: swap;
src: url('../fonts/Hasklig/Hasklig-ExtraLightIt.otf') format('opentype');
}
@font-face {
font-family: Hasklig;
font-style: normal;
font-weight: 300;
font-display: swap;
src: url('../fonts/Hasklig/Hasklig-Light.otf') format('opentype');
}
@font-face {
font-family: Hasklig;
font-style: italic;
font-weight: 300;
font-display: swap;
src: url('../fonts/Hasklig/Hasklig-LightIt.otf') format('opentype');
}
@font-face {
font-family: Hasklig;
font-style: normal;
font-weight: normal;
font-display: swap;
src: url('../fonts/Hasklig/Hasklig-Regular.otf') format('opentype');
}
@font-face {
font-family: Hasklig;
font-style: italic;
font-weight: normal;
font-display: swap;
src: url('../fonts/Hasklig/Hasklig-It.otf') format('opentype');
}
@font-face {
font-family: Hasklig;
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('../fonts/Hasklig/Hasklig-Medium.otf') format('opentype');
}
@font-face {
font-family: Hasklig;
font-style: italic;
font-weight: 500;
font-display: swap;
src: url('../fonts/Hasklig/Hasklig-MediumIt.otf') format('opentype');
}
@font-face {
font-family: Hasklig;
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('../fonts/Hasklig/Hasklig-SemiBold.otf') format('opentype');
}
@font-face {
font-family: Hasklig;
font-style: italic;
font-weight: 600;
font-display: swap;
src: url('../fonts/Hasklig/Hasklig-SemiBoldIt.otf') format('opentype');
}
@font-face {
font-family: Hasklig;
font-style: normal;
font-weight: bold;
font-display: swap;
src: url('../fonts/Hasklig/Hasklig-Bold.otf') format('opentype');
}
@font-face {
font-family: Hasklig;
font-style: italic;
font-weight: bold;
font-display: swap;
src: url('../fonts/Hasklig/Hasklig-BoldIt.otf') format('opentype');
}
@font-face {
font-family: Hasklig;
font-style: normal;
font-weight: 900;
font-display: swap;
src: url('../fonts/Hasklig/Hasklig-Black.otf') format('opentype');
}
@font-face {
font-family: Hasklig;
font-style: italic;
font-weight: 900;
font-display: swap;
src: url('../fonts/Hasklig/Hasklig-BlackIt.otf') format('opentype');
}

View File

@ -0,0 +1,23 @@
#footer {
display: flex;
font-weight: bold;
padding: 2rem;
@include constrain-width;
a {
border-bottom: 0.25rem solid;
padding-bottom: 0.25rem;
text-decoration: none;
}
}
#license {
margin-left: auto;
&::before {
content: '©';
display: inline-block;
padding-left: 0.25rem;
transform: scale(-1, 1);
}
}

View File

@ -0,0 +1,57 @@
#header {
align-items: center;
display: flex;
padding: 1rem 0;
@include constrain-width;
a {
align-items: center;
display: flex;
text-decoration: none;
}
h1.small {
display: none;
}
img {
height: 4rem;
margin-right: 1rem;
width: 4rem;
}
@media (max-width: $small-breakpoint) {
h1.big {
display: none;
}
h1.small {
display: initial;
}
img {
display: none;
}
}
button {
border: none;
margin-left: auto;
&:hover {
cursor: pointer;
}
&::before {
background: url('../images/moon-crescent-face-right.png') center center no-repeat;
content: '';
display: block;
height: 4rem;
width: 4rem;
}
&.light::before {
background: url('../images/sun.png') center center no-repeat;
}
}
}

View File

@ -0,0 +1,45 @@
body {
font-family: Inter, sans-serif;
}
li,
p {
line-height: 1.25;
margin-bottom: 0.5rem;
}
code,
pre {
font-family: Hasklig, monospace;
font-size: 90%;
padding: 0 0.5rem;
}
ul {
list-style-type: symbols(cyclic '~');
margin-left: 2rem;
> li {
&::marker {
font-weight: bolder;
}
&:last-child {
margin-bottom: 0;
}
}
}
table {
border-collapse: collapse;
width: 100%;
}
th {
padding: 1rem;
text-align: left;
}
td {
padding: 1rem;
}

View File

@ -0,0 +1 @@
//

View File

@ -0,0 +1,17 @@
#main {
padding: 2rem;
@include constrain-width;
> section {
padding-bottom: 2rem;
&:last-child {
padding-bottom: 0;
}
}
h2,
h3 {
margin-bottom: 1rem;
}
}

View File

@ -0,0 +1,13 @@
@mixin constrain-width {
margin: auto;
transition: 0.5s width;
width: $large-breakpoint;
@media (max-width: $large-breakpoint) {
width: $medium-breakpoint;
}
@media (max-width: $medium-breakpoint) {
width: 95%;
}
}

View File

@ -0,0 +1,37 @@
#post {
> :last-child {
margin-bottom: 0;
}
h2,
h3 {
padding-bottom: 1rem;
}
h2 {
align-items: flex-end;
display: flex;
}
ol,
ul {
margin-bottom: 2rem;
}
p {
margin-bottom: 2rem;
&.half-margin {
margin-bottom: 1rem;
}
}
#authors {
font-size: 50%;
margin-left: auto;
a {
text-decoration: none;
}
}
}

View File

@ -0,0 +1,49 @@
#posts {
display: grid;
grid-template-columns: repeat(3, 1fr);
h2 {
grid-column: 1 / 4;
}
@media (max-width: $medium-breakpoint) {
grid-template-columns: repeat(2, 1fr);
h2 {
grid-column: 1 / 3;
}
}
@media (max-width: $small-breakpoint) {
grid-template-columns: repeat(1, 1fr);
h2 {
grid-column: 1 / 1;
}
}
.year {
padding-bottom: 2rem;
h3 {
margin: 0;
}
}
.posts {
list-style: none;
}
.post {
margin-bottom: 0;
&.newest {
font-weight: bolder;
}
a {
display: block;
padding: 0.5rem;
}
}
}

View File

@ -0,0 +1,25 @@
%zero-margin {
margin: 0;
}
%zero-padding {
padding: 0;
}
@for $index from 1 through 5 {
h#{$index} {
@extend %zero-margin;
}
}
button {
@extend %zero-padding;
}
li,
ol,
p,
ul {
@extend %zero-margin;
@extend %zero-padding;
}

View File

@ -0,0 +1,137 @@
@use 'sass:list';
@use 'sass:map';
@mixin theme($colors) {
background-color: map.get($colors, 'background-1');
color: map.get($colors, 'foreground-1');
a,
a:visited {
color: map.get($colors, 'accent-1');
&:hover {
color: map.get($colors, 'accent-2');
}
}
code,
pre {
background-color: map.get($colors, 'background-1');
}
ul > li {
$highlights: map.get($colors, 'highlights');
@for $index from 1 through list.length($highlights) {
$highlight: nth($highlights, $index);
&:nth-child(#{list.length($highlights)}n + #{$index})::marker {
color: $highlight;
}
}
}
table {
border: 0.1rem solid map.get($colors, 'background-1');
}
thead > tr {
background-color: map.get($colors, 'background-1');
border-bottom: 0.1rem solid map.get($colors, 'background-1');
}
tbody > tr {
background-color: map.get($colors, 'background-3');
border-bottom: 0.1rem solid map.get($colors, 'background-1');
&:last-child {
border-bottom: none;
}
}
#header {
a {
color: map.get($colors, 'foreground-1');
}
button {
background-color: transparent;
color: map.get($colors, 'accent-1');
&:hover {
background-color: map.get($colors, 'accent-2');
}
}
}
#main {
background-color: map.get($colors, 'background-2');
}
#footer {
background-color: scale-color(map.get($colors, 'background-3'), $lightness: -3.5%);
border-top: 0.25rem solid map.get($colors, 'accent-2');
}
#post {
$highlights: map.get($colors, 'highlights');
h2 {
border-bottom: 0.5rem solid map.get($colors, 'accent-1');
}
@for $index from 1 through list.length($highlights) {
$highlight: nth($highlights, $index);
h3:nth-of-type(#{list.length($highlights)}n + #{$index}) {
border-bottom: 0.5rem solid $highlight;
}
}
.link-group {
color: map.get($colors, 'highlight-2');
&:hover {
color: map.get($colors, 'accent-2');
}
}
.link-user {
color: map.get($colors, 'highlight-5');
&:hover {
color: map.get($colors, 'accent-2');
}
}
}
#posts .year {
$highlights: map.get($colors, 'highlights');
@for $index from 1 through list.length($highlights) {
$highlight: nth($highlights, $index);
&:nth-child(#{list.length($highlights)}n + #{$index}) {
h3 {
border-bottom: 0.5rem solid $highlight;
}
a {
border-bottom: 0.25rem solid scale-color($highlight, $alpha: -75%);
border-left: 0.25rem solid scale-color($highlight, $alpha: -75%);
&:hover {
background-color: $highlight;
color: map.get($colors, 'background-1');
}
}
}
}
}
}
#dark-theme {
@include theme($dark-theme);
}
#light-theme {
@include theme($light-theme);
}

View File

@ -0,0 +1,54 @@
$small-breakpoint: 600px;
$medium-breakpoint: 900px;
$large-breakpoint: 1200px;
$extra-large-breakpoint: 1800px;
$dark-highlight-1: #e95678;
$dark-highlight-2: #fac29a;
$dark-highlight-3: #b877db;
$dark-highlight-4: #25b2bc;
$dark-highlight-5: #09f7a0;
$dark-highlights: $dark-highlight-1, $dark-highlight-2, $dark-highlight-3, $dark-highlight-4, $dark-highlight-5;
$dark-theme: (
'background-1': #16161c,
'background-2': #1c1e26,
'background-3': #1a1c23,
'foreground-1': #fdf0ed,
'foreground-2': #fadad1,
'foreground-3': #f9cbbe,
'accent-1': #59e3e3,
'accent-2': #ee64ae,
'highlight-1': $dark-highlight-1,
'highlight-2': $dark-highlight-2,
'highlight-3': $dark-highlight-3,
'highlight-4': $dark-highlight-4,
'highlight-5': $dark-highlight-5,
'highlights': $dark-highlights,
);
$light-highlight-1: #da103f;
$light-highlight-2: #f77d26;
$light-highlight-3: #8931b9;
$light-highlight-4: #1eaeae;
$light-highlight-5: #1eb980;
$light-highlights: $light-highlight-1, $light-highlight-2, $light-highlight-3, $light-highlight-4, $light-highlight-5;
$light-theme: (
'background-1': #f9cbbe,
'background-2': #fadad1,
'background-3': scale-color(#fadad1, $lightness: 10%),
'foreground-1': #16161c,
'foreground-2': #1c1e26,
'foreground-3': #1a1c23,
'accent-1': #1d8991,
'accent-2': #f43e5c,
'highlight-1': $light-highlight-1,
'highlight-2': $light-highlight-2,
'highlight-3': $light-highlight-3,
'highlight-4': $light-highlight-4,
'highlight-5': $light-highlight-5,
'highlights': $light-highlights,
);

View File

@ -0,0 +1,25 @@
// External imports.
@import '../../../node_modules/modern-normalize/modern-normalize';
// Top-level and Sass-related imports.
@import 'reset';
@import 'variables';
@import 'mixins';
// HTML-targetted imports.
@import 'html';
@import 'fonts';
// Site styling imports.
@import 'header';
@import 'main';
@import 'footer';
// Location-specific imports.
@import 'attributions';
@import 'index';
@import 'post';
@import 'posts';
// Theming imports.
@import 'theme';

View File

@ -0,0 +1,35 @@
{% set pageTitle = 'Tildes Issue Log - Attributions' %}
{% set toRoot = '.' %}
{% extends "base.html" %}
{% block body %}
<main id="main">
<section id="attributions">
<h2>Attributions</h2>
<h3>Fonts</h3>
<ul>
<li>
<a href="https://rsms.me/inter/">Inter</a>,
licensed under the
<a href="https://github.com/rsms/inter/blob/master/LICENSE.txt">SIL Open Font License 1.1</a>.
</li>
<li>
<p id="hasklig">
<a href="https://github.com/i-tu/Hasklig">Hasklig</a>,
licensed under the
<a href="https://github.com/adobe-fonts/source-code-pro/blob/release/LICENSE.md">SIL Open Font License 1.1</a>.
</p>
</li>
</ul>
<h3>Images</h3>
<ul>
<li>
<a href="https://mutant.tech/">Mutant Standard</a>,
licensed under the
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License</a>.
</li>
</ul>
</section>
</main>
{% endblock %}

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="alternate" type="application/rss+xml" href="{{ toRoot }}/feed.rss" title="Tildes Issue Log">
<link rel="alternate" type="application/atom+xml" href="{{ toRoot }}/feed.atom" title="Tildes Issue Log">
<link rel="alternate" type="application/json" href="{{ toRoot }}/feed.json" title="Tildes Issue Log">
<title>{{ pageTitle }}</title>
<link href="{{ toRoot }}/css/style.css" rel="stylesheet">
{% block head %}{% endblock %}
<link rel="apple-touch-icon" sizes="180x180" href="{{ toRoot }}/apple-touch-icon.png">
<link rel="apple-touch-icon" sizes="180x180" href="{{ toRoot }}/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{ toRoot }}/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{ toRoot }}/favicon-16x16.png">
<link rel="manifest" href="{{ toRoot }}/site.webmanifest">
<link rel="mask-icon" href="{{ toRoot }}/safari-pinned-tab.svg" color="#282a36">
<link rel="shortcut icon" href="{{ toRoot }}/favicon.ico">
<meta name="msapplication-TileColor" content="#282a36">
<meta name="msapplication-config" content="{{ toRoot }}/browserconfig.xml">
<meta name="theme-color" content="#282a36">
</head>
<body id="dark-theme">
<header id="header">
<a href="{{ toRoot }}">
<img alt="The Tildes Issue Log logo." src="{{ toRoot }}/images/tildes-issue-log.png">
<h1 class="small">Issue Log</h1>
<h1 class="big">Tildes Issue Log</h1>
</a>
<button aria-label="Theme Toggle" id="theme-toggle"></button>
</header>
{% block body %}
{% endblock %}
<footer id="footer">
<a href="{{ toRoot }}">Home</a>
<a id="license" href="{{ toRoot }}/attributions.html">Tildes Issue Log</a>
</footer>
<script src="{{ toRoot }}/js/theme-switcher.js"></script>
</body>
</html>

View File

@ -0,0 +1,43 @@
{% set pageTitle = 'Tildes Issue Log' %}
{% set toRoot = '.' %}
{% extends "base.html" %}
{% block body %}
<main id="main">
<section id="newest-post">
<h2>
Newest Post:
<a href="{{ newestPost.url }}">
{{ months[newestPost.month]|capitalize }}&nbsp;{{ newestPost.year }}
</a>
</h2>
<a href="{{ toRoot }}/posts.html">Click here to view all posts</a>.
</section>
<section id="about">
<h2>About</h2>
<p>The Tildes Issue Log is a monthly blog about the development of <a href="https://tildes.net">Tildes</a>. Each month highlighting any new features, bug fixes and occasional community-related events.</p>
<p></p>
</section>
<section id="feeds">
<h2>Feeds</h2>
<p>Want to stay up to date in your own way? That's possible! You can subscribe to any of the feeds or the mailing list below and you'll get all the highlights right where you like 'em.</p>
<ul>
<li>
<a href="{{ toRoot }}/feed.atom">Atom</a>
</li>
<li>
<a href="{{ toRoot }}/feed.json">JSON Feed</a>
</li>
<li>
<a href="{{ toRoot }}/feed.rss">RSS</a>
</li>
<li>
<a href="mailto:~bauke/tildes-issue-log+subscribe@lists.sr.ht">Subscribe to the mailing list</a>
or
<a href="https://lists.sr.ht/~bauke/tildes-issue-log">view the archives</a>.
</li>
</ul>
</section>
</main>
{% endblock %}

View File

@ -0,0 +1,26 @@
{% if officialTopics.length > 0 %}
<h3 id="official-topics">Official Topics</h3>
<table>
<thead>
<tr>
<th>Date</th>
<th>Topic</th>
</tr>
</thead>
<tbody>
{% for topic in officialTopics %}
<tr>
<td>
<time datetime="{{ topic.date.toISOString() }}" title="{{ topic.date.toUTCString() }}">
{{ fecha.format(topic.date, 'YYYY-MM-DD') }}
</time>
</td>
<td>
<a href="{{ topic.url }}">{{ topic.title }}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}

View File

@ -0,0 +1,14 @@
{% set pageTitle = months[post.month]|capitalize + ' ' + post.year %}
{% set toRoot = '..' %}
{% extends "base.html" %}
{% block body %}
<main id="main">
<section id="post">
{{ post.renderedHTML|safe }}
{% include "statistics.html" %}
{% include "official-topics.html" %}
</section>
</main>
{% endblock %}

View File

@ -0,0 +1,28 @@
{% set pageTitle = 'Tildes Issue Log' %}
{% set toRoot = '.' %}
{% extends "base.html" %}
{% block body %}
<main id="main">
<div id="posts">
<h2>All Posts</h2>
{% for list in posts %}
{% set parent_first_loop = loop.first %}
<div class="year">
<h3>{{ list.year }}</h3>
<ul class="posts">
{% for post in list.posts %}
{% set is_newest = parent_first_loop and loop.last %}
<li class="post {{ 'newest' if is_newest }}">
<a href="{{toRoot }}/{{ post.url }}">
{{ months[post.month]|capitalize }}
</a>
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
</main>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% set pageTitle = 'Tildes Issue Log - Redirecting…' %}
{% set toRoot = '..' %}
{% extends "base.html" %}
{% set newURL = toRoot + '/' + year + '/' + month + '.html' %}
{% block head %}
<meta http-equiv="refresh" content="5; url={{ newURL }}" />
{% endblock %}
{% block body %}
<main id="main">
<p>
You've followed an old link to the
<a href="{{ newURL }}">{{ month|capitalize }} {{ year }} post</a>.
</p>
<p>
You can click on the link to go to the new post or wait 5 seconds to be redirected.
</p>
</main>
{% endblock %}

View File

@ -0,0 +1,63 @@
<h3 id="statistics">Statistics</h3>
<p class="half-margin">In the month of {{ pageTitle }}…</p>
<ul>
{% if statistic.contributions.commits > 0 and statistic.contributions.authors > 0 %}
<li>
<strong>{{ statistic.contributions.commits }}</strong>
{{ pluralize('commit', 'commits', statistic.contributions.commits) }}
{{ pluralize('was', 'were', statistic.contributions.commits) }} made by
<strong>{{ statistic.contributions.authors }}</strong>
{{ pluralize('contributor', 'contributors', statistic.contributions.authors) }}.
</li>
{% endif %}
{% if statistic.lines.added > 0 %}
<li>
<strong>{{ statistic.lines.added }}</strong>
{{ pluralize('line', 'lines', statistic.lines.added) }} of code
{{ pluralize('was', 'were', statistic.lines.added) }} added.
</li>
{% endif %}
{% if statistic.lines.removed > 0 %}
<li>
<strong>{{ statistic.lines.removed }}</strong>
{{ pluralize('line', 'lines', statistic.lines.removed) }} of code
{{ pluralize('was', 'were', statistic.lines.removed) }} removed.
</li>
{% endif %}
{% if statistic.issues.opened > 0 %}
<li>
<strong>{{ statistic.issues.opened }}</strong>
{{ pluralize('issue', 'issues', statistic.issues.opened) }}
{{ pluralize('was', 'were', statistic.issues.opened) }} opened.
</li>
{% endif %}
{% if statistic.issues.closed > 0 %}
<li>
<strong>{{ statistic.issues.closed }}</strong>
{{ pluralize('issue', 'issues', statistic.issues.closed) }}
{{ pluralize('was', 'were', statistic.issues.closed) }} closed.
</li>
{% endif %}
{% if statistic.mergeRequests.opened > 0 %}
<li>
<strong>{{ statistic.mergeRequests.opened }}</strong>
{{ pluralize('issue', 'issues', statistic.mergeRequests.opened) }}
{{ pluralize('was', 'were', statistic.mergeRequests.opened) }} opened.
</li>
{% endif %}
{% if statistic.mergeRequests.closed > 0 %}
<li>
<strong>{{ statistic.mergeRequests.closed }}</strong>
{{ pluralize('issue', 'issues', statistic.mergeRequests.closed) }}
{{ pluralize('was', 'were', statistic.mergeRequests.closed) }} closed/merged.
</li>
{% endif %}
</ul>

21
source/scripts/assets.ts Normal file
View File

@ -0,0 +1,21 @@
import {join} from 'path';
import tar from 'tar';
async function entry(): Promise<void> {
await tar.extract({
file: join(__dirname, '../pages/assets/fonts/Inter.tar'),
cwd: join(__dirname, '../../public/fonts/')
});
await tar.extract({
file: join(__dirname, '../pages/assets/fonts/Hasklig.tar'),
cwd: join(__dirname, '../../public/fonts/')
});
await tar.extract({
file: join(__dirname, '../pages/assets/favicons.tar'),
cwd: join(__dirname, '../../public/')
});
}
if (require.main === module) {
entry();
}

118
source/scripts/download.ts Normal file
View File

@ -0,0 +1,118 @@
import fs, {promises as fsp} from 'fs';
import {join} from 'path';
import fecha from 'fecha';
import gitlab, {Gitlab} from 'gitlab';
type GitLab = InstanceType<typeof Gitlab>;
interface Config {
gitlabToken: string;
}
async function entry(): Promise<void> {
await Promise.all([getCommits(), getIssues(), getMergeRequests()]);
}
async function setupGitLabAPI(): Promise<GitLab> {
const config: Config = await getConfig();
const api: GitLab = new Gitlab({token: config.gitlabToken});
return api;
}
async function setupTempDirectory(): Promise<string> {
const tempDirectory: string = join(__dirname, '../../temp/');
await fsp.mkdir(tempDirectory + 'data/', {recursive: true});
return tempDirectory;
}
async function getConfig(): Promise<Config> {
const config: Config = JSON.parse(
await fsp.readFile(join(__dirname, '../../config.json'), 'utf8')
);
return config;
}
export async function getCommits(): Promise<any[]> {
const tempDirectory: string = await setupTempDirectory();
const api: GitLab = await setupGitLabAPI();
const project: gitlab.ProjectSchema = await api.Projects.show(
'tildes/tildes'
);
const commitsPath: string = join(
tempDirectory,
`data/commits (${fecha.format(new Date(), 'YYYY-MM-DD')}).json`
);
let commits: any[];
if (fs.existsSync(commitsPath)) {
console.log('Commits JSON file available for today, using local file.');
commits = JSON.parse(await fsp.readFile(commitsPath, 'utf8'));
} else {
console.log('Downloading all commits from GitLab.');
commits = (await api.Commits.all(project.id, {
ref_name: 'master',
with_stats: true
})) as any[];
await fsp.writeFile(commitsPath, JSON.stringify(commits, null, 2));
}
return commits;
}
export async function getIssues(): Promise<any[]> {
const tempDirectory: string = await setupTempDirectory();
const api: GitLab = await setupGitLabAPI();
const project: gitlab.ProjectSchema = await api.Projects.show(
'tildes/tildes'
);
const issuesPath: string = join(
tempDirectory,
`data/issues (${fecha.format(new Date(), 'YYYY-MM-DD')}).json`
);
let issues: any[];
if (fs.existsSync(issuesPath)) {
console.log('Issues JSON file available for today, using local file.');
issues = JSON.parse(await fsp.readFile(issuesPath, 'utf8'));
} else {
console.log('Downloading all issues from GitLab.');
issues = (await api.Issues.all({projectId: project.id})) as any[];
await fsp.writeFile(issuesPath, JSON.stringify(issues, null, 2));
}
issues.sort((a, b) => a.iid - b.iid);
return issues;
}
export async function getMergeRequests(): Promise<any[]> {
const tempDirectory: string = await setupTempDirectory();
const api: GitLab = await setupGitLabAPI();
const project: gitlab.ProjectSchema = await api.Projects.show(
'tildes/tildes'
);
const mergeRequestsPath: string = join(
tempDirectory,
`data/merge requests (${fecha.format(new Date(), 'YYYY-MM-DD')}).json`
);
let mergeRequests: any[];
if (fs.existsSync(mergeRequestsPath)) {
console.log(
'Merge Requests JSON file available for today, using local file.'
);
mergeRequests = JSON.parse(await fsp.readFile(mergeRequestsPath, 'utf8'));
} else {
console.log('Downloading all merge requests from GitLab.');
mergeRequests = (await api.MergeRequests.all({
projectId: project.id
})) as any[];
await fsp.writeFile(
mergeRequestsPath,
JSON.stringify(mergeRequests, null, 2)
);
}
mergeRequests.sort((a, b) => a.iid - b.iid);
return mergeRequests;
}
if (require.main === module) {
entry();
}

87
source/scripts/feeds.ts Normal file
View File

@ -0,0 +1,87 @@
import {promises as fsp} from 'fs';
import {join} from 'path';
import cheerio from 'cheerio';
import {Feed} from 'feed';
import {PostList, months} from './html';
export async function generateFeeds(allPosts: PostList[]): Promise<void> {
const feed = new Feed({
title: 'Tildes Issue Log',
description:
'The Tildes Issue Log is a monthly blog about the development of Tildes.',
id: 'https://til.bauke.xyz',
link: 'https://til.bauke.xyz',
language: 'en',
image: 'https://til.bauke.xyz/android-chrome-192x192.png',
favicon: 'https://til.bauke.xyz/favicon.ico',
copyright:
'AGPL-3.0-or-later Tildes Issue Log Contributors https://gitlab.com/Bauke/tildes-issue-log',
generator: 'https://github.com/jpmonette/feed',
feedLinks: {
atom: 'https://til.bauke.xyz/feed.atom',
json: 'https://til.bauke.xyz/feed.json',
rss: 'https://til.bauke.xyz/feed.rss'
},
author: {
name: 'Bauke',
email: 'me@bauke.xyz',
link: 'https://bauke.xyz'
}
});
// Sort posts by most recent first.
allPosts.sort((a, b) => b.year - a.year);
for (const post of allPosts) {
post.posts.sort((a, b) => b.month - a.month);
}
const maxPosts = 5;
let postsAdded = 0;
for (const year of allPosts) {
if (postsAdded >= maxPosts) {
break;
}
for (const post of year.posts) {
if (postsAdded >= maxPosts) {
break;
}
const month = `${months[post.month][0].toUpperCase()}${months[
post.month
].slice(1)}`;
const title = `${month} ${year.year}`;
const id = `https://til.bauke.xyz/${year.year}/${
months[post.month]
}.html`;
const date = new Date(year.year, post.month, 0, 23, 59, 59);
const html: string = await fsp.readFile(
join(__dirname, `../../public/${year.year}/${months[post.month]}.html`),
'utf8'
);
const $: CheerioStatic = cheerio.load(html);
const content: string = $('#post').html()!;
feed.addItem({
title,
id,
link: id,
date,
published: date,
description: `${title}'s Issue Log`,
content,
image: 'https://til.bauke.xyz/android-chrome-192x192.png'
});
postsAdded++;
}
}
const feedPath: string = join(__dirname, '../../public/feed');
await fsp.writeFile(`${feedPath}.atom`, feed.atom1());
await fsp.writeFile(`${feedPath}.json`, feed.json1());
await fsp.writeFile(`${feedPath}.rss`, feed.rss2());
}

214
source/scripts/html.ts Normal file
View File

@ -0,0 +1,214 @@
import {promises as fsp} from 'fs';
import {basename, join} from 'path';
import fecha from 'fecha';
// @ts-ignore
import htmlclean from 'htmlclean';
import marked from 'marked';
import nunjucks from 'nunjucks';
import wordWrap from 'wordwrap';
import {generateFeeds} from './feeds';
import {OfficialTopic, getTopicsFromMonth} from './official-topics';
import {Statistics} from './statistics';
// Regular expressions taken and slightly adapted from Tildes' Markdown code:
// https://gitlab.com/tildes/tildes/-/blob/89c7c13be2a7fc11d429ee1e340bf2079862eb98/tildes/tildes/lib/markdown.py#L285-296
const groupRegex = /\s(?<!\w)~([\w.]+)\b(?!~)/gm;
const mentionRegex = /\s(?<![\w\\])(?:\/?u\/|@)([\w-]+)\b/gm;
export const months: string[] = [
'zeroary',
'january',
'february',
'march',
'april',
'may',
'june',
'july',
'august',
'september',
'october',
'november',
'december'
];
export interface Post {
markdown: string;
month: number;
rawMarkdown: string;
url: string;
}
export interface PostList {
year: number;
posts: Post[];
}
async function entry(): Promise<void> {
nunjucks.configure(join(__dirname, '../pages/templates/'), {
lstripBlocks: true,
throwOnUndefined: true,
trimBlocks: true
});
const dataDirectory: string = join(__dirname, '../pages/data/');
const statistics: Statistics[] = JSON.parse(
await fsp.readFile(join(dataDirectory, 'statistics.json'), 'utf8')
);
const officialTopics: OfficialTopic[] = JSON.parse(
await fsp.readFile(join(dataDirectory, 'official-topics.json'), 'utf8')
);
const allPosts: PostList[] = [];
// Get a list of all the years we have Markdown files for.
const yearDirectories: string[] = await fsp.readdir(
join(__dirname, '../pages/posts/')
);
for (const year of yearDirectories) {
const postList: PostList = {
year: Number(year),
posts: []
};
// Create the output directory if it doesn't already exist.
await fsp.mkdir(join(__dirname, `../../public/${year}/`), {
recursive: true
});
// Get a list of all the Markdown files.
const markdownFiles: string[] = await fsp.readdir(
join(__dirname, `../pages/posts/${year}/`)
);
for (const file of markdownFiles) {
// Get the filename without the extension.
const fileBasename: string = basename(file, '.md');
// Find the month index in our months array.
const monthNumber: number = months.indexOf(fileBasename);
// Read the actual Markdown.
const markdown: string = await fsp.readFile(
join(__dirname, `../pages/posts/${year}/${file}`),
'utf8'
);
// Add the post to the list.
postList.posts.push({
markdown,
month: monthNumber,
rawMarkdown: markdown,
url: `${year}/${fileBasename}.html`
});
// Get the statistics for that month.
const statistic: Statistics = statistics.find(
val => val.year === Number(year) && val.month === monthNumber
)!;
const topics: OfficialTopic[] = await getTopicsFromMonth(
officialTopics,
Number(year),
monthNumber
);
let renderedHTML: string = marked(markdown);
// Replace ~group with a link to that group.
renderedHTML = renderedHTML.replace(
groupRegex,
' <a class="link-group" href="https://tildes.net/~$1">~$1</a>'
);
// Replace @person with a link to that person.
renderedHTML = renderedHTML.replace(
mentionRegex,
' <a class="link-user" href="https://tildes.net/user/$1">@$1</a>'
);
// Render the post template.
await renderTemplate(
'post.html',
join(__dirname, `../../public/${year}/${fileBasename}.html`),
{
fecha,
months,
officialTopics: topics,
post: {
month: monthNumber,
renderedHTML,
year: Number(year)
},
pluralize,
statistic
}
);
}
// Sort the posts to be ascending by month.
postList.posts.sort((a, b) => a.month - b.month);
allPosts.push(postList);
}
// Sort the post list to be descending by year.
allPosts.sort((a, b) => b.year - a.year);
await renderTemplate(
'posts.html',
join(__dirname, '../../public/posts.html'),
{
months,
posts: allPosts
}
);
// Sort the posts of the most recent year descending by month to select the most recent post.
allPosts[0].posts.sort((a, b) => b.month - a.month);
await renderTemplate(
'index.html',
join(__dirname, '../../public/index.html'),
{
months,
newestPost: {
year: allPosts[0].year,
...allPosts[0].posts[0]
}
}
);
await renderTemplate(
'attributions.html',
join(__dirname, '../../public/attributions.html')
);
await writeEmail(allPosts[0].posts[0].rawMarkdown);
await generateFeeds(allPosts);
}
export async function renderTemplate(
template: string,
location: string,
context: object = {}
): Promise<void> {
const html: string = htmlclean(nunjucks.render(template, context));
await fsp.writeFile(location, html);
}
export async function writeEmail(input: string): Promise<void> {
await fsp.mkdir(join(__dirname, '../../temp/'), {recursive: true});
await fsp.writeFile(
join(__dirname, '../../temp/email.md'),
wordWrap(72)('# Tildes Issue Log\n\n' + input)
);
}
export function pluralize(
singular: string,
plural: string,
count: number
): string {
if (count === 1) {
return singular;
}
return plural;
}
if (require.main === module) {
entry();
}

View File

@ -0,0 +1,104 @@
import {promises as fsp} from 'fs';
import {join} from 'path';
import cheerio from 'cheerio';
import got, {Response} from 'got';
export interface OfficialTopic {
date: Date;
id: string;
title: string;
url: string;
}
async function entry(): Promise<void> {
const timeout = 250;
const officialTopics: OfficialTopic[] = [];
const baseURL = 'https://tildes.net/~tildes.official?order=new&per_page=100';
let hasNextButton = true;
while (hasNextButton) {
// Set the URL to be downloaded, on the first iteration we won't have an ID for
// `&after=` yet so we make sure to check for that.
let getURL: string = baseURL;
if (officialTopics.length > 0) {
getURL += `&after=${officialTopics[officialTopics.length - 1].id}`;
}
// Download the page and load the HTML into Cheerio.
const response: Response<string> = await got(getURL, {
headers: {
'User-Agent': 'Tildes Issue Log scraping Official Topics'
}
});
const html: CheerioStatic = cheerio.load(response.body);
// Grab all topics from the listing.
const topics: CheerioElement[] = html(
'.topic-listing > li > article'
).toArray();
for (const topic of topics) {
const $topic: CheerioStatic = cheerio.load(topic);
// Topic IDs in the listing will start with `topic-` so remove that first.
const id: string = topic.attribs.id.slice(6);
officialTopics.push({
date: new Date($topic('time').attr('datetime')!),
id,
title: $topic('.topic-title > a').text(),
url: `https://tild.es/${id}`
});
}
const paginationButtons: CheerioElement[] = html('.pagination')
.find('.page-item.btn')
.toArray();
// If all pagination buttons are "previous" buttons, stop the loop.
if (
paginationButtons.every(val =>
val.firstChild.data?.toLowerCase().includes('prev')
)
) {
hasNextButton = false;
}
await wait(timeout);
}
await fsp.mkdir(join(__dirname, '../pages/data/'), {recursive: true});
await fsp.writeFile(
join(__dirname, '../pages/data/official-topics.json'),
JSON.stringify(officialTopics, null, 2)
);
}
export async function wait(timeout: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, timeout));
}
export async function getTopicsFromMonth(
data: OfficialTopic[],
year: number,
month: number
): Promise<OfficialTopic[]> {
const topics: OfficialTopic[] = [];
for (const topic of data) {
topic.date = new Date(topic.date);
if (
topic.date.getFullYear() === year &&
topic.date.getMonth() + 1 === month
) {
topics.push(topic);
}
}
topics.sort((a, b) => a.date.getDate() - b.date.getDate());
return topics;
}
if (require.main === module) {
entry();
}

View File

@ -0,0 +1,37 @@
import {promises as fsp} from 'fs';
import {join} from 'path';
import nunjucks from 'nunjucks';
import {renderTemplate, months} from './html';
async function entry(): Promise<void> {
const redirectDirectory: string = join(__dirname, '../../public/posts/');
await fsp.mkdir(redirectDirectory, {recursive: true});
nunjucks.configure(join(__dirname, '../pages/templates/'), {
lstripBlocks: true,
throwOnUndefined: true,
trimBlocks: true
});
for (let year = 2018; year < new Date().getFullYear() + 1; year++) {
for (let monthNumber = 1; monthNumber < 13; monthNumber++) {
if (year === 2018 && monthNumber < 5) {
continue;
}
const month: string = months[monthNumber];
await renderTemplate(
'redirect.html',
join(redirectDirectory, `${month}-${year}.html`),
{
month,
year
}
);
}
}
}
if (require.main === module) {
entry();
}

View File

@ -0,0 +1,321 @@
import {promises as fsp} from 'fs';
import {join} from 'path';
import {getIssues, getCommits, getMergeRequests} from './download';
export interface Result {
count: number;
}
export interface MonthlyResult extends Result {
month: number;
year: number;
}
export interface Statistics {
contributions: {
authors: number;
commits: number;
};
issues: {
closed: number;
opened: number;
};
lines: {
added: number;
removed: number;
};
mergeRequests: {
closed: number;
opened: number;
};
month: number;
year: number;
}
async function entry(): Promise<void> {
const dataDirectory: string = join(__dirname, '../pages/data/');
await fsp.mkdir(dataDirectory, {recursive: true});
// Get the GitLab data from a local file or download them from the API.
const commits: any[] = await getCommits();
const issues: any[] = await getIssues();
const mergeRequests: any[] = await getMergeRequests();
// Get the amount of commits made for each month.
const commitsMade: MonthlyResult[] = await findData(
commits,
() => false,
(result: MonthlyResult, commit: any) =>
result.year === new Date(commit.created_at).getFullYear() &&
result.month === new Date(commit.created_at).getMonth() + 1,
(commit: any): MonthlyResult => ({
count: 1,
year: new Date(commit.created_at).getFullYear(),
month: new Date(commit.created_at).getMonth() + 1
}),
() => 1
);
// Get the amount of lines added for each month.
const linesAdded: MonthlyResult[] = await findData(
commits,
() => false,
(result: MonthlyResult, commit: any) =>
result.year === new Date(commit.created_at).getFullYear() &&
result.month === new Date(commit.created_at).getMonth() + 1,
(commit: any): MonthlyResult => ({
count: commit.stats.additions,
year: new Date(commit.created_at).getFullYear(),
month: new Date(commit.created_at).getMonth() + 1
}),
(commit: any): number => commit.stats.additions
);
// Get the amount of lines removed for each month.
const linesRemoved: MonthlyResult[] = await findData(
commits,
() => false,
(result: MonthlyResult, commit: any) =>
result.year === new Date(commit.created_at).getFullYear() &&
result.month === new Date(commit.created_at).getMonth() + 1,
(commit: any): MonthlyResult => ({
count: commit.stats.deletions,
year: new Date(commit.created_at).getFullYear(),
month: new Date(commit.created_at).getMonth() + 1
}),
(commit: any): number => commit.stats.deletions
);
// Get the amount of issues opened for each month.
const issuesOpened: MonthlyResult[] = await findData(
issues,
() => false,
(result: MonthlyResult, issue: any) =>
result.year === new Date(issue.created_at).getFullYear() &&
result.month === new Date(issue.created_at).getMonth() + 1,
(issue: any) => ({
count: 1,
month: new Date(issue.created_at).getMonth() + 1,
year: new Date(issue.created_at).getFullYear()
}),
() => 1
);
// Get the amount of issues closed for each month.
const issuesClosed: MonthlyResult[] = await findData(
issues,
(issue: any) => issue.closed_at === null,
(result: MonthlyResult, issue: any) =>
result.year === new Date(issue.closed_at).getFullYear() &&
result.month === new Date(issue.closed_at).getMonth() + 1,
(issue: any) => ({
count: 1,
month: new Date(issue.closed_at).getMonth() + 1,
year: new Date(issue.closed_at).getFullYear()
}),
() => 1
);
// Get the amount of merge requests opened for each month.
const mergeRequestsOpened: MonthlyResult[] = await findData(
mergeRequests,
() => false,
(result: MonthlyResult, mergeRequest: any) =>
result.year === new Date(mergeRequest.created_at).getFullYear() &&
result.month === new Date(mergeRequest.created_at).getMonth() + 1,
(mergeRequest: any) => ({
count: 1,
month: new Date(mergeRequest.created_at).getMonth() + 1,
year: new Date(mergeRequest.created_at).getFullYear()
}),
() => 1
);
// Get the amount of merge requests closed/merged for each month.
const mergeRequestsClosed: MonthlyResult[] = await findData(
mergeRequests,
(mergeRequest: any) =>
mergeRequest.closed_at === null && mergeRequest.merged_at === null,
(result: MonthlyResult, mergeRequest: any) =>
result.year ===
new Date(
mergeRequest.closed_at === null
? mergeRequest.merged_at
: mergeRequest.closed_at
).getFullYear() &&
result.month ===
new Date(
mergeRequest.closed_at === null
? mergeRequest.merged_at
: mergeRequest.closed_at
).getMonth() +
1,
(mergeRequest: any) => ({
count: 1,
month:
new Date(
mergeRequest.closed_at === null
? mergeRequest.merged_at
: mergeRequest.closed_at
).getMonth() + 1,
year: new Date(
mergeRequest.closed_at === null
? mergeRequest.merged_at
: mergeRequest.closed_at
).getFullYear()
}),
() => 1
);
// Create empty statistics for months that may not have any data.
const statistics: Statistics[] = [];
// Start at 2018 and end in the current year.
for (let year = 2018; year < new Date().getFullYear() + 1; year++) {
for (let month = 1; month < 13; month++) {
// Tildes started in April of 2018, so don't create anything unless we're in or
// after that month.
if (year === 2018 && month < 4) {
continue;
}
// Once we reach the current year and the next month, stop creating more data.
if (
year === new Date().getFullYear() &&
month === new Date().getMonth() + 2
) {
break;
}
statistics.push({
contributions: {
authors: 0,
commits: 0
},
issues: {
closed: 0,
opened: 0
},
lines: {
added: 0,
removed: 0
},
mergeRequests: {
closed: 0,
opened: 0
},
month,
year
});
}
}
for (const statistic of statistics) {
// Create a `Set()` that will hold all our unique author usernames.
const uniqueContributors: Set<string> = new Set();
for (const commit of commits) {
if (
statistic.year === new Date(commit.created_at).getFullYear() &&
statistic.month === new Date(commit.created_at).getMonth() + 1
) {
uniqueContributors.add(commit.author_name);
}
}
// Add all the data to the statistic.
statistic.contributions.authors = uniqueContributors.size;
statistic.issues.opened = await getResultCount(
issuesOpened,
statistic.year,
statistic.month
);
statistic.contributions.commits = await getResultCount(
commitsMade,
statistic.year,
statistic.month
);
statistic.issues.closed = await getResultCount(
issuesClosed,
statistic.year,
statistic.month
);
statistic.lines.added = await getResultCount(
linesAdded,
statistic.year,
statistic.month
);
statistic.lines.removed = await getResultCount(
linesRemoved,
statistic.year,
statistic.month
);
statistic.mergeRequests.closed = await getResultCount(
mergeRequestsClosed,
statistic.year,
statistic.month
);
statistic.mergeRequests.opened = await getResultCount(
mergeRequestsOpened,
statistic.year,
statistic.month
);
}
// Sort the statistics ascending by year and month.
statistics.sort((a, b) => a.year - b.year || a.month - b.month);
// Write the statistics to file.
await fsp.writeFile(
join(dataDirectory, 'statistics.json'),
JSON.stringify(statistics, null, 2)
);
}
// A generic function that takes in data and a set of functions to count something from
// that data.
export async function findData<T extends Result>(
data: any[],
ignoreFn: (point: any) => boolean,
findFn: (result: T, point: any) => boolean,
undefinedFn: (point: any) => T,
incrementFn: (point: any) => number
): Promise<T[]> {
const monthlies: T[] = [];
for (const point of data) {
// If the ignore function returns true we want to skip the current datapoint.
if (ignoreFn(point)) {
continue;
}
// Find the monthly that applies to this datapoint.
const monthly: T | undefined = monthlies.find(result =>
findFn(result, point)
);
// If it doesn't exist yet we want to create it.
// Otherwise we want to increment the count.
if (monthly === undefined) {
monthlies.push(undefinedFn(point));
} else {
monthly.count += incrementFn(point);
}
}
return monthlies;
}
// A function that returns the count of a MonthlyResult in the specified year and month.
async function getResultCount(
results: MonthlyResult[],
year: number,
month: number
): Promise<number> {
const result: MonthlyResult | undefined = results.find(
val => val.year === year && val.month === month
);
return result === undefined ? 0 : result.count;
}
if (require.main === module) {
entry();
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#282a36</TileColor>
</tile>
</msapplication>
</browserconfig>

Some files were not shown because too many files have changed in this diff Show More