commit 9f6fb1a2c54423129f11abab7c00f5e905307e5e Author: Anders Englöf Ytterström Date: Sat May 3 18:48:38 2025 +0200 Initial import diff --git a/2008.ie-valign.css b/2008.ie-valign.css new file mode 100644 index 0000000..d0f4306 --- /dev/null +++ b/2008.ie-valign.css @@ -0,0 +1,66 @@ +/* @group valignfix */ +/* *************************************** +valignfix, by Anders Ytterström @ madr.se +**************************************** */ + /* + HTML fixture: + .valign + %div + %div + %h1 + %h2 + %img + */ + + /* @group ie8, ff, opera, safari + ===================================== */ + body > div { + display:table; + } + *.valign { + display:table-row; + } + *.valign > * { + width:50%; + display:table-cell; + vertical-align:middle; + } + /* @end ie8, ff, opera, safari + ===================================== */ + + /* @group ie6,ie7 + ===================================== */ + /* + use this in CC file. + Works only in standards mode. + */ + * html *.valign, + *+html *.valign { + position:relative; + overflow:auto; + } + * html *.valign div, + *+html *.valign div { + position:absolute; + top:50%; + } + * html *.valign div div, + *+html *.valign div div { + position:relative; + top:-50%; + } + * html *.valign div div *, + *+html *.valign div div * { + position:static; + top:0; + } + * html *.valign img, + *+html *.valign img { + float:right; + } + /* @end ie6, ie7 + ===================================== */ +/* **************************************** +/valignfix, by Anders Ytterström @ madr.se +**************************************** */ +/* @end valignfix */ diff --git a/2011.fix-outlook-2011-utf8-decoded-mails.php b/2011.fix-outlook-2011-utf8-decoded-mails.php new file mode 100644 index 0000000..58bc134 --- /dev/null +++ b/2011.fix-outlook-2011-utf8-decoded-mails.php @@ -0,0 +1,4 @@ + +Licensed under the MIT license: http://en.wikipedia.org/wiki/MIT_License +*/ +/* +Usage: +Alter the below snippet for your needs and put the modified snippet in a js-file or a + +*/ + +/*jslint indent: 2, maxlen: 90, browser: true */ +var autocomplete = function (id, values, callback) { + "use strict"; + var autoCompleteTimer, + stopDefaultAction, + getPosition, + field, + initAutoComplete, + keydownAutoComplete, + generateDropdown, + autoComplete, + mouseoverDropdown, + mouseoutDropdown, + mousedownDropdown, + assignMouseListeners, + blurAutoComplete, + closeDropdown; + + stopDefaultAction = function (event) { + event.returnValue = false; + if (typeof event.preventDefault !== "undefined") { + event.preventDefault(); + } + return true; + }; + + getPosition = function (theElement) { + var positionX = 0, + positionY = 0; + while (theElement !== null) { + positionX += theElement.offsetLeft; + positionY += theElement.offsetTop; + theElement = theElement.offsetParent; + } + return [positionX, positionY]; + }; + + initAutoComplete = function () { + field = document.getElementById(id); + field.setAttribute("autocomplete", "off"); + if (window.attachEvent) { + field.attachEvent("onkeydown", keydownAutoComplete); + } else { + field.addEventListener("keydown", keydownAutoComplete); + } + field.onblur = function () { + blurAutoComplete(); + }; + }; + + keydownAutoComplete = function (event) { + var target = event.target || event.srcElement, + autoCompleteDropdown, + childLis, + selected, + i, + max, + inputRanges; + if (typeof event === "undefined") { + event = window.event; + } + switch (event.keyCode) { + case 9: // tab + case 13: // enter + case 16: // shift + case 17: // ctrl + case 18: // alt + case 20: // caps lock + case 27: // esc + case 33: // page up + case 34: // page down + case 35: // end + case 36: // home + case 37: // left arrow + case 39: // right arrow + break; + case 38: // up arrow + autoCompleteDropdown = document.getElementById("autoCompleteDropdown"); + if (autoCompleteDropdown !== null) { + childLis = autoCompleteDropdown.childNodes; + selected = false; + for (i = 0, max = childLis.length; i < max; i += 1) { + if (childLis[i].className === "hover") { + selected = true; + if (i > 0) { + childLis[i].className = ""; + childLis[i - 1].className = "hover"; + target.value = childLis[i - 1].firstChild.nodeValue; + } + break; + } + } + if (!selected) { + childLis[0].className = "hover"; + target.value = childLis[0].firstChild.nodeValue; + } + } + stopDefaultAction(event); + break; + case 40: // down arrow + autoCompleteDropdown = document.getElementById("autoCompleteDropdown"); + if (autoCompleteDropdown !== null) { + childLis = autoCompleteDropdown.childNodes; + selected = false; + for (i = 0, max = childLis.length; i < max; i += 1) { + if (childLis[i].className === "hover") { + selected = true; + + if (i < childLis.length - 1) { + childLis[i].className = ""; + childLis[i + 1].className = "hover"; + target.value = childLis[i + 1].firstChild.nodeValue; + } + break; + } + } + if (!selected) { + childLis[0].className = "hover"; + target.value = childLis[0].firstChild.nodeValue; + } + } + stopDefaultAction(event); + break; + case 8: // backspace + case 46: // delete + if (typeof autoCompleteTimer !== "undefined") { + clearTimeout(autoCompleteTimer); + } + autoCompleteTimer = setTimeout(function () { + generateDropdown(false); + }, 500); + break; + default: + if (typeof autoCompleteTimer !== "undefined") { + clearTimeout(autoCompleteTimer); + } + target = event.target || event.srcElement; + inputRanges = "false"; + if (typeof target.createTextRange !== "undefined" + || typeof target.setSelectionRange !== "undefined") { + inputRanges = "true"; + } + autoCompleteTimer = setTimeout(function () { + generateDropdown(inputRanges); + }, 500); + } + return true; + }; + + assignMouseListeners = function (e) { + e.onmouseover = function () { + mouseoverDropdown(this); + }; + e.mouseout = function () { + mouseoutDropdown(this); + }; + e.mousedown = function () { + mousedownDropdown(this); + }; + }; + + generateDropdown = function (doAutoComplete) { + closeDropdown(); + var input = document.getElementById(id), + newUl = document.createElement("div"), + newLi, + i, + max = values.length; + newUl.setAttribute("id", "autoCompleteDropdown"); + newUl.autoCompleteInput = input; + newUl.style.position = "absolute"; + newUl.style.left = getPosition(input)[0] + "px"; + newUl.style.top = getPosition(input)[1] + input.offsetHeight - 2 + "px"; + newUl.style.width = input.offsetWidth - 3 + "px"; + for (i = 0; i < max; i += 1) { + if (values[i].indexOf(input.value) === 0) { + newLi = document.createElement("button"); + newLi.innerHTML = values[i]; + assignMouseListeners(newLi); + newUl.appendChild(newLi); + } + } + if (newUl.firstChild !== null) { + document.getElementsByTagName("body")[0].appendChild(newUl); + } + if (typeof doAutoComplete !== "undefined" && doAutoComplete) { + autoComplete(); + } + return true; + }; + + autoComplete = function () { + var input = document.getElementById(id), + cursorMidway = false, + range, + originalValue, + autoCompleteDropdown; + if (typeof document.selection !== "undefined") { + range = document.selection.createRange(); + if (range.move("character", 1) !== 0) { + cursorMidway = true; + } + } else if (typeof input.selectionStart !== "undefined" + && input.selectionStart < input.value.length) { + cursorMidway = true; + } + originalValue = input.value; + autoCompleteDropdown = document.getElementById("autoCompleteDropdown"); + if (autoCompleteDropdown !== null && !cursorMidway) { + autoCompleteDropdown.firstChild.className = "hover"; + input.value = autoCompleteDropdown.firstChild.firstChild.nodeValue; + if (typeof input.createTextRange !== "undefined") { + range = input.createTextRange(); + range.moveStart("character", originalValue.length); + range.select(); + } else if (typeof input.setSelectionRange !== "undefined") { + input.setSelectionRange(originalValue.length, input.value.length); + } + if (autoCompleteDropdown.childNodes.length === 1) { + setTimeout(function () { + closeDropdown(); + }, 10); + } + } + return true; + }; + + mouseoverDropdown = function (target) { + var childLis, i, max; + while (target.nodeName.toLowerCase() !== "button") { + target = target.parentNode; + } + childLis = target.parentNode.childNodes; + max = childLis.length; + for (i = 0; i < max; i += 1) { + childLis[i].className = ""; + } + target.className = "hover"; + return true; + }; + + mouseoutDropdown = function (target) { + while (target.nodeName.toLowerCase() !== "button") { + target = target.parentNode; + } + target.className = ""; + return true; + }; + + mousedownDropdown = function (target) { + while (target.nodeName.toLowerCase() !== "button") { + target = target.parentNode; + } + target.parentNode.autoCompleteInput.value = target.firstChild.nodeValue; + closeDropdown(); + return true; + }; + + blurAutoComplete = function () { + if (typeof autoCompleteTimer !== "undefined") { + clearTimeout(autoCompleteTimer); + } + closeDropdown(); + callback(field.value); + return true; + }; + + closeDropdown = function () { + var autoCompleteDropdown = document.getElementById("autoCompleteDropdown"); + if (autoCompleteDropdown !== null) { + autoCompleteDropdown.parentNode.removeChild(autoCompleteDropdown); + } + return true; + }; + initAutoComplete(); +}; diff --git a/2012.js.get-week.js b/2012.js.get-week.js new file mode 100644 index 0000000..5658648 --- /dev/null +++ b/2012.js.get-week.js @@ -0,0 +1,8 @@ +/** + * getWeek function, return current week number from a `Date` object. + */ + getWeek = function (date) { + var onejan = new Date(date.getFullYear(), 0, 1); + return Math.ceil((((date - onejan) / 86400000) + + onejan.getDay()) / 7); + }; diff --git a/2012.js.match-media-conditional-exec.js b/2012.js.match-media-conditional-exec.js new file mode 100644 index 0000000..b506a80 --- /dev/null +++ b/2012.js.match-media-conditional-exec.js @@ -0,0 +1,97 @@ +/*jslint devel:true, browser:true, indent:2, maxlen: 70 */ +(function (window) { + "use strict"; + var Condition, Listener, watcher; + + // Requires window.matchMedia: + // https://developer.mozilla.org/en/DOM/window.matchMedia + if (typeof window.matchMedia === "undefined") { return; } + + Condition = function (mq, callback) { + this.mq = window.matchMedia(mq); + this.callback = function () { + callback.call(); + }; + }; + Condition.prototype.matches = function () { + return this.mq.matches; + }; + + Listener = function () { + this.callbacks = []; + this.cbLen = 0; + this.runned = 0; + }; + Listener.prototype.waitFor = function (condition) { + this.callbacks.push(condition); + this.cbLen += 1; + }; + Listener.prototype.walk = function () { + var toRun = [], + i = this.cbLen, + cb = this.callbacks; + + while (i) { + i -= 1; + if (cb[i]) { + if (cb[i].matches()) { + toRun.push(i); + } + } + } + + return toRun; + }; + Listener.prototype.execute = function (toRun) { + var i = toRun.length; + + while (i) { + i -= 1; + this.callbacks[toRun[i]].callback.call(); + this.callbacks[toRun[i]] = ''; + this.runned += 1; + } + }; + Listener.prototype.investigate = function () { + var toRun; + toRun = this.walk(); + + if (toRun.length) { this.execute(toRun); } + + if (this.runned === this.cbLen) { + window.onresize = ""; + } + }; + Listener.prototype.install = function () { + var callback, wait, that = this; + + callback = function () { + that.investigate(); + }; + + window.onresize = function () { + clearTimeout(wait); + wait = setTimeout(callback, 333); + }; + + this.investigate(); + }; + + watcher = new Listener(); + + watcher.waitFor(new Condition( + "all and (min-width: 801px)", + function () { + console.log("load delicicous bookmarks (we have room!)"); + } + )); + + watcher.waitFor(new Condition( + "all and (min-width: 1281px)", + function () { + console.log("load heavy pictures (we are on desktop!)"); + } + )); + + watcher.install(); +}(window)); diff --git a/2012.js.sprintf.js b/2012.js.sprintf.js new file mode 100644 index 0000000..2c770c6 --- /dev/null +++ b/2012.js.sprintf.js @@ -0,0 +1,10 @@ +/* +Thank you, almighty! +http://www.nczonline.net/blog/2011/10/11/simple-maintainable-templating-with-javascript/ +*/ +function sprintf(text){ + var i=1, args=arguments; + return text.replace(/%s/g, function(pattern){ + return (i < args.length) ? args[i++] : ""; + }); +} diff --git a/2012.python.download-hmtl-page-behind-login.py b/2012.python.download-hmtl-page-behind-login.py new file mode 100644 index 0000000..a834f65 --- /dev/null +++ b/2012.python.download-hmtl-page-behind-login.py @@ -0,0 +1,50 @@ +''' +Example htmldump_config.py: + + url = "http://localhost:5000" + login = "admin@adeprimo.se" + password = "app161770" + + def pages_to_validate(): + pages = [ + # event registration + ('event-start', '/events/start/'), + ('event-form', '/events/create'), + ('events', '/events'), + ('event-edit', '/events/view/'), + + return pages +''' +import mechanize +import re +from htmldump_config import pages_to_validate, url, login, password + +url = url + "%s" +filepattern = "../static/htmldumps/%s.html" + +print "creating fake browser env" +br = mechanize.Browser() +print "--- done" +print "logging in as admin" +br.open(url % "/login") +br.select_form(nr=0) +br["email"] = login +br["password"] = password +br.submit() +print "--- now logged in as %s" % login + +def htmldump(name, doc): + with open(filepattern % name, "w") as f: + f.write(doc) + f.close() + +def begin_download(pages): + for filename, url_path in pages: + print "downloading: %s" % url_path + print " to: %s.html" % filename + response = br.open(url % url_path) + htmldump(filename, response.read()) + +pages = pages_to_validate() + +begin_download(pages) diff --git a/2012.scss.column-separator.scss b/2012.scss.column-separator.scss new file mode 100644 index 0000000..dc8b017 --- /dev/null +++ b/2012.scss.column-separator.scss @@ -0,0 +1,14 @@ +/* vertical ruler using pseudo-elements */ +@mixin columnseparator { + @extend .relative; + + &:before { + content: ''; + position: absolute; + top: 0; + width: 3px; + background: #eee; + left: 681px; + bottom: 0; + } +} diff --git a/2012.scss.folded-corner.scss b/2012.scss.folded-corner.scss new file mode 100644 index 0000000..ea2853e --- /dev/null +++ b/2012.scss.folded-corner.scss @@ -0,0 +1,18 @@ +// folded box +@mixin folded ($size: 10px, $y: 30px, $background: $grey4) { + line-height: $y; + height: $y; + margin-left: ($size * -1); + text-indent: $size; + position: relative; + + &:before { + border-left: $size solid $pagebg; + border-top: $size solid darken($background, 80%); + float: left; + content: ''; + position: absolute; + bottom: ($size * -1); + left: 0; + } +} diff --git a/2013.bash.new-mamp-vhost.sh b/2013.bash.new-mamp-vhost.sh new file mode 100644 index 0000000..181d864 --- /dev/null +++ b/2013.bash.new-mamp-vhost.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# +# usage: ./adp-new-vhost +# +mampdir=/Applications/MAMP +confdir=$mampdir/conf/apache/extra/vhosts +codedir=~/Code +mampport=80 + +cat < $confdir/$1.conf + + ServerName $1 + DocumentRoot $codedir/$1 + + + AllowOverride all + Options -Indexes + Order allow,deny + Allow from all + + +end + +echo | sudo -- sh -c "echo '127.0.0.1 $1' >> /etc/hosts" diff --git a/2013.python.embed-png-in-css-as-datauri.py b/2013.python.embed-png-in-css-as-datauri.py new file mode 100644 index 0000000..1f515bb --- /dev/null +++ b/2013.python.embed-png-in-css-as-datauri.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +import base64, re, argparse + +parser = argparse.ArgumentParser(description='CSS-Embed PNG images as data-URIs') +parser.add_argument('files', metavar='file', nargs="+", type=str, + help='path to a css file') + +img = re.compile("url\('?\"?(.*\.png)'?\"?\)") +repl = "url(data:image/png;base64,%s)" + +cssfiles = parser.parse_args().files + +if "swapcase" in dir(["a"]): + cssfiles = [cssfiles] + +for cssfile in cssfiles: + css = open(cssfile, "r").read() + + for image_path in img.findall(css): + data = base64.b64encode(open(image_path, "r").read()) + pattern = "url\('?\"?%s'?\"?\)" % image_path + css = re.sub(re.compile(pattern), repl % data, css) + + f = open(cssfile, "w") + f.write(css) + f.close() diff --git a/2013.ruby.ftp-upload.rb b/2013.ruby.ftp-upload.rb new file mode 100644 index 0000000..04bf5b0 --- /dev/null +++ b/2013.ruby.ftp-upload.rb @@ -0,0 +1,100 @@ +require "rubygems" +require 'net/ftp' +require 'stringio' + +ftp = "" +username = "" +passwd = "" +root_dir = "../../../../" +current_rev_file = root_dir + "__CURRENT_VERSION__" + +root_dir = "" + +# new rev is either HEAD or a commit, set by ARGV +new_rev = "" +new_rev = ARGV.first unless ARGV.first != nil +cmd_newrev = "git show -s --format=\"%%h %%s (%%ar)\" %s" % new_rev +new_rev = `#{cmd_newrev}` + +# old rev (current in production) are stored in a file. +old_rev = File.open(current_rev_file, "r").read + +cmd = "git diff --name-status %s %s" % [old_rev.split(" ").first, new_rev.split(" ").first] + +files = `#{cmd}` + +# change to root +Dir.chdir root_dir + +class Net::FTP + def puttextcontent(content, remotefile, &block) + f = StringIO.new(content) + begin + storlines("STOR " + remotefile, f, &block) + ensure + f.close + end + end +end + +def chkdir(ftp, parents) + path = [] + + parents.each do |d| + parent_path = path.join("/") + path << d + unless ftp.list(parent_path).any? { |dir| dir.match(d) } + ftp.mkdir(path.join("/")) + puts "[chkdir] created dir: %s" % path.join("/") + end + end +end + +def put_file(ftp, path) + parents = path.split("/") + parents.pop + + chkdir ftp, parents + + if path.match(".png") + ftp.putbinaryfile(path, path) + else + ftp.puttextfile(path, path) + end + + puts "[put_file] %s" % path +end + +puts "\nCurrent rev in Production: %s" % old_rev +puts " The new rev: %s\n" % new_rev + +if old_rev == new_rev + puts "Everything is up to date, exiting" + exit +end + +# list files to be changed in ftp prod +puts "files changed:", files, "" + +# connect to ftp server +ftp = Net::FTP.new(ftp) +ftp.login username, passwd +remote = ftp.chdir root_dir + +# make changes +files.each_line do |file| + flag, file = file.split("\t") + file.strip! + + if flag == "A" || flag == "M" + put_file ftp, file + end + + if flag == "D" + ftp.delete file + end +end + +File.open(current_rev_file, "w").write(new_rev) + +ftp.close diff --git a/2013.ruby.html-validator.rb b/2013.ruby.html-validator.rb new file mode 100644 index 0000000..f3da16f --- /dev/null +++ b/2013.ruby.html-validator.rb @@ -0,0 +1,40 @@ +require "rubygems" +require 'living-validator' +require 'uri' + +url = ARGV.first +files = ARGV.slice(1, 9999) +stop_at = files.length +i = 1 + +def colorize(text, color_code) + "\e[#{color_code}m#{text}\e[0m" +end + +def red(text); colorize(text, 31); end +def green(text); colorize(text, 32); end + +valid = true + +files.each do |file| + puts '[%d/%d] %s%s:' % [i, stop_at, url, file] + i += 1 + result = LivingValidator::Validator.check '%s%s' % [url, file] + unless result == false + if result.errorCount > 0 + result.errors.each do |error| + puts red("%d:%d %s" % [error["lastLine"], error["lastColumn"], error["message"]]) + end + + valid = false + else + puts green "valid" + end + else + puts red("FAILED") + end +end + +exit 1 unless valid == true + +exit 0 diff --git a/2014.js.geolocation-pos2px.html b/2014.js.geolocation-pos2px.html new file mode 100644 index 0000000..1510faf --- /dev/null +++ b/2014.js.geolocation-pos2px.html @@ -0,0 +1,94 @@ + + + + + + diff --git a/2015.grunt.htmldev-checklist.js b/2015.grunt.htmldev-checklist.js new file mode 100644 index 0000000..7db67d3 --- /dev/null +++ b/2015.grunt.htmldev-checklist.js @@ -0,0 +1,143 @@ +/* +The Following folder structure is required: + +./ + ./dist + app.js + app.min.js + app.min.zipped.js + design.css + design.min.css + design.min.zipped.css + ./src + ./src/js + .src/js/core + .src/js/modules + init.js + design.less + ./styleguide + ./styleguide/template + index.html + section-1.html + section-2.html + section-3.html + section-5.html +*/ +module.exports = function(grunt) { + + // Project configuration. + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + csslint: { + strict: { + src: ['dist/design.css'] + } + }, + compress: { + main: { + options: { + mode: 'gzip' + }, + files: [ + { + expand: true, + src: ['dist/app.min.js'], + dest: '', + ext: '.min.zipped.js' + }, + { + expand: true, + src: ['dist/design.min.css'], + dest: '', + ext: '.min.zipped.css' + } + ] + } + }, + concat: { + dist: { + src: ['src/js/core/*.js', 'src/js/modules/*.js', 'src/js/init.js'], + dest: 'dist/app.js', + }, + }, + devserver: { + "options": { + "port": 8888 + }, + server: {} + }, + htmllint: { + all: ["styleguide/*.html"] + }, + jasmine: { + pivotal: { + src: ['src/js/*/*.js'], + options: { + specs: 'spec/*Spec.js', + helpers: 'spec/*Helper.js' + } + } + }, + jslint: { + client: { + src: [ + 'src/js/*/*.js', + 'src/js/*.js' + ], + exclude: [], + } + }, + kss: { + options: { + template: 'styleguide/template', // create this manually: `kss-node -i styleguide/template` + includeType: 'less' + }, + files: { + src: ['src/less'], + dest: 'styleguide' + } + }, + less: { + development: { + files: { + "dist": "src/less" + }, + files: { + "dist/design.css": "src/less/design.less" + } + }, + production: { + options: { + cleancss: true + }, + files: { + "dist/design.min.css": "src/less/design.less" + } + } + }, + uglify: { + options: { + banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' + }, + build: { + src: 'dist/app.js', + dest: 'dist/app.min.js' + } + } + }); + + grunt.loadNpmTasks('grunt-contrib-compress'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-csslint'); + grunt.loadNpmTasks('grunt-contrib-jasmine'); + grunt.loadNpmTasks('grunt-contrib-less'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-devserver'); + grunt.loadNpmTasks('grunt-html'); + grunt.loadNpmTasks('grunt-jslint'); + grunt.loadNpmTasks('grunt-kss'); + + grunt.registerTask('deploy', ['less:production', 'less:development', 'concat', 'uglify', 'compress', 'kss']); + grunt.registerTask('test', ['htmllint', 'csslint', 'jslint', 'jasmine']); + grunt.registerTask('default', ['test']); +}; diff --git a/2015.js.infinite-scroll.js b/2015.js.infinite-scroll.js new file mode 100644 index 0000000..2693e33 --- /dev/null +++ b/2015.js.infinite-scroll.js @@ -0,0 +1,66 @@ +/*jslint browser: true, indent: 4 */ +/* +Example use of ScrollGuard: a simple, unobtrusive lazy loader. + +Each time the user scrolled to the bottom of the page: + + 1. Look if there is content to load (lookForNextPage) + 2. If new content, get content using Ajax (getNextPage) + 3. Append the new content and do some polish (appencContent) + +*/ +(function (g) { + "use strict"; + var lookForNextPage, + getNextPage, + appendContent; + + if (g.ScrollGuard === undefined) { + throw new Error("scrollguard.js is required!"); + } + + appendContent = function (content) { + var body, rows, i, max, newContent, table; + + body = document.createElement("div"); + body.innerHTML = content; + + newContent = document.createDocumentFragment(); + rows = body.querySelectorAll(".main-c table tr"); + + for (i = 1, max = rows.length; i < max; i += 1) { + newContent.appendChild(rows[i]); + } + + table = document.querySelector(".main-c table").tBodies[0]; + table.replaceChild(newContent, table.querySelector("tr.pagination")); + }; + + lookForNextPage = function () { + var nextPage = document.getElementById("pager-next"); + + if (nextPage) { + getNextPage(nextPage.href); + } + }; + + getNextPage = function (href) { + var request = new XMLHttpRequest(); + + request.onreadystatechange = function () { + if (request.readyState === 4) { + if (request.status === 200 || request.status === 304) { + appendContent(request.responseText); + } + } + }; + + request.open("GET", href, true); + request.send(null); + }; + + if (document.getElementById("pager-next")) { + g.ScrollGuard.add(lookForNextPage, -1); + g.ScrollGuard.start(); + } +}(this)); diff --git a/2015.js.pirate-speech.js b/2015.js.pirate-speech.js new file mode 100644 index 0000000..a322d8f --- /dev/null +++ b/2015.js.pirate-speech.js @@ -0,0 +1,25 @@ +/* Kopiera och klistra in i Firebug eller Dev Tools, ändra sista raden. */ +(function (str) { + "use strict"; + var blacklist = ' aeiouyöäåAEIOUYÖÄÅ'; + + function ify(c) { + return c + "o" + c.toLowerCase(); + } + + function pirate(str) { + var chars = str.split(""), + i = chars.length; + + while (i) { + i -= 1; + if (!blacklist.match(chars[i])) { + chars[i] = ify(chars[i]); + } + } + + return chars.join(""); + } + + return pirate(str); +}("Det här ska jag säga")); diff --git a/2015.js.responsive-loader.js b/2015.js.responsive-loader.js new file mode 100644 index 0000000..7173704 --- /dev/null +++ b/2015.js.responsive-loader.js @@ -0,0 +1,113 @@ +/*jslint browser: true, indent: 4 */ +/*global updateCanvas: true */ +(function (g) { + "use strict"; + var lookForNextPage, + getNextPage, + appendContent, + loadManually, + manual, + loader, + working; + + if (g.ScrollGuard === undefined) { + throw new Error("scrollguard.js is required!"); + } + + appendContent = function (content) { + var body, rows, i, max, newContent, table; + body = document.createElement("div"); + body.innerHTML = content; + + newContent = document.createDocumentFragment(); + rows = body.querySelectorAll(".main-c table tr"); + + for (i = 1, max = rows.length; i < max; i += 1) { + newContent.appendChild(rows[i]); + } + + table = document.querySelector(".main-c table").tBodies[0]; + table.replaceChild(newContent, table.querySelector("tr.pagination")); + + if (typeof(updateCanvas) === "function") { + updateCanvas(); + } + + loadManually(); + }; + + loadManually = function () { + var button; + + button = document.getElementById("load-manually"); + + if (button) { + if (!document.getElementById("pager-next")) { + button.parentNode.removeChild(button); + } + + button.onclick = function () { + var nextPage = document.getElementById("pager-next"); + + if (nextPage) { + getNextPage(nextPage.href); + button.parentNode.replaceChild(loader.cloneNode(), button); + } + }; + } + }; + + manual = function () { + var style, elm; + + elm = document.getElementById("load-manually"); + + if (!elm) { return false; } + + style = window.getComputedStyle(elm, null); + return style.getPropertyValue("display") !== "none"; + }; + + lookForNextPage = function () { + if (!manual()) { + var nextPage = document.getElementById("pager-next"); + + if (nextPage) { + getNextPage(nextPage.href); + } + } + }; + + getNextPage = function (href) { + var request = new XMLHttpRequest(); + + if (working === true) { + return; + } + + working = true; + + request.onreadystatechange = function () { + if (request.readyState === 4) { + working = false; + if (request.status === 200 || request.status === 304) { + appendContent(request.responseText); + } + } + }; + + request.open("GET", href, true); + request.send(null); + }; + + loadManually(); + + loader = document.createElement("img"); + loader.style.paddingTop = "14px"; + loader.src = "/templates/wide/css/images/loader.gif"; + + if (document.getElementById("pager-next")) { + g.ScrollGuard.add(lookForNextPage, -1); + g.ScrollGuard.start(); + } +}(this)); diff --git a/2015.js.scrollguard.js b/2015.js.scrollguard.js new file mode 100644 index 0000000..7cc2765 --- /dev/null +++ b/2015.js.scrollguard.js @@ -0,0 +1,116 @@ +/*jslint browser: true, indent: 4 */ +(function (g) { + "use strict"; + var ScrollGuard, + started = false, + lock = false, + timer, + breakpoints = [], + getDocHeight, + getScrollTop; + + // found at http://james.padolsey.com/javascript/ + // get-document-height-cross-browser/ + getDocHeight = function () { + return Math.max( + Math.max(document.body.scrollHeight, + document.documentElement.scrollHeight), + Math.max(document.body.offsetHeight, + document.documentElement.offsetHeight), + Math.max(document.body.clientHeight, + document.documentElement.clientHeight) + ); + }; + + // found at http://stackoverflow.com/questions/871399/ + // cross-browser-method-for-detecting-the-scrolltop-of-the-browser-g + getScrollTop = function () { + var b, d; + if (g.pageYOffset !== undefined) { + return g.pageYOffset; + } + + b = document.body; + d = document.documentElement; + + d = (d.clientHeight) ? d : b; + + return d.scrollTop; + }; + + ScrollGuard = { + // from: a distance from top, given in pixels (required) + // to: a distance from top, given in pixels (optional) + // + // special cases: + // from = -1 will instead look if bottom of page + // is visible. + add: function (callback, from, to) { + var range; + + if (from === -1) { + range = function (topPos) { + return getDocHeight() - topPos <= g.innerHeight; + }; + } else { + if (to === undefined) { + range = function (topPos) { + return topPos >= from; + }; + } else { + range = function (topPos) { + return topPos >= from && topPos <= to; + }; + } + } + + breakpoints.push({ + within: range, + callback: callback + }); + }, + fire: function (pos) { + var max = breakpoints.length, + i, + bp; + + lock = true; + + for (i = 0; i < max; i += 1) { + bp = breakpoints[i]; + + if (bp.within(pos)) { + bp.callback(); + } + } + + lock = false; + }, + start: function () { + if (started) { return; } + + var callback = function () { + if (lock === false) { + clearTimeout(timer); + + timer = setTimeout(function () { + ScrollGuard.fire(getScrollTop()); + }, 100); + } + }; + + if (g.attachEvent) { + g.attachEvent('onscroll', callback); + } else { + g.addEventListener('scroll', callback, false); + } + + started = true; + + // initial call to catch initial scroll positions + callback(); + } + }; + + g.ScrollGuard = ScrollGuard; +}(this)); diff --git a/2015.php.view-engine.php b/2015.php.view-engine.php new file mode 100644 index 0000000..eefb0ca --- /dev/null +++ b/2015.php.view-engine.php @@ -0,0 +1,61 @@ +file = $file_path; + } + } + + /** + * Compile the view + * + * @return $HTMLSource the compiled view + */ + public function compile($registry = false) { + if($registry === false){ + $registry =& registry::getInstance(); + } + $user =& user::getInstance(); + foreach($registry as $k=>$v){ + $$k = $v; + } + ob_start(); + require $this->file; + $body = ob_get_clean(); + + (isset($contentType)) ? http_response::content($contentType) : http_response::content(CONTENT_TYPE); + + $isFragment = isset($this->isFragment); + + if(defined('LAYOUT') && !$isFragment){ + ob_start(); + require LAYOUT; + $body = ob_get_clean(); + } + return $body; + } + + public function isFragment() { + $this->isFragment = true; + } + + function __toString() { + return __CLASS__; + } +} diff --git a/2015.python.color-contrast-foreground-color.py b/2015.python.color-contrast-foreground-color.py new file mode 100644 index 0000000..e60802f --- /dev/null +++ b/2015.python.color-contrast-foreground-color.py @@ -0,0 +1,19 @@ +''' +based on: http://24ways.org/2010/calculating-color-contrast/ +accepts hex colors with 3 or 6 chars. hash-char is optional. +''' +def get_contrast_yiq(hex_color): + hex_color = hex_color.replace("#", "") + + if len(hex_color) == 3: + hex_color = "".join([c + c for c in hex_color]) + + r = int(hex_color[0:2], 16) + g = int(hex_color[2:4], 16) + b = int(hex_color[4:6], 16) + + yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000 + + if yiq >= 128: + return 'black' + return 'white' diff --git a/2016.less.boilerplate.less b/2016.less.boilerplate.less new file mode 100644 index 0000000..6d6c765 --- /dev/null +++ b/2016.less.boilerplate.less @@ -0,0 +1,293 @@ +/*csslint box-model: false, box-sizing: false, universal-selector: false */ +/* + + + + All projects + should have nice + ASCII ART! + + http://patorjk.com/software/taag/ + + + + +*/ + +@bg: #fff; +@fg: #000; + +.transform(@value) { + -webkit-transform: @value; + -moz-transform: @value; + -o-transform: @value; + -ms-transform: @value; + transform: @value; +} + +/* ========================================================================== + Base + ========================================================================== */ + +/* +Base + +Normalisering av HTML-element, t ex inmatningsfält, knappar och länkar. Detta +avsnitt bör innehålla element- och attributselektorer som är enkla att stila +med hjälp av klasser. Klasser ska undvikas. + +Låna så mycket som möjligt från +[normalise.css](https://github.com/necolas/normalize.css/blob/master/normalize.css). + +Bastypografin sätts till *large* (ca 24px) med 1.25 radhöjd. Fonten **Open Sans** bäddas in från Google Web Fonts. +Fonten ändras med hjälp av media queries (blir mindre på små viewports). + +Markup: +Normaliserad text som fyller ut en hel rad genom att +fler ord fylls på i det här stycket, även länk läggs in för att visa hur den ser ut. +
Detta är ett hårt radbryt. + +Styleguide 1 +*/ + +*, :before, :after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html { + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + + background: @bg; + color: @fg; + font: normal large/1.25 sans-serif; +} + +body, ul, p { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; + border: 0; +} + +legend { + display: none; +} + +a { + text-decoration: none; + color: inherit; +} + +a[href]:not([class]) { + color: blue; + text-decoration: underline; +} + +em { + font-style: normal; +} + +button, +input[type=submit] { + .user-select(text); + + border: 0; + font: inherit; + line-height: 1; + cursor: pointer; +} + +button::-moz-focus-inner, +input[type=submit]::-moz-focus-inner { + padding: 0; + border: 0; +} + +input { + border: 0; + background: #fff; + padding: 0; + margin: 0; + line-height: 1; + font-size: inherit; +} + +/* ========================================================================== + Template + ========================================================================== */ + +/* +Mall + +Innehåller kolumner, ett system för grids och containers fö övriga +designkomponenter. Detta avsnitt ska i strikt mening innehålla endast klasser. + +Styleguide 2 +*/ + +/* Float Helpers + ========================================================================== */ + +/* +Layouthjälpare (floats) + +Klasser som främst hanterar floats. Är i dagsläget ej anpassade till +följsam layout. + +.pull-left - gör ett objekt float: left; +.pull-right - gör ett objekt float: right; +.line - får en behållare att rymma objekt med floats +.clear - får en behållare eller objekt att rensa floats + +Markup: +
+
Text
+
+ +Styleguide 2.1 +*/ + +.pull-left, .unit { + float: left; +} + +.pull-right { + float: right; +} + +.line:after { + content: ""; + display: block; + clear: both; +} + +.clear { + clear: both; +} + + +/* Columns + ========================================================================== */ + +.page { +} + + +/* Grid + ========================================================================== */ + +/* +Grid + +Griden är baserad på YUI grids och är i skrivandets stund ej anpassad för +följsam layout. + +Markup: +
+
1/2
+
1/2
+
+
+
1/3
+
2/3
+
+
+
1/4
+
3/4
+
+
+
1/5
+
4/5
+
3/5
+
2/5
+
+ +Styleguide 2.2 +*/ + +.unit { + list-style: none; + padding: 0; + margin: 0; +} + +.size1of5 { width: 20% } +.size1of4 { width: 25% } +.size1of3 { width: 33.3333% } +.size2of5 { width: 40% } +.size1of2 { width: 50% } +.size3of5 { width: 60% } +.size2of3 { width: 66.6666% } +.size3of4 { width: 75% } +.size4of5 { width: 80% } +.size1of7 { width: 14.2857% } + + +/* ========================================================================== + Core modules + ========================================================================== */ + +/* +Kärnmoduler + +Moduler som andra moduler är beroende av för att fungera korrekt, vilket gör +att dessa behöver komma först i The Cascade. + +Detta avsnitt ska endast innehålla klasser och media queries, med få undantag. + +Styleguide 3 +*/ + +/* nav pattern */ +.nav { list-style: none; padding: 0;} +.nav a, .nav strong { display: block; } + + +/* Headings + ========================================================================== */ + +/* +Rubriker + +Rubriker används för att skapa sektioner och avdelningar i designen. + +.big - Överdrivet stor rubrik +.h1 - Dominant one-of-a-kind rubrik +.h2 - typisk sektionsrubrik + +Markup: +
En rubrik
med ett radbryt
+ +Styleguide 3.2 +*/ + +h1, .h1 { + font-weight: normal; + text-align: center; + font-size: 1.66em; +} + + +/* ========================================================================== + Modules + ========================================================================== */ + +/* +Moduler + +Modulerna i detta asvnitt är isolerade och beror ej på andra moduler som ej är +kärnmoduler, detta för att enkelt kunna slå av och på CSS. Normalt sett så +är modulerna utökningar av kärnmoduler och behållare. + +Avsnittet ska enbart innehålla klasser och media queries, med få undantag. + +Styleguide 4 +*/ + + diff --git a/2019.python.podcast-downloader.py b/2019.python.podcast-downloader.py new file mode 100644 index 0000000..bb1812d --- /dev/null +++ b/2019.python.podcast-downloader.py @@ -0,0 +1,92 @@ +""" +Podcast backup script + +Parses an RSS feed from SOURCE_FILE and download all items to +DESTINATION_PATH. Downloads are done in parallel, for +PARALLEL_COUNT downloads at the time. + +How to use +---------- + + 1. Set DESTINATION_PATH. Make sure the folder exists on your + file system. + 2. Save the source file (RSS or Atom) on your computer and + update SOURCE_FILE if needed. + 3. Alter PARALLEL_COUNT for your needs. Higher number will + decrease total time for this script to be done, but will + increase net traffic. + 4. Run script in a python intepreter, 3.7 is recommended. + +This script was written by Anders Ytterström in October 2019. +If you find it useful, buy him a 🍺. +""" +import queue +import threading +import xml.etree.ElementTree as ET +from urllib.request import urlretrieve + +DESTINATION_PATH = "D:\Asmodean\podcasts\inbox" +SOURCE_FILE = "D:\Kod\gists\src.xml" +PARALLEL_COUNT = 3 + + +def download_file(url, target): + print(f"Downloading {target} <- {url}") + urlretrieve(url, f"{DESTINATION_PATH}\{target}.mp3") + + +def get_urls(): + tree = ET.parse(SOURCE_FILE) + root = tree.getroot() + + def f(item): + url = item.find("enclosure").attrib["url"] + filename = slugify(item.find("title").text) + return (url, filename) + + return map(f, root.findall("./channel/item")) + + +def slugify(text): + return ( + text.lower() + .replace(" ", "-") + .replace(":", "") + .replace("/", "-av-") + .replace("?", "") + ) + + +def do_work(item): + download_file(*item) + + +if __name__ == "__main__": + + def worker(): + while True: + item = q.get() + if item is None: + break + do_work(item) + q.task_done() + + q = queue.Queue() + threads = [] + for i in range(PARALLEL_COUNT): + t = threading.Thread(target=worker) + t.start() + threads.append(t) + + source = get_urls() + for item in source: + q.put(item) + + # block until all tasks are done + q.join() + + # stop workers + for i in range(PARALLEL_COUNT): + q.put(None) + for t in threads: + t.join() diff --git a/2020.css.shame.css b/2020.css.shame.css new file mode 100644 index 0000000..0ff135e --- /dev/null +++ b/2020.css.shame.css @@ -0,0 +1,71 @@ +acronym, +applet, +basefont, +big, +center, +dir, +font, +isindex, +menu, +noframes, +s, +strike, +tt, +u { + color: red !important; + font-weight: bold !important; +} + +[rev], +[charset], +[shape], +[coords], +[longdesc], +link[target] +[nohref] +img[name] +archive object +classid object +codebase object +codetype object +declare object +standby object +valuetype param +type param +axis td and t +abbr td and t +scope td +align caption, iframe, img, input, object, legend, table, hr, div, h1, h2, h3, h4, h5, h6, p, col, colgroup, tbody, td, tfoot, th, thead and tr. +alink body +link body +vlink body +text body +background body +bgcolor table, tr, td, th and body. +border table and object. +cellpadding table +cellspacing table +char col, colgroup, tbody, td, tfoot, th, thead and tr. +charoff col, colgroup, tbody, td, tfoot, th, thead and tr. +clear br +compact dl, menu, ol and ul. +frame table +compact dl, menu, ol and ul. +frame table +frameborder iframe +hspace img and object. +vspace img and object. +marginheight iframe +marginwidth iframe +noshade hr +nowrap td and th +rules table +scrolling iframe +size hr +type li, ol and ul. +valign col, colgroup, tbody, td, tfoot, th, thead and tr +width hr, table, td, th, col, colgroup and pre. + +br + br { + display: none; +} diff --git a/2021.spreadsheet.army-fat.txt b/2021.spreadsheet.army-fat.txt new file mode 100644 index 0000000..24cde8b --- /dev/null +++ b/2021.spreadsheet.army-fat.txt @@ -0,0 +1,12 @@ +# Males (B2: waist, C2: neck) +# Replace `height` with actual height +# for the imperial system, replace 30,3 with 36,76 +=86,01 * Log10(B2 - C2) - 70,041 * log10(height) + 30,3 + +# Females (B2: waist, C2: neck, D2: hips) +# Replace `height` with actual height +# for the imperial system, replace 104,912 with 78,387 +=163,205 * Log10(B2 + D2 - C2) - 97,684 * Log10(height) - 104,912 + +# ALL MEASUREMENTS SHOULD BE DONE IN METRIC, LIKE THE REST OF THE WORLD DOES IT. +# https://friendlybit.com/other/im-not-from-america/