diff --git a/Makefile b/Makefile index 63659e8..2a21c66 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/index.html b/index.html index ae85240..0774fc5 100755 --- a/index.html +++ b/index.html @@ -326,6 +326,6 @@ function updateGutters(cm) { - + diff --git a/index.js b/index.js index b87257d..f10fa96 100644 --- a/index.js +++ b/index.js @@ -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 ${fontData[font].name}!` } 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 = '' @@ -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() } } }) diff --git a/module/cookies.js b/module/cookies.js deleted file mode 100644 index 864fe54..0000000 --- a/module/cookies.js +++ /dev/null @@ -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` - } -} diff --git a/module/theme.js b/module/theme.js deleted file mode 100644 index 9ac2772..0000000 --- a/module/theme.js +++ /dev/null @@ -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) - } -} diff --git a/modules/cookies.js b/modules/cookies.js new file mode 100644 index 0000000..bcbe486 --- /dev/null +++ b/modules/cookies.js @@ -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` + } +} diff --git a/modules/filters.js b/modules/filters.js new file mode 100644 index 0000000..df6ad29 --- /dev/null +++ b/modules/filters.js @@ -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) + } +} diff --git a/module/fontsize.js b/modules/fontsize.js similarity index 89% rename from module/fontsize.js rename to modules/fontsize.js index bb1c058..452c5c3 100644 --- a/module/fontsize.js +++ b/modules/fontsize.js @@ -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() } diff --git a/modules/language.js b/modules/language.js new file mode 100644 index 0000000..cdacc22 --- /dev/null +++ b/modules/language.js @@ -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) + } +} diff --git a/modules/spacing.js b/modules/spacing.js new file mode 100644 index 0000000..af1882d --- /dev/null +++ b/modules/spacing.js @@ -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() + } +} diff --git a/modules/theme.js b/modules/theme.js new file mode 100644 index 0000000..eb04143 --- /dev/null +++ b/modules/theme.js @@ -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() + } +}