improved modularization

This commit is contained in:
Koen Lagveen 2023-07-05 16:42:30 +02:00
parent 2944154ed7
commit b4b7ea6da9
11 changed files with 266 additions and 207 deletions

View File

@ -4,7 +4,7 @@ install:
npm install
lint:
npx eslint *.js
npx eslint *.js modules/*.js
test:
npx ajv -c ajv-formats -s fonts-schema.json -d fonts.json

View File

@ -326,6 +326,6 @@ function updateGutters(cm) {
<script defer data-domain="programmingfonts.org" src="https://plausible.io/js/script.tagged-events.js"></script>
<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script>
<script src="index.js"></script>
<script src="index.js" type="module"></script>
</body>
</html>

205
index.js
View File

@ -1,11 +1,16 @@
/* global CodeMirror window document Set plausible */
/* eslint-disable no-implicit-globals */
/* global CodeMirror plausible */
import { Cookies } from './modules/cookies.js'
import { Fontsize } from './modules/fontsize.js'
import { Filters } from './modules/filters.js'
import { Language } from './modules/language.js'
import { Spacing } from './modules/spacing.js'
import { Theme } from './modules/theme.js'
// CodeMirror
let fontData
const fontsize = new Fontsize()
window.CMeditor = CodeMirror.fromTextArea(document.getElementById('code'), {
lineNumbers: true,
styleActiveLine: true,
@ -14,37 +19,41 @@ window.CMeditor = CodeMirror.fromTextArea(document.getElementById('code'), {
lineWrapping: true
})
let fontData
const filters = {
style: false,
rendering: false,
liga: false,
zerostyle: false,
author: 'all',
name: ''
/**
* Get the font from the #, the cookie, or a default
*/
function getFont () {
let font = window.location.hash.substring(1)
if (!font) {
font = Cookies.get('font')
}
if (!font) {
font = 'source-code-pro'
}
return font
}
// ProgrammingFonts font selector
const selectFont = () => {
let font = window.location.hash.substring(1)
const msg = document.querySelector('footer .subtitle')
const codeMirror = document.querySelector('.CodeMirror')
const font = getFont()
if (!font) {
font = 'source-code-pro'
msg.innerHTML = 'Test drive all the programming fonts!'
} else if (typeof fontData !== 'undefined') {
if (typeof fontData !== 'undefined') {
msg.innerHTML = `Test drive <a rel="external" href="${fontData[font].website}">${fontData[font].name}!</a>`
}
if (typeof fontData !== 'undefined' && fontData[font].rendering === 'bitmap') {
codeMirror.classList.add('no-smooth')
if (fontData[font]['bitmap size']) {
Fontsize.forceSize(fontData[font]['bitmap size'])
fontsize.forceSize(fontData[font]['bitmap size'])
}
} else {
codeMirror.classList.remove('no-smooth')
Fontsize.reset()
fontsize.reset()
}
if (font === 'input') {
@ -69,72 +78,6 @@ const selectFont = () => {
Cookies.set('font', font)
}
function setSpacing () {
const spacing = document.getElementById('spacing').value
document.querySelector('.CodeMirror').style.lineHeight = spacing
Cookies.set('spacing', spacing)
window.CMeditor.refresh()
}
function selectLanguage () {
const lang = document.getElementById('select-language').value
window.CMeditor.setOption('mode', lang.toLowerCase())
Cookies.set('language', lang)
}
function setCounter (amount) {
const element = document.querySelector('h1 a:first-child')
if (amount === 1) {
element.innerHTML = `${amount} Programming Font`
} else {
element.innerHTML = `${amount} Programming Fonts`
}
}
function applyFilters () {
let count = 0
Object.keys(filters).forEach((filter) => {
const button = document.querySelector(`button[value="${filter}"]`)
if (!button) {
return
}
if (filters[filter]) {
button.classList.add('selected')
button.querySelectorAll('svg').forEach((image) => {
image.classList.remove('selected')
})
button.querySelector(`svg[alt="${filters[filter]}"]`).classList.add('selected')
} else {
button.classList.remove('selected')
button.querySelectorAll('svg').forEach((image) => {
image.classList.remove('selected')
})
}
})
document.querySelectorAll('.entry[data-alias]').forEach((element) => {
const data = fontData[element.dataset.alias]
if (
(!filters.style || data.style === filters.style) &&
(!filters.rendering || data.rendering === filters.rendering) &&
(!filters.liga ||
(data.ligatures === false && filters.liga === 'no') ||
(data.ligatures === true && filters.liga === 'yes')) &&
(!filters.zerostyle || data.zerostyle === filters.zerostyle) &&
(filters.author === 'all' || data.author === filters.author) &&
(!filters.name || data.name.toLowerCase().indexOf(filters.name) > -1)
) {
element.classList.remove('filtered-out')
count++
} else {
element.classList.add('filtered-out')
}
})
setCounter(count)
}
function renderSelectList () {
const icon =
'<svg class="octicon" viewBox="0 0 12 14" version="1.1" width="12" height="14" aria-hidden="true"><path fill-rule="evenodd" d="M11 10h1v3c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1h3v1H1v10h10v-3zM6 2l2.25 2.25L5 7.5 6.5 9l3.25-3.25L12 8V2H6z"></path></svg>'
@ -211,15 +154,14 @@ function renderSelectList () {
})
selectFont()
applyFilters()
new Filters(fontData).init()
}
ajax.responseType = 'json'
ajax.open('GET', 'fonts.json', true)
ajax.send()
}
// eslint-disable-next-line no-unused-vars
function toggleFavorite (alias) {
window.toggleFavorite = (alias) => {
try {
let favorites = JSON.parse(localStorage.getItem('favorites')) || []
if (favorites.indexOf(alias) > -1) {
@ -279,94 +221,17 @@ function walk (direction) {
}
}
function toggleFilter (filter) {
// cycle through the possible values for each filter
// and set the filters[filter] value,
// or at the end of the cycle set it to false
const options = {
style: [false, 'sans', 'serif'],
rendering: [false, 'vector', 'bitmap'],
liga: [false, 'yes', 'no'],
zerostyle: [false, 'slashed', 'dotted', 'empty']
}
const index = options[filter].indexOf(filters[filter])
const next = index + 1
if (next < options[filter].length) {
filters[filter] = options[filter][next]
} else {
filters[filter] = options[filter][0]
}
applyFilters()
}
function walkThemes (direction) {
const select = document.getElementById('select-theme')
const current = select.selectedOptions[0]
let next
if (current) {
next = direction === 'up' ? current.previousElementSibling : current.nextElementSibling
}
if (next) {
select.value = next.value
}
Theme.set()
}
window.onhashchange = () => {
plausible('Font Selected')
selectFont()
}
window.addEventListener('DOMContentLoaded', () => {
if (Cookies.get('spacing') !== '') {
document.getElementById('spacing').value = Cookies.get('spacing')
}
if (Cookies.get('size') !== '') {
document.getElementById('size').value = Cookies.get('size')
}
if (Cookies.get('theme') !== '') {
document.getElementById('select-theme').value = Cookies.get('theme')
}
if (Cookies.get('language') !== '') {
document.getElementById('select-language').value = Cookies.get('language')
}
renderSelectList()
Theme.init()
Fontsize.init()
setSpacing()
selectLanguage()
document.getElementById('theme-next').onclick = () => {
walkThemes('down')
}
document.getElementById('theme-previous').onclick = () => {
walkThemes('up')
}
document
.getElementById('filters')
.querySelectorAll('button')
.forEach((button) => {
button.onclick = (event) => {
event.preventDefault()
event.stopPropagation()
toggleFilter(button.value)
}
})
document.getElementById('authors-list').onchange = (event) => {
filters.author = event.target.value
applyFilters()
}
document.getElementById('name-search').onkeyup = (event) => {
filters.name = event.target.value.toLowerCase()
applyFilters()
}
new Theme().init()
fontsize.init()
new Spacing().init()
new Language().init()
document.querySelector('.select-list').onkeyup = (event) => {
if (event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) {
@ -388,11 +253,11 @@ window.addEventListener('DOMContentLoaded', () => {
if (event.key === '-') {
event.preventDefault()
event.stopPropagation()
Fontsize.down()
fontsize.down()
} else if (event.key === '=') {
event.preventDefault()
event.stopPropagation()
Fontsize.up()
fontsize.up()
}
}
})

View File

@ -1,12 +0,0 @@
export default class Cookies {
get (key) {
return document.cookie.replace(
new RegExp(`/(?:(?:^|.*;\\s*)${key}\\s*=\\s*([^;]*).*$)|^.*$/`),
'$1'
)
}
set (key, value) {
document.cookie = `${key}=${value};max-age=172800`
}
}

View File

@ -1,22 +0,0 @@
import { Cookies } from './cookies.js'
export default class Theme {
el = document.getElementById('select-theme')
init () {
this.el.onchange = () => {
this.set()
}
this.set()
}
set () {
let theme = 'oceanic-next'
if (this.el.selectedIndex > -1) {
theme = this.el.options[this.el.selectedIndex].textContent
}
window.CMeditor.setOption('theme', theme)
Cookies.set('theme', theme)
}
}

19
modules/cookies.js Normal file
View File

@ -0,0 +1,19 @@
export class Cookies {
static get (key) {
return document.cookie
.split('; ')
.find((row) => row.startsWith(`${key}=`))
?.split('=')[1]
}
convert (value) {
if (value[0] === '"') {
value = value.slice(1, -1)
}
return value.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent)
}
static set (key, value) {
document.cookie = `${key}=${value};max-age=172800`
}
}

116
modules/filters.js Normal file
View File

@ -0,0 +1,116 @@
export class Filters {
filters = {
style: false,
rendering: false,
liga: false,
zerostyle: false,
author: 'all',
name: ''
}
fontData = {}
constructor (data) {
this.fontData = data
}
init () {
document.getElementById('authors-list').onchange = (event) => {
this.filters.author = event.target.value
this.apply()
}
document.getElementById('name-search').onkeyup = (event) => {
this.filters.name = event.target.value.toLowerCase()
this.apply()
}
document
.getElementById('filters')
.querySelectorAll('button')
.forEach((button) => {
button.onclick = (event) => {
event.preventDefault()
event.stopPropagation()
this.toggle(button.value)
}
})
this.apply()
}
toggle (filter) {
// cycle through the possible values for each filter
// and set the filters[filter] value,
// or at the end of the cycle set it to false
const options = {
style: [false, 'sans', 'serif'],
rendering: [false, 'vector', 'bitmap'],
liga: [false, 'yes', 'no'],
zerostyle: [false, 'slashed', 'dotted', 'empty']
}
const index = options[filter].indexOf(this.filters[filter])
const next = index + 1
if (next < options[filter].length) {
this.filters[filter] = options[filter][next]
} else {
this.filters[filter] = options[filter][0]
}
this.apply()
}
setCounter (amount) {
const element = document.querySelector('h1 a:first-child')
if (amount === 1) {
element.innerHTML = `${amount} Programming Font`
} else {
element.innerHTML = `${amount} Programming Fonts`
}
}
apply () {
let count = 0
Object.keys(this.filters).forEach((filter) => {
const button = document.querySelector(`button[value="${filter}"]`)
if (!button) {
return
}
if (this.filters[filter]) {
button.classList.add('selected')
button.querySelectorAll('svg').forEach((image) => {
image.classList.remove('selected')
})
button.querySelector(`svg[alt="${this.filters[filter]}"]`).classList.add('selected')
} else {
button.classList.remove('selected')
button.querySelectorAll('svg').forEach((image) => {
image.classList.remove('selected')
})
}
})
document.querySelectorAll('.entry[data-alias]').forEach((element) => {
const data = this.fontData[element.dataset.alias]
if (
(!this.filters.style || data.style === this.filters.style) &&
(!this.filters.rendering || data.rendering === this.filters.rendering) &&
(!this.filters.liga ||
(data.ligatures === false && this.filters.liga === 'no') ||
(data.ligatures === true && this.filters.liga === 'yes')) &&
(!this.filters.zerostyle || data.zerostyle === this.filters.zerostyle) &&
(this.filters.author === 'all' || data.author === this.filters.author) &&
(!this.filters.name || data.name.toLowerCase().indexOf(this.filters.name) > -1)
) {
element.classList.remove('filtered-out')
count++
} else {
element.classList.add('filtered-out')
}
})
this.setCounter(count)
}
}

View File

@ -1,9 +1,12 @@
import { Cookies } from './cookies.js'
export default class Fontsize {
export class Fontsize {
el = document.getElementById('size')
init () {
if (Cookies.get('size')) {
this.el.value = Cookies.get('size')
}
this.el.onchange = () => {
this.set()
}

22
modules/language.js Normal file
View File

@ -0,0 +1,22 @@
import { Cookies } from './cookies.js'
export class Language {
el = document.getElementById('select-language')
init () {
if (Cookies.get('language')) {
this.el.value = Cookies.get('language')
}
this.el.onchange = () => {
this.set()
}
this.set()
}
set () {
const lang = this.el.value
window.CMeditor.setOption('mode', lang.toLowerCase())
Cookies.set('language', lang)
}
}

23
modules/spacing.js Normal file
View File

@ -0,0 +1,23 @@
import { Cookies } from './cookies.js'
export class Spacing {
el = document.getElementById('spacing')
init () {
if (Cookies.get('spacing')) {
this.el.value = Cookies.get('spacing')
}
this.el.onchange = () => {
this.set()
}
this.set()
}
set () {
const spacing = this.el.value
document.querySelector('.CodeMirror').style.lineHeight = spacing
Cookies.set('spacing', spacing)
window.CMeditor.refresh()
}
}

45
modules/theme.js Normal file
View File

@ -0,0 +1,45 @@
import { Cookies } from './cookies.js'
export class Theme {
el = document.getElementById('select-theme')
init () {
if (Cookies.get('theme')) {
this.el.value = Cookies.get('theme')
}
this.el.onchange = () => {
this.set()
}
this.set()
document.getElementById('theme-next').onclick = () => {
this.walk('down')
}
document.getElementById('theme-previous').onclick = () => {
this.walk('up')
}
}
set () {
let theme = 'oceanic-next'
if (this.el.selectedIndex > -1) {
theme = this.el.options[this.el.selectedIndex].textContent
}
window.CMeditor.setOption('theme', theme)
Cookies.set('theme', theme)
}
walk (direction) {
const current = this.el.selectedOptions[0]
let next
if (current) {
next = direction === 'up' ? current.previousElementSibling : current.nextElementSibling
}
if (next) {
this.el.value = next.value
}
this.set()
}
}