COMMIT ALL THE THINGS

* refactor JS (a lot)
* use a CSS framework (neat/bourbon), because CSS is hard
* up/downvote comments
* cleaner HTML
* HTML inclusion in JS
* SVG icons for reference, up and downvote
* basic i18n: english and german supported ootb
* lazy (because slow) client-side identicon generation (preview ability)
* removed website input field for no particular reason
* remove HTML.js in favour of a homebrew DOM manipulation tool
This commit is contained in:
Martin Zimmermann 2013-10-01 14:42:00 +02:00
parent 6d9792d22e
commit b36e2fdb28
102 changed files with 4738 additions and 383 deletions

View File

@ -54,7 +54,7 @@ from werkzeug.contrib.fixers import ProxyFix
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
from isso import db, migrate, views, wsgi, notify, colors from isso import db, migrate, views, wsgi, notify, colors, utils
from isso.views import comment, admin from isso.views import comment, admin
url_map = Map([ url_map = Map([
@ -62,16 +62,20 @@ url_map = Map([
Rule('/id/<int:id>', methods=['GET', 'PUT', 'DELETE'], endpoint=views.comment.single), Rule('/id/<int:id>', methods=['GET', 'PUT', 'DELETE'], endpoint=views.comment.single),
Rule('/id/<int:id>/like', methods=['POST'], endpoint=views.comment.like), Rule('/id/<int:id>/like', methods=['POST'], endpoint=views.comment.like),
Rule('/id/<int:id>/dislike', methods=['POST'], endpoint=views.comment.dislike),
Rule('/', methods=['GET'], endpoint=views.comment.fetch), Rule('/', methods=['GET'], endpoint=views.comment.fetch),
Rule('/count', methods=['GET'], endpoint=views.comment.count), Rule('/count', methods=['GET'], endpoint=views.comment.count),
Rule('/admin/', endpoint=views.admin.index) Rule('/admin/', endpoint=views.admin.index),
Rule('/check-ip', endpoint=views.comment.checkip)
]) ])
class Isso(object): class Isso(object):
PRODUCTION = False PRODUCTION = False
SALT = "Eech7co8Ohloopo9Ol6baimi"
def __init__(self, dbpath, secret, origin, max_age, passphrase, mailer): def __init__(self, dbpath, secret, origin, max_age, passphrase, mailer):

89
isso/crypto.py Normal file
View File

@ -0,0 +1,89 @@
# -*- encoding: utf-8 -*-
#
# Copyright (c) Django Software Foundation and individual contributors.
# All rights reserved.
import hmac
import time
import struct
import base64
import hashlib
import binascii
import operator
def _bin_to_long(x):
"""
Convert a binary string into a long integer
This is a clever optimization for fast xor vector math
"""
return int(binascii.hexlify(x), 16)
def _long_to_bin(x, hex_format_string):
"""
Convert a long integer into a binary string.
hex_format_string is like "%020x" for padding 10 characters.
"""
return binascii.unhexlify((hex_format_string % x).encode('ascii'))
def _fast_hmac(key, msg, digest):
"""
A trimmed down version of Python's HMAC implementation.
This function operates on bytes.
"""
dig1, dig2 = digest(), digest()
if len(key) > dig1.block_size:
key = digest(key).digest()
key += b'\x00' * (dig1.block_size - len(key))
dig1.update(key.translate(hmac.trans_36))
dig1.update(msg)
dig2.update(key.translate(hmac.trans_5C))
dig2.update(dig1.digest())
return dig2
def _pbkdf2(password, salt, iterations, dklen=0, digest=None):
"""
Implements PBKDF2 as defined in RFC 2898, section 5.2
HMAC+SHA256 is used as the default pseudo random function.
Right now 10,000 iterations is the recommended default which takes
100ms on a 2.2Ghz Core 2 Duo. This is probably the bare minimum
for security given 1000 iterations was recommended in 2001. This
code is very well optimized for CPython and is only four times
slower than openssl's implementation.
"""
assert iterations > 0
if not digest:
digest = hashlib.sha1
password = b'' + password
salt = b'' + salt
hlen = digest().digest_size
if not dklen:
dklen = hlen
if dklen > (2 ** 32 - 1) * hlen:
raise OverflowError('dklen too big')
l = -(-dklen // hlen)
r = dklen - (l - 1) * hlen
hex_format_string = "%%0%ix" % (hlen * 2)
def F(i):
def U():
u = salt + struct.pack(b'>I', i)
for j in xrange(int(iterations)):
u = _fast_hmac(password, u, digest).digest()
yield _bin_to_long(u)
return _long_to_bin(reduce(operator.xor, U()), hex_format_string)
T = [F(x) for x in range(1, l + 1)]
return b''.join(T[:-1]) + T[-1][:r]
pbkdf2 = lambda text, salt, iterations, dklen: base64.b16encode(
_pbkdf2(text.encode('utf-8'), salt, iterations, dklen)).lower()

View File

@ -141,7 +141,7 @@ class Comments:
self._remove_stale() self._remove_stale()
return self.get(id) return self.get(id)
def like(self, id, remote_addr): def vote(self, upvote, id, remote_addr):
"""+1 a given comment. Returns the new like count (may not change because """+1 a given comment. Returns the new like count (may not change because
the creater can't vote on his/her own comment and multiple votes from the the creater can't vote on his/her own comment and multiple votes from the
same ip address are ignored as well).""" same ip address are ignored as well)."""
@ -151,23 +151,26 @@ class Comments:
.fetchone() .fetchone()
if rv is None: if rv is None:
return 0 return None
likes, dislikes, voters = rv likes, dislikes, voters = rv
if likes + dislikes >= 142: if likes + dislikes >= 142:
return likes return {'likes': likes, 'dislikes': dislikes}
bf = Bloomfilter(bytearray(voters), likes + dislikes) bf = Bloomfilter(bytearray(voters), likes + dislikes)
if remote_addr in bf: if remote_addr in bf:
return likes return {'likes': likes, 'dislikes': dislikes}
bf.add(remote_addr) bf.add(remote_addr)
self.db.execute([ self.db.execute([
'UPDATE comments SET', 'UPDATE comments SET',
' likes = likes + 1, voters = ?', ' likes = likes + 1,' if upvote else 'dislikes = dislikes + 1,',
' voters = ?'
'WHERE id=?;'], (buffer(bf.array), id)) 'WHERE id=?;'], (buffer(bf.array), id))
return likes + 1 if upvote:
return {'likes': likes + 1, 'dislikes': dislikes}
return {'likes': likes, 'dislikes': dislikes + 1}
def count(self, uri): def count(self, uri):
""" """

View File

@ -5,11 +5,14 @@
define(["lib/q"], function(Q) { define(["lib/q"], function(Q) {
"use strict";
// http://stackoverflow.com/questions/17544965/unhandled-rejection-reasons-should-be-empty // http://stackoverflow.com/questions/17544965/unhandled-rejection-reasons-should-be-empty
Q.stopUnhandledRejectionTracking(); // Q.stopUnhandledRejectionTracking();
Q.longStackSupport = true; Q.longStackSupport = true;
var endpoint = null, var endpoint = null, remote_addr = null,
salt = "Eech7co8Ohloopo9Ol6baimi",
location = window.location.pathname; location = window.location.pathname;
// guess Isso API location // guess Isso API location
@ -34,20 +37,17 @@ define(["lib/q"], function(Q) {
var response = Q.defer(); var response = Q.defer();
if (! ("withCredentials" in xhr)) { if (! ("withCredentials" in xhr)) {
respone.reject("I won't support IE ≤ 10.") respone.reject("I won't support IE ≤ 10.");
return response.promise; return response.promise;
} }
xhr.withCredentials = true;
function onload() { function onload() {
response.resolve({status: xhr.status, body: xhr.responseText}); response.resolve({status: xhr.status, body: xhr.responseText});
} }
try { try {
xhr.open(method, url, true); xhr.open(method, url, true);
xhr.overrideMimeType("application/javascript"); xhr.withCredentials = true; // fuck you, fuck you, fuck you IE
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState === 4) { if (xhr.readyState === 4) {
onload(); onload();
@ -62,56 +62,56 @@ define(["lib/q"], function(Q) {
}; };
var qs = function(params) { var qs = function(params) {
rv = ""; var rv = "";
for (var key in params) { for (var key in params) {
if (params.hasOwnProperty(key)) { if (params.hasOwnProperty(key)) {
rv += key + "=" + encodeURIComponent(params[key]) + "&"; rv += key + "=" + encodeURIComponent(params[key]) + "&";
} }
} }
return rv.substring(0, rv.length - 1) // chop off trailing "&" return rv.substring(0, rv.length - 1); // chop off trailing "&"
} }
var create = function(data) { var create = function(data) {
return curl("POST", endpoint + "/new?" + qs({uri: location}), JSON.stringify(data)) return curl("POST", endpoint + "/new?" + qs({uri: location}), JSON.stringify(data))
.then(function (rv) { .then(function (rv) {
if (rv.status == 201 || rv.status == 202) { if (rv.status === 201 || rv.status === 202) {
return JSON.parse(rv.body); return JSON.parse(rv.body);
} else { } else {
msg = rv.body.match("<p>(.+)</p>") var msg = rv.body.match("<p>(.+)</p>");
throw {status: rv.status, reason: (msg && msg[1]) || rv.body} throw {status: rv.status, reason: (msg && msg[1]) || rv.body};
} }
}) });
} };
var modify = function(data) { var modify = function(data) {
// ... // ...
} };
var remove = function(id) { var remove = function(id) {
return curl("DELETE", endpoint + "/id/" + id, null) return curl("DELETE", endpoint + "/id/" + id, null)
.then(function(rv) { .then(function(rv) {
if (rv.status == 200) { if (rv.status === 200) {
return JSON.parse(rv.body) == null; return JSON.parse(rv.body) === null;
} else { } else {
throw {status: rv.status, reason: rv.body} throw {status: rv.status, reason: rv.body};
} }
}) });
} };
var fetchall = function() { var fetch = function() {
return curl("GET", endpoint + "/?" + qs({uri: location}), null) return curl("GET", endpoint + "/?" + qs({uri: location}), null)
.then(function (rv) { .then(function (rv) {
if (rv.status == 200) { if (rv.status === 200) {
return JSON.parse(rv.body) return JSON.parse(rv.body);
} else { } else {
msg = rv.body.match("<p>(.+)</p>") var msg = rv.body.match("<p>(.+)</p>");
throw {status: rv.status, reason: (msg && msg[1]) || rv.body} throw {status: rv.status, reason: (msg && msg[1]) || rv.body};
} }
}) });
} };
var count = function(uri) { var count = function(uri) {
return curl("GET", endpoint + "/count?" + qs({uri: uri}), null) return curl("GET", endpoint + "/count?" + qs({uri: uri}), null)
@ -123,12 +123,30 @@ define(["lib/q"], function(Q) {
}) })
} }
var like = function(id) {
return curl("POST", endpoint + "/id/" + id + "/like", null)
.then(function(rv) {
return JSON.parse(rv.body);
})
}
var dislike = function(id) {
return curl("POST", endpoint + "/id/" + id + "/dislike", null)
.then(function(rv) {
return JSON.parse(rv.body);
})
}
remote_addr = curl("GET", endpoint + "/check-ip", null).then(function(rv) {return rv.body});
return { return {
endpoint: endpoint, endpoint: endpoint, remote_addr: remote_addr, salt: salt,
create: create, create: create,
remove: remove, remove: remove,
fetchall: fetchall, fetch: fetch,
count: count count: count,
like: like,
dislike: dislike
} }
}); });

View File

@ -1,15 +1,15 @@
define(["app/api", "lib/HTML"], function(api, HTML) { define(["app/api", "app/dom", "app/markup"], function(api, $, Mark) {
return function() { return function() {
HTML.query("a").each(function(el, i, all) { $.each("a", function(el) {
if (! el.href.match("#isso-thread$")) { if (! el.href.match("#isso-thread$")) {
return; return;
}; }
var uri = el.href.match("^(.+)#isso-thread$")[1] var uri = el.href.match("^(.+)#isso-thread$")[1]
.replace(/^.*\/\/[^\/]+/, ''); .replace(/^.*\/\/[^\/]+/, '');
api.count(uri).then(function(rv) { api.count(uri).then(function(rv) {
el.textContent = rv + (rv > 1 ? " Kommentare" : " Kommentar"); el.textContent = Mark.up("{{ i18n-num-comments | pluralize : `n` }}", {n: rv});
}) });
}); });
} };
}); });

82
isso/js/app/dom.js Normal file
View File

@ -0,0 +1,82 @@
define(function() {
"use strict";
window.Element.prototype.replace = function(el) {
var element = DOM.htmlify(el);
this.parentNode.replaceChild(element, this);
return element;
};
window.Element.prototype.prepend = function(el) {
var element = DOM.htmlify(el);
this.insertBefore(element, this.firstChild);
return element;
};
window.Element.prototype.append = function(el) {
var element = DOM.htmlify(el);
this.appendChild(element);
return element;
};
window.Element.prototype.insertAfter = function(el) {
var element = DOM.htmlify(el);
this.parentNode.insertBefore(element, this.nextSibling);
return element;
};
window.Element.prototype.on = function(type, listener, prevent) {
this.addEventListener(type, function(event) {
listener();
if (prevent === undefined || prevent) {
event.preventDefault();
}
});
};
window.Element.prototype.remove = function() {
// Mimimi, I am IE and I am so retarded, mimimi.
this.parentNode.removeChild(this);
};
var DOM = function(query, root) {
if (! root) {
root = window.document;
}
var elements = root.querySelectorAll(query);
if (elements.length === 0) {
return null;
}
if (elements.length === 1) {
return elements[0];
}
return elements;
};
DOM.htmlify = function(html) {
if (html instanceof window.Element) {
return html;
}
var wrapper = DOM.new("div");
wrapper.innerHTML = html;
return wrapper.firstChild;
};
DOM.new = function(tag) {
return document.createElement(tag);
};
DOM.each = function(tag, func) {
Array.prototype.forEach.call(document.getElementsByTagName(tag), func);
};
return DOM;
});

View File

@ -1,51 +0,0 @@
define(["lib/HTML"], function(HTML) {
var msgbox = function(defaults) {
var form = document.createElement("div")
form.className = "isso-comment-box"
HTML.ify(form);
var optional = form.add("ul.optional");
optional.add("li>input[type=text name=author placeholder=Name ]").value = defaults.author || "";
optional.add("li>input[type=email name=email placeholder=Email]").value = defaults.email || "";
optional.add("li>input[type=url name=website placeholder=Website]").value = defaults.website || "";
var textarea = form.add("div>textarea[rows=2 name=text]");
textarea.value = defaults.text || "";
textarea.placeholder = "Kommentar hier eintippen (andere Felder sind optional)"
textarea.onfocus = function() {
textarea.rows = 8;
// scrollIntoView enhancement
};
form.add("input[type=submit]").value = "Kommentar hinzufügen";
form.add("span");
return form;
}
var validate = function(msgbox) {
if (msgbox.query("textarea").value.length < 3) {
msgbox.query("textarea").focus();
msgbox.span.className = "isso-popup"
msgbox.span.innerHTML = "Dein Kommentar sollte schon etwas länger sein.";
msgbox.span.addEventListener("click", function(event) {
msgbox.span.className = "";
msgbox.span.innerHTML = "";
})
setTimeout(function() {
msgbox.span.className = ""
msgbox.span.innerHTML = ""
}, 5000 )
return false;
}
return true;
}
return {
msgbox: msgbox,
validate: validate
}
});

70
isso/js/app/i18n.js Normal file
View File

@ -0,0 +1,70 @@
define(function() {
"use strict";
// pluralization functions for each language you support
var plurals = {
"en": function (msgs, n) {
return msgs[n === 1 ? 0 : 1];
}
};
plurals["de"] = plurals["en"];
// the user's language. you can replace this with your own code
var lang = (navigator.language || navigator.userLanguage).split("-")[0];
// fall back to English
if (!plurals[lang]) {
lang = "en";
}
return {
plurals: plurals,
lang: lang,
de: {
"postbox-text" : "Kommentar hier eintippen (mindestens 3 Zeichen)",
"postbox-author" : "Name (optional)",
"postbox-email" : "Email (optional)",
"postbox-submit": "Abschicken.",
"num-comments": "1 Kommentar\n{{ n }} Kommentare",
"no-comments": "Keine Kommentare bis jetzt",
"comment-reply": "Antworten",
"comment-delete": "Löschen",
"comment-confirm": "Bestätigen",
"comment-close": "Schließen",
"date-now": "eben jetzt",
"date-minute": "vor einer Minute\nvor {{ n }} Minuten",
"date-hour": "vor einer Stunde\nvor {{ n }} Stunden",
"date-day": "Gestern\nvor {{ n }} Tagen",
"date-week": "letzte Woche\nvor {{ n }} Wochen",
"date-month": "letzten Monat\nvor {{ n }} Monaten",
"date-year": "letztes Jahr\nvor {{ n }} Jahren"
},
en: {
"postbox-text": "Type Comment Here (at least 3 chars)",
"postbox-author": "Name (optional)",
"postbox-email": "E-mail (optional)",
"postbox-submit": "Post Comment.",
"num-comments": "One Comment\n{{ n }} Comments",
"no-comments": "No Comments Yet",
"comment-reply": "Reply",
"comment-delete": "Delete",
"comment-confirm": "Confirm",
"comment-close": "Close",
"date-now": "now",
"date-minute": "a minute ago\n{{ n }} minutes ago",
"date-hour": "an hour ago\n{{ n }} hours ago",
"date-day": "Yesterday\n{{ n }} days ago",
"date-week": "last week\n{{ n }} weeks ago",
"date-month": "last month\n{{ n }} months ago",
"date-year": "last year\n{{ n }} years ago"
}
};
});

View File

@ -5,172 +5,224 @@
*/ */
define(["lib/q", "lib/HTML", "helper/utils", "helper/identicons", "./api", "./forms"], function(Q, HTML, utils, identicons, api, forms) { define(["lib/pbkdf2", "lib/identicons", "text/html", "./dom", "./utils", "./api", "./markup", "./i18n"],
function(pbkdf2, identicons, templates, $, utils, api, Mark, i18n) {
"use strict";
var msgs = i18n[i18n.lang];
var toggle = function(el, on, off) {
if (el.classList.contains("off") || ! el.classList.contains("on")) {
el.classList.remove("off");
el.classList.add("on");
on(el);
} else {
el.classList.remove("on");
el.classList.add("off");
off(el);
}
};
var Postbox = function(parent) {
var el = $.htmlify(Mark.up(templates["postbox"]));
// add a blank identicon to not waste CPU cycles
// XXX show a space invader instead :>
$(".avatar > canvas", el).replace(identicons.blank(48, 48));
// on text area focus, generate identicon from IP address
$(".textarea-wrapper > textarea", el).on("focus", function() {
if ($(".avatar canvas", el).classList.contains("blank")) {
$(".avatar canvas", el).replace(
identicons.generate(pbkdf2(api.remote_addr, api.salt, 1000, 6), 48, 48));
}
});
// update identicon, when the user provices an email address
var active;
$(".input-wrapper > [type=email]", el).on("keyup", function() {
if (active) {
clearTimeout(active);
}
active = setTimeout(function() {
pbkdf2($(".input-wrapper > [type=email]", el).value || api.remote_addr, api.salt, 1000, 6)
.then(function(rv) {
$(".avatar canvas", el).replace(identicons.generate(rv, 48, 48));
});
}, 200);
}, false);
$(".input-wrapper > [type=email]", el).on("keydown", function() {
clearTimeout(active);
}, false);
el.validate = function() {
if ($("textarea", this).value.length < 3) {
$("textarea", this).focus();
return false;
}
return true;
};
$("[type=submit]", el).on("click", function() {
if (! el.validate()) {
return;
}
api.create({
author: $("[name=author]", el).value || null,
email: $("[name=email]", el).value || null,
text: $("textarea", el).value,
parent: parent || null
}).then(function(comment) {
$("[name=author]", el).value = "";
$("[name=email]", el).value = "";
$("textarea", el).value = "";
$("textarea", el).rows = 2;
$("textarea", el).blur();
insert(comment, true);
if (parent !== null) {
el.remove();
}
});
});
return el;
};
var map = {id: {}, name: {}};
var insert = function(comment, scrollIntoView) { var insert = function(comment, scrollIntoView) {
/*
* insert a comment (JSON/object) into the #isso-thread or below a parent (#isso-N), renders some HTML and
* registers events to reply to, edit and remove a comment.
*/
map.name[comment.id] = comment.author;
if (comment.parent) { if (comment.parent) {
entrypoint = HTML.query("#isso-" + comment.parent).add("div.isso-follow-up"); comment["replyto"] = map.name[comment.parent];
} else {
entrypoint = HTML.query("div#isso-root")
} }
entrypoint.add("article.isso-comment#isso-" + comment.id) var el = $.htmlify(Mark.up(templates["comment"], comment));
.add("header+span.avatar+div.text+footer")
var node = HTML.query("#isso-" + comment.id),
date = new Date(parseInt(comment.created) * 1000);
if (comment.mode == 2) {
node.header.add("span.note").textContent = 'Kommentar muss noch freigeschaltet werden';
} else if (comment.mode == 4) { // deleted
node.classList.add('deleted');
node.header.add("span.note").textContent = "Kommentar gelöscht."
}
if (comment.website) {
var el = node.header.add("a.author")
el.textContent= comment.author || 'Anonymous';
el.href = comment.website;
el.rel = "nofollow"
} else {
node.header.add("span.author").innerHTML = comment.author || 'Anonymous';
}
node.header.add("span.spacer").textContent = "•";
var permalink = node.header.add("a.permalink");
permalink.href = '#isso-' + comment.id;
permalink.add("date[datetime=" + date.getUTCFullYear() + "-" + date.getUTCMonth() + "-" + date.getUTCDay() + "]")
var refresh = function() { var refresh = function() {
permalink.date.textContent = utils.ago(date); $(".permalink > date", el).textContent = utils.ago(new Date(parseInt(comment.created, 10) * 1000));
setTimeout(refresh, 60*1000) setTimeout(refresh, 60*1000);
}; refresh(); }; refresh();
var canvas = node.query("span.avatar").add("canvas[hash=" + comment.hash + "]"); $("div.avatar > canvas", el).replace(identicons.generate(comment.hash, 48, 48));
canvas.width = canvas.height = 48;
identicons.generate(canvas.getContext('2d'), comment.hash);
if (comment.mode == 4) { var entrypoint;
node.query(".text").add("p").value = "&nbsp;" if (comment.parent === null) {
entrypoint = $("#isso-root");
} else { } else {
node.query(".text").innerHTML = comment.text; var key = comment.parent;
while (key in map.id) {
key = map.id[key];
}
map.id[comment.id] = comment.parent;
entrypoint = $("#isso-" + key + " > .text-wrapper > .isso-follow-up");
} }
node.footer.add("a.liek{Liek}").href = "#"; entrypoint.append(el);
node.footer.add("a.reply{Antworten}").href = "#";
if (scrollIntoView) { if (scrollIntoView) {
node.scrollIntoView(false); el.scrollIntoView();
} }
if (utils.read(comment.id)) { var footer = $("#isso-" + comment.id + " > .text-wrapper > footer"),
node.footer.add("a.delete{Löschen}").href = "#"; header = $("#isso-" + comment.id + " > .text-wrapper > header");
node.footer.add("a.edit{Bearbeiten}").href = "#";
var delbtn = node.query("a.delete"), var form = new Postbox(comment.id);
editbtn = node.query("a.edit"); $("a.reply", footer).on("click", function() {
toggle(
delbtn.addEventListener("click", function(event) { $("a.reply", footer),
if (delbtn.textContent == "Bestätigen") { function(reply) {
api.remove(comment.id).then(function(rv) { footer.insertAfter(form);
if (rv) { reply.textContent = msgs["comment-close"];
node.remove(); },
} else { function(reply) {
node.classList.add('deleted'); form.remove();
node.header.add("span.note").textContent = "Kommentar gelöscht."; reply.textContent = msgs["comment-reply"];
HTML.query("#isso-" + comment.id + " > div.text").innerHTML = "<p>&nbsp;</p>"
}
})
} else {
delbtn.textContent = "Bestätigen"
setTimeout(function() {delbtn.textContent = "Löschen"}, 1500)
} }
event.preventDefault(); );
}) });
if (comment.parent !== null) {
$("a.parent", header).on("mouseover", function() {
$("#isso-" + comment.parent).classList.add("parent-highlight");
});
$("a.parent", header).on("mouseout", function() {
$("#isso-" + comment.parent).classList.remove("parent-highlight");
});
} }
// ability to answer directly to a comment var votes = function (value) {
HTML.query("#isso-" + comment.id + " a.reply").addEventListener("click", function(event) { var span = $("span.votes", footer);
if (span === null) {
// remove active form when clicked again or reply to another comment if (value === 0) {
var active = HTML.query(".isso-active-msgbox"); // [] when empty, element if not span.remove();
return;
if (! (active instanceof Array)) { } else {
active.query("div.isso-comment-box").remove() footer.prepend($.htmlify('<span class="votes">' + value + '</span>'));
active.classList.remove("isso-active-msgbox"); }
active.query("a.reply").textContent = "Antworten" } else {
if (value === 0) {
if (active.id == "isso-" + comment.id) { span.remove();
event.preventDefault(); } else {
return; span.textContent = value;
} }
} }
};
var msgbox = forms.msgbox({}) $("a.upvote", footer).on("click", function() {
HTML.query("#isso-" + comment.id).footer.appendChild(msgbox); api.like(comment.id).then(function(rv) {
HTML.query("#isso-" + comment.id).classList.add("isso-active-msgbox"); votes(rv.likes - rv.dislikes);
HTML.query("#isso-" + comment.id + " a.reply").textContent = "Schließen";
// msgbox.scrollIntoView(false);
msgbox.query("input[type=submit]").addEventListener("click", function(event) {
forms.validate(msgbox) && api.create({
author: msgbox.query("[name=author]").value || null,
email: msgbox.query("[name=email]").value || null,
website: msgbox.query("[name=website]").value || null,
text: msgbox.query("textarea").value,
parent: comment.id })
.then(function(rv) {
// remove box on submit
msgbox.parentNode.parentNode.classList.remove("isso-active-msgbox");
msgbox.parentNode.parentNode.query("a.reply").textContent = "Antworten"
msgbox.remove()
insert(rv, true);
})
event.preventDefault()
}); });
event.preventDefault();
});
}
var init = function() {
var rootmsgbox = forms.msgbox({});
var h4 = HTML.query("#isso-thread").add("h4")
HTML.query("#isso-thread").add("div#isso-root").add(rootmsgbox);
rootmsgbox.query("input[type=submit]").addEventListener("click", function(event) {
forms.validate(rootmsgbox) && api.create({
author: rootmsgbox.query("[name=author]").value || null,
email: rootmsgbox.query("[name=email]").value || null,
website: rootmsgbox.query("[name=website]").value || null,
text: rootmsgbox.query("textarea").value,
parent: null })
.then(function(rv) {
rootmsgbox.query("[name=author]").value = "";
rootmsgbox.query("[name=email]").value = "";
rootmsgbox.query("[name=website]").value = "";
rootmsgbox.query("textarea").value = "";
rootmsgbox.query("textarea").rows = 2;
rootmsgbox.query("textarea").blur();
insert(rv, true);
})
event.preventDefault()
}); });
api.fetchall().then(function(comments) { $("a.downvote", footer).on("click", function() {
h4.textContent = comments.length + " Kommentare zu \"" + utils.heading() + "\""; api.dislike(comment.id).then(function(rv) {
for (var i in comments) { votes(rv.likes - rv.dislikes);
insert(comments[i]) });
});
if (! utils.cookie(comment.id)) {
// $("a.edit", footer).remove();
$("a.delete", footer).remove();
return;
}
$("a.delete", footer).on("click", function() {
if ($("a.delete", footer).textContent === msgs["comment-confirm"]) {
api.remove(comment.id).then(function(rv) {
if (rv) {
el.remove();
} else {
$("span.note", el).textContent = "Kommentar gelöscht.";
$(".text", el).innerHTML = "<p>&nbsp;</p>";
}
});
} else {
$("a.delete", footer).textContent = msgs["comment-confirm"];
setTimeout(function() {
$("a.delete", footer).textContent = msgs["comment-delete"];
}, 1500);
} }
}).fail(function(rv) { });
h4.textContent = "Kommentiere \"" + utils.heading() + "\"";
}) return;
}
// var votes = node.footer.add("span.votes{" + (comment.likes - comment.dislikes) + "}");
// node.footer.add(forms.like(comment.id, function(rv) {
// votes.textContent = rv.likes - rv.dislikes;
// }))
// node.footer.add(forms.dislike(comment.id, function(rv) {
// votes.textContent = rv.likes - rv.dislikes;
// }))
};
return { return {
init: init insert: insert,
} Postbox: Postbox
};
}); });

58
isso/js/app/markup.js Normal file
View File

@ -0,0 +1,58 @@
define(["lib/markup", "./i18n", "text/svg"], function(Mark, i18n, svg) {
"use strict";
var pad = function(n, width, z) {
z = z || '0';
n = n + '';
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
};
// circumvent https://github.com/adammark/Markup.js/issues/22
function merge(obj) {
var result = {};
for (var prefix in obj) {
for (var attrname in obj[prefix]) {
result[prefix + "-" + attrname] = obj[prefix][attrname];
}
}
return result;
}
Mark.delimiter = ":";
Mark.includes = merge({
i18n: i18n[i18n.lang],
svg: svg
});
Mark.pipes.datetime = function(date) {
if (typeof date !== "object") {
date = new Date(parseInt(date, 10) * 1000);
}
return [date.getUTCFullYear(), pad(date.getUTCMonth(), 2), pad(date.getUTCDay(), 2)].join("-");
};
Mark.pipes.substract = function(a, b) {
return parseInt(a, 10) - parseInt(b, 10);
};
Mark.pipes.pluralize = function(str, n) {
return i18n.plurals[i18n.lang](str.split("\n"), +n).trim();
};
var strip = function(string) {
// allow whitespace between Markup.js delimiters such as
// {{ var | pipe : arg }} instead of {{var|pipe:arg}}
return string.replace(/\{\{\s*(.+?)\s*\}\}/g, function(match, val) {
return ("{{" + val + "}}").replace(/\s*\|\s*/g, "|")
.replace(/\s*\:\s*/g, ":");
});
};
return {
up: function(template, context) {
return Mark.up(strip(template), context);
}
};
});

45
isso/js/app/utils.js Normal file
View File

@ -0,0 +1,45 @@
/* Copyright 2013, Martin Zimmermann <info@posativ.org>. All rights reserved.
* License: BSD Style, 2 clauses. See isso/__init__.py.
*
* utility functions
*/
define(["./markup"], function(Mark) {
// return `cookie` string if set
var cookie = function(cookie) {
return (document.cookie.match('(^|; )' + cookie + '=([^;]*)') || 0)[2];
};
var ago = function(date) {
var diff = (((new Date()).getTime() - date.getTime()) / 1000),
day_diff = Math.floor(diff / 86400);
if (isNaN(day_diff) || day_diff < 0) {
return;
}
var i18n = function(msgid, n) {
if (! n) {
return Mark.up("{{ i18n-" + msgid + " }}");
} else {
return Mark.up("{{ i18n-" + msgid + " | pluralize : `n` }}", {n: n});
}
};
return day_diff === 0 && (
diff < 60 && i18n("date-now") ||
diff < 3600 && i18n("date-minute", Math.floor(diff / 60)) ||
diff < 86400 && i18n("date-hour", Math.floor(diff / 3600))) ||
day_diff === 1 && i18n("date-day", day_diff) ||
day_diff < 31 && i18n("date-week", Math.ceil(day_diff / 7)) ||
day_diff < 365 && i18n("date-month", Math.ceil(day_diff / 30)) ||
i18n("date-year", Math.ceil(day_diff / 365.25));
};
return {
cookie: cookie,
ago: ago
};
});

View File

@ -3,5 +3,7 @@
name: "lib/almond", name: "lib/almond",
include: ['embed'], include: ['embed'],
out: "embed.min.js", out: "embed.min.js",
optimizeAllPluginResources: true,
wrap: true wrap: true
}) })

View File

@ -1,6 +1,21 @@
require(["lib/ready", "app/isso", "app/count"], function(domready, isso, count) { require(["lib/ready", "app/api", "app/isso", "app/count", "app/dom", "app/markup"], function(domready, api, isso, count, $, Mark) {
"use strict";
domready(function() { domready(function() {
count(); count();
isso.init();
}) $("#isso-thread").append($.new('h4'));
}); $("#isso-thread").append(new isso.Postbox(null));
$("#isso-thread").append('<div id="isso-root"></div>');
api.fetch().then(function(comments) {
$("#isso-thread > h4").textContent = Mark.up("{{ i18n-num-comments | pluralize : `n` }}", {n: comments.length});
for (var i=0; i < comments.length; i++) {
isso.insert(comments[i], false);
}
}).fail(function() {
$("#isso-thread > h4").textContent = Mark.up("{{ i18n-no-comments }}");
});
});
});

View File

@ -1,83 +0,0 @@
/* Copyright 2013, Martin Zimmermann <info@posativ.org>. All rights reserved.
* License: BSD Style, 2 clauses. See isso/__init__.py.
*
* utility functions
*/
define({
// return `cookie` string if set
read: function(cookie) {
return (document.cookie.match('(^|; )' + cookie + '=([^;]*)') || 0)[2]
},
ago: function(date) {
/*!
* JavaScript Pretty Date
* Copyright (c) 2011 John Resig (ejohn.org)
* Licensed under the MIT and GPL licenses.
*/
var diff = (((new Date()).getTime() - date.getTime()) / 1000),
day_diff = Math.floor(diff / 86400);
if (isNaN(day_diff) || day_diff < 0)
return;
return day_diff == 0 && (
diff < 60 && "eben jetzt" ||
diff < 120 && "vor einer Minute" ||
diff < 3600 && "vor " + Math.floor(diff / 60) + " Minuten" ||
diff < 7200 && "vor einer Stunde" ||
diff < 86400 && "vor " + Math.floor(diff / 3600) + " Stunden") ||
day_diff == 1 && "Gestern" ||
day_diff < 7 && "vor " + day_diff + " Tagen" ||
day_diff < 31 && "vor " + Math.ceil(day_diff / 7) + " Wochen" ||
day_diff < 365 && "vor " + Math.ceil(day_diff / 30) + " Monaten" ||
"vor " + Math.ceil(day_diff / 365.25) + " Jahren";
},
heading: function() {
/*
* return first level heading that is probably the
* blog title. If no h1 is found, "Untitled." is used.
*/
var el = document.getElementById("isso-thread");
var visited = [];
var recurse = function(node) {
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
if (child.nodeType != child.ELEMENT_NODE) {
continue;
}
if (child.nodeName == "H1") {
return child;
}
if (visited.indexOf(child) == -1) {
return recurse(child);
}
}
}
while (el != null) {
visited.push(el);
if (el == document.documentElement) {
break
}
var rv = recurse(el);
if (rv) {
return rv.textContent.trim();
}
el = el.parentNode;
}
return "Untitled."
}
});

View File

@ -1,7 +1,9 @@
define(function() { define(["lib/q"], function(Q) {
"use strict";
// JS Identicon generation via Gregory Schier (http://codepen.io/gschier/pen/GLvAy) // JS Identicon generation via Gregory Schier (http://codepen.io/gschier/pen/GLvAy)
// modified to work with a given seed using Jenkins hashing. // extended to produce the same identicon for a given hash
// Size of a grid square in pixels // Size of a grid square in pixels
var SQUARE = 8; var SQUARE = 8;
@ -10,28 +12,11 @@ define(function() {
var GRID = 5; var GRID = 5;
// Padding on the edge of the canvas in px // Padding on the edge of the canvas in px
var PADDING = SQUARE/2; var PADDING = 4;
/* Jenkins 18-bit hash */
var jenkins = function(key) {
var hash = 0;
for (var i=0; i<key.length; ++i) {
hash += key.charCodeAt(i);
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return (hash >>> 0) % Math.pow(2, 18);
}
var pad = function(n, width) { var pad = function(n, width) {
return n.length >= width ? n : new Array(width - n.length + 1).join("0") + n; return n.length >= width ? n : new Array(width - n.length + 1).join("0") + n;
} };
/** /**
* Fill in a square on the canvas. * Fill in a square on the canvas.
@ -51,13 +36,31 @@ define(function() {
/** /**
* Pick random squares to fill in. * Pick random squares to fill in.
*/ */
var generateIdenticon = function(ctx, key) { var generateIdenticon = function(key, height, width) {
var hash = pad(jenkins(key).toString(2), 18), var canvas = document.createElement("canvas"),
index = 0, color = null; ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
// via http://colrd.com/palette/19308/ // FILL CANVAS BG
switch (hash.substring(hash.length - 3, hash.length)) { ctx.beginPath();
ctx.rect(0, 0, height, width);
ctx.fillStyle = '#F0F0F0';
ctx.fill();
if (typeof key === null) {
return canvas;
}
Q.when(key, function(key) {
var hash = pad((parseInt(key, 16) % Math.pow(2, 18)).toString(2), 18),
index = 0, color = null;
canvas.setAttribute("data-hash", key);
// via http://colrd.com/palette/19308/
switch (hash.substring(hash.length - 3, hash.length)) {
case "000": case "000":
color = "#9abf88"; color = "#9abf88";
break; break;
@ -82,35 +85,36 @@ define(function() {
case "111": case "111":
color = "#447c69"; color = "#447c69";
break; break;
}
// FILL CANVAS BG
ctx.beginPath();
ctx.rect(0, 0, 48, 48);
ctx.fillStyle = '#F0F0F0';
ctx.fill();
// FILL THE SQUARES
for (var x=0; x<Math.ceil(GRID/2); x++) {
for (var y=0; y<GRID; y++) {
if (hash.charAt(index) == "1") {
fillBlock(x, y, color, ctx);
// FILL RIGHT SIDE SYMMETRICALLY
if (x < Math.floor(GRID/2)) {
fillBlock((GRID-1) - x, y, color, ctx);
}
}
index++;
} }
}
return ctx; // FILL THE SQUARES
for (var x=0; x<Math.ceil(GRID/2); x++) {
for (var y=0; y<GRID; y++) {
if (hash.charAt(index) === "1") {
fillBlock(x, y, color, ctx);
// FILL RIGHT SIDE SYMMETRICALLY
if (x < Math.floor(GRID/2)) {
fillBlock((GRID-1) - x, y, color, ctx);
}
}
index++;
}
}
});
return canvas;
};
var generateBlank = function(height, width) {
var el = generateIdenticon(null, height, width);
el.className = "blank";
return el;
}; };
return { return {
generate: generateIdenticon, generate: generateIdenticon,
hash: jenkins blank: generateBlank
}; };
}) });

476
isso/js/lib/markup.js Normal file
View File

@ -0,0 +1,476 @@
/*
Markup.js v1.5.16: http://github.com/adammark/Markup.js
MIT License
(c) 2011 - 2013 Adam Mark
*/
var Mark = {
// Templates to include, by name. A template is a string.
includes: {},
// Global variables, by name. Global variables take precedence over context variables.
globals: {},
// The delimiter to use in pipe expressions, e.g. {{if color|like>red}}.
delimiter: ">",
// Collapse white space between HTML elements in the resulting string.
compact: false,
// Shallow-copy an object.
_copy: function (a, b) {
b = b || [];
for (var i in a) {
b[i] = a[i];
}
return b;
},
// Get the value of a number or size of an array. This is a helper function for several pipes.
_size: function (a) {
return a instanceof Array ? a.length : (a || 0);
},
// This object represents an iteration. It has an index and length.
_iter: function (idx, size) {
this.idx = idx;
this.size = size;
this.length = size;
this.sign = "#";
// Print the index if "#" or the count if "##".
this.toString = function () {
return this.idx + this.sign.length - 1;
};
},
// Pass a value through a series of pipe expressions, e.g. _pipe(123, ["add>10","times>5"]).
_pipe: function (val, expressions) {
var expression, parts, fn, result;
// If we have expressions, pull out the first one, e.g. "add>10".
if ((expression = expressions.shift())) {
// Split the expression into its component parts, e.g. ["add", "10"].
parts = expression.split(this.delimiter);
// Pull out the function name, e.g. "add".
fn = parts.shift().trim();
try {
// Run the function, e.g. add(123, 10) ...
result = Mark.pipes[fn].apply(null, [val].concat(parts));
// ... then pipe again with remaining expressions.
val = this._pipe(result, expressions);
}
catch (e) {
}
}
// Return the piped value.
return val;
},
// TODO doc
_eval: function (context, filters, child) {
var result = this._pipe(context, filters),
ctx = result,
i = -1,
j,
opts;
if (result instanceof Array) {
result = "";
j = ctx.length;
while (++i < j) {
opts = {
iter: new this._iter(i, j)
};
result += child ? Mark.up(child, ctx[i], opts) : ctx[i];
}
}
else if (result instanceof Object) {
result = Mark.up(child, ctx);
}
return result;
},
// Process the contents of an IF or IF/ELSE block.
_test: function (bool, child, context, options) {
// Process the child string, then split it into the IF and ELSE parts.
var str = Mark.up(child, context, options).split(/\{\{\s*else\s*\}\}/);
// Return the IF or ELSE part. If no ELSE, return an empty string.
return (bool === false ? str[1] : str[0]) || "";
},
// Determine the extent of a block expression, e.g. "{{foo}}...{{/foo}}"
_bridge: function (tpl, tkn) {
var exp = "{{\\s*" + tkn + "([^/}]+\\w*)?}}|{{/" + tkn + "\\s*}}",
re = new RegExp(exp, "g"),
tags = tpl.match(re) || [],
t,
i,
a = 0,
b = 0,
c = -1,
d = 0;
for (i = 0; i < tags.length; i++) {
t = i;
c = tpl.indexOf(tags[t], c + 1);
if (tags[t].indexOf("{{/") > -1) {
b++;
}
else {
a++;
}
if (a === b) {
break;
}
}
a = tpl.indexOf(tags[0]);
b = a + tags[0].length;
d = c + tags[t].length;
// Return the block, e.g. "{{foo}}bar{{/foo}}" and its child, e.g. "bar".
return [tpl.substring(a, d), tpl.substring(b, c)];
}
};
// Inject a template string with contextual data and return a new string.
Mark.up = function (template, context, options) {
context = context || {};
options = options || {};
// Match all tags like "{{...}}".
var re = /\{\{(.+?)\}\}/g,
// All tags in the template.
tags = template.match(re) || [],
// The tag being evaluated, e.g. "{{hamster|dance}}".
tag,
// The expression to evaluate inside the tag, e.g. "hamster|dance".
prop,
// The token itself, e.g. "hamster".
token,
// An array of pipe expressions, e.g. ["more>1", "less>2"].
filters = [],
// Does the tag close itself? e.g. "{{stuff/}}".
selfy,
// Is the tag an "if" statement?
testy,
// The contents of a block tag, e.g. "{{aa}}bb{{/aa}}" -> "bb".
child,
// The resulting string.
result,
// The global variable being evaluated, or undefined.
global,
// The included template being evaluated, or undefined.
include,
// A placeholder variable.
ctx,
// Iterators.
i = 0,
j = 0;
// Set custom pipes, if provided.
if (options.pipes) {
this._copy(options.pipes, this.pipes);
}
// Set templates to include, if provided.
if (options.includes) {
this._copy(options.includes, this.includes);
}
// Set global variables, if provided.
if (options.globals) {
this._copy(options.globals, this.globals);
}
// Optionally override the delimiter.
if (options.delimiter) {
this.delimiter = options.delimiter;
}
// Optionally collapse white space.
if (options.compact !== undefined) {
this.compact = options.compact;
}
// Loop through tags, e.g. {{a}}, {{b}}, {{c}}, {{/c}}.
while ((tag = tags[i++])) {
result = undefined;
child = "";
selfy = tag.indexOf("/}}") > -1;
prop = tag.substr(2, tag.length - (selfy ? 5 : 4));
prop = prop.replace(/`(.+?)`/g, function (s, p1) {
return Mark.up("{{" + p1 + "}}", context);
});
testy = prop.trim().indexOf("if ") === 0;
filters = prop.split("|");
filters.shift(); // instead of splice(1)
prop = prop.replace(/^\s*if/, "").split("|").shift().trim();
token = testy ? "if" : prop.split("|")[0];
ctx = context[prop];
// If an "if" statement without filters, assume "{{if foo|notempty}}"
if (testy && !filters.length) {
filters = ["notempty"];
}
// Does the tag have a corresponding closing tag? If so, find it and move the cursor.
if (!selfy && template.indexOf("{{/" + token) > -1) {
result = this._bridge(template, token);
tag = result[0];
child = result[1];
i += tag.match(re).length - 1; // fast forward
}
// Skip "else" tags. These are pulled out in _test().
if (/^\{\{\s*else\s*\}\}$/.test(tag)) {
continue;
}
// Evaluating a global variable.
else if ((global = this.globals[prop]) !== undefined) {
result = this._eval(global, filters, child);
}
// Evaluating an included template.
else if ((include = this.includes[prop])) {
if (include instanceof Function) {
include = include();
}
result = this._pipe(Mark.up(include, context), filters);
}
// Evaluating a loop counter ("#" or "##").
else if (prop.indexOf("#") > -1) {
options.iter.sign = prop;
result = this._pipe(options.iter, filters);
}
// Evaluating the current context.
else if (prop === ".") {
result = this._pipe(context, filters);
}
// Evaluating a variable with dot notation, e.g. "a.b.c"
else if (prop.indexOf(".") > -1) {
prop = prop.split(".");
ctx = Mark.globals[prop[0]];
if (ctx) {
j = 1;
}
else {
j = 0;
ctx = context;
}
// Get the actual context
while (ctx && j < prop.length) {
ctx = ctx[prop[j++]];
}
result = this._eval(ctx, filters, child);
}
// Evaluating an "if" statement.
else if (testy) {
result = this._pipe(ctx, filters);
}
// Evaluating an array, which might be a block expression.
else if (ctx instanceof Array) {
result = this._eval(ctx, filters, child);
}
// Evaluating a block expression.
else if (child) {
result = ctx ? Mark.up(child, ctx) : undefined;
}
// Evaluating anything else.
else if (context.hasOwnProperty(prop)) {
result = this._pipe(ctx, filters);
}
// Evaluating an "if" statement.
if (testy) {
result = this._test(result, child, context, options);
}
// Replace the tag, e.g. "{{name}}", with the result, e.g. "Adam".
template = template.replace(tag, result === undefined ? "???" : result);
}
return this.compact ? template.replace(/>\s+</g, "><") : template;
};
// Freebie pipes. See usage in README.md
Mark.pipes = {
empty: function (obj) {
return !obj || (obj + "").trim().length === 0 ? obj : false;
},
notempty: function (obj) {
return obj && (obj + "").trim().length ? obj : false;
},
blank: function (str, val) {
return !!str || str === 0 ? str : val;
},
more: function (a, b) {
return Mark._size(a) > b ? a : false;
},
less: function (a, b) {
return Mark._size(a) < b ? a : false;
},
ormore: function (a, b) {
return Mark._size(a) >= b ? a : false;
},
orless: function (a, b) {
return Mark._size(a) <= b ? a : false;
},
between: function (a, b, c) {
a = Mark._size(a);
return a >= b && a <= c ? a : false;
},
equals: function (a, b) {
return a == b ? a : false;
},
notequals: function (a, b) {
return a != b ? a : false;
},
like: function (str, pattern) {
return new RegExp(pattern, "i").test(str) ? str : false;
},
notlike: function (str, pattern) {
return !Mark.pipes.like(str, pattern) ? str : false;
},
upcase: function (str) {
return String(str).toUpperCase();
},
downcase: function (str) {
return String(str).toLowerCase();
},
capcase: function (str) {
return str.replace(/\b\w/g, function (s) { return s.toUpperCase(); });
},
chop: function (str, n) {
return str.length > n ? str.substr(0, n) + "..." : str;
},
tease: function (str, n) {
var a = str.split(/\s+/);
return a.slice(0, n).join(" ") + (a.length > n ? "..." : "");
},
trim: function (str) {
return str.trim();
},
pack: function (str) {
return str.trim().replace(/\s{2,}/g, " ");
},
round: function (num) {
return Math.round(+num);
},
clean: function (str) {
return String(str).replace(/<\/?[^>]+>/gi, "");
},
size: function (obj) {
return obj.length;
},
length: function (obj) {
return obj.length;
},
reverse: function (arr) {
return Mark._copy(arr).reverse();
},
join: function (arr, separator) {
return arr.join(separator);
},
limit: function (arr, count, idx) {
return arr.slice(+idx || 0, +count + (+idx || 0));
},
split: function (str, separator) {
return str.split(separator || ",");
},
choose: function (bool, iffy, elsy) {
return !!bool ? iffy : (elsy || "");
},
toggle: function (obj, csv1, csv2, str) {
return csv2.split(",")[csv1.match(/\w+/g).indexOf(obj + "")] || str;
},
sort: function (arr, prop) {
var fn = function (a, b) {
return a[prop] > b[prop] ? 1 : -1;
};
return Mark._copy(arr).sort(prop ? fn : undefined);
},
fix: function (num, n) {
return (+num).toFixed(n);
},
mod: function (num, n) {
return (+num) % (+n);
},
divisible: function (num, n) {
return num && (+num % n) === 0 ? num : false;
},
even: function (num) {
return num && (+num & 1) === 0 ? num : false;
},
odd: function (num) {
return num && (+num & 1) === 1 ? num : false;
},
number: function (str) {
return parseFloat(str.replace(/[^\-\d\.]/g, ""));
},
url: function (str) {
return encodeURI(str);
},
bool: function (obj) {
return !!obj;
},
falsy: function (obj) {
return !obj;
},
first: function (iter) {
return iter.idx === 0;
},
last: function (iter) {
return iter.idx === iter.size - 1;
},
call: function (obj, fn) {
return obj[fn].apply(obj, [].slice.call(arguments, 2));
},
set: function (obj, key) {
Mark.globals[key] = obj; return "";
},
log: function (obj) {
console.log(obj);
return obj;
}
};
// Shim for IE.
if (typeof String.prototype.trim !== "function") {
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, "");
}
}
// Export for Node.js and AMD.
if (typeof module !== "undefined" && module.exports) {
module.exports = Mark;
}
else if (typeof define === "function" && define.amd) {
define(function() {
return Mark;
});
}

201
isso/js/lib/pbkdf2.js Normal file
View File

@ -0,0 +1,201 @@
define(["lib/q", "lib/sha1"], function(Q, sha1) {
/*
* JavaScript implementation of Password-Based Key Derivation Function 2
* (PBKDF2) as defined in RFC 2898.
* Version 1.5
* Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Parvez Anandam
* parvez@anandam.com
* http://anandam.com/pbkdf2
*
* Distributed under the BSD license
*
* Uses Paul Johnston's excellent SHA-1 JavaScript library sha1.js:
* http://pajhome.org.uk/crypt/md5/sha1.html
* (uses the binb_sha1(), rstr2binb(), binb2str(), rstr2hex() functions from that libary)
*
* Thanks to Felix Gartsman for pointing out a bug in version 1.0
* Thanks to Thijs Van der Schaeghe for pointing out a bug in version 1.1
* Thanks to Richard Gautier for asking to clarify dependencies in version 1.2
* Updated contact information from version 1.3
* Thanks to Stuart Heinrich for pointing out updates to PAJ's SHA-1 library in version 1.4
*/
/*
* The four arguments to the constructor of the PBKDF2 object are
* the password, salt, number of iterations and number of bytes in
* generated key. This follows the RFC 2898 definition: PBKDF2 (P, S, c, dkLen)
*
* The method deriveKey takes two parameters, both callback functions:
* the first is used to provide status on the computation, the second
* is called with the result of the computation (the generated key in hex).
*
* Example of use:
*
* <script src="sha1.js"></script>
* <script src="pbkdf2.js"></script>
* <script>
* var mypbkdf2 = new PBKDF2("mypassword", "saltines", 1000, 16);
* var status_callback = function(percent_done) {
* document.getElementById("status").innerHTML = "Computed " + percent_done + "%"};
* var result_callback = function(key) {
* document.getElementById("status").innerHTML = "The derived key is: " + key};
* mypbkdf2.deriveKey(status_callback, result_callback);
* </script>
* <div id="status"></div>
*
*/
var PBKDF2 = function(password, salt, num_iterations, num_bytes)
{
// Remember the password and salt
var m_bpassword = sha1.rstr2binb(password);
var m_salt = salt;
// Total number of iterations
var m_total_iterations = num_iterations;
// Run iterations in chunks instead of all at once, so as to not block.
// Define size of chunk here; adjust for slower or faster machines if necessary.
var m_iterations_in_chunk = 10;
// Iteration counter
var m_iterations_done = 0;
// Key length, as number of bytes
var m_key_length = num_bytes;
// The hash cache
var m_hash = null;
// The length (number of bytes) of the output of the pseudo-random function.
// Since HMAC-SHA1 is the standard, and what is used here, it's 20 bytes.
var m_hash_length = 20;
// Number of hash-sized blocks in the derived key (called 'l' in RFC2898)
var m_total_blocks = Math.ceil(m_key_length/m_hash_length);
// Start computation with the first block
var m_current_block = 1;
// Used in the HMAC-SHA1 computations
var m_ipad = new Array(16);
var m_opad = new Array(16);
// This is where the result of the iterations gets sotred
var m_buffer = new Array(0x0,0x0,0x0,0x0,0x0);
// The result
var m_key = "";
// This object
var m_this_object = this;
// The function to call with the result
var m_result_func;
// The function to call with status after computing every chunk
var m_status_func;
// Set up the HMAC-SHA1 computations
if (m_bpassword.length > 16) m_bpassword = sha1.binb_sha1(m_bpassword, password.length * chrsz);
for(var i = 0; i < 16; ++i)
{
m_ipad[i] = m_bpassword[i] ^ 0x36363636;
m_opad[i] = m_bpassword[i] ^ 0x5C5C5C5C;
}
// Starts the computation
this.deriveKey = function(status_callback, result_callback)
{
m_status_func = status_callback;
m_result_func = result_callback;
setTimeout(function() { m_this_object.do_PBKDF2_iterations() }, 0);
}
// The workhorse
this.do_PBKDF2_iterations = function()
{
var iterations = m_iterations_in_chunk;
if (m_total_iterations - m_iterations_done < m_iterations_in_chunk)
iterations = m_total_iterations - m_iterations_done;
for(var i=0; i<iterations; ++i)
{
// compute HMAC-SHA1
if (m_iterations_done == 0)
{
var salt_block = m_salt +
String.fromCharCode(m_current_block >> 24 & 0xF) +
String.fromCharCode(m_current_block >> 16 & 0xF) +
String.fromCharCode(m_current_block >> 8 & 0xF) +
String.fromCharCode(m_current_block & 0xF);
m_hash = sha1.binb_sha1(m_ipad.concat(sha1.rstr2binb(salt_block)),
512 + salt_block.length * 8);
m_hash = sha1.binb_sha1(m_opad.concat(m_hash), 512 + 160);
}
else
{
m_hash = sha1.binb_sha1(m_ipad.concat(m_hash),
512 + m_hash.length * 32);
m_hash = sha1.binb_sha1(m_opad.concat(m_hash), 512 + 160);
}
for(var j=0; j<m_hash.length; ++j)
m_buffer[j] ^= m_hash[j];
m_iterations_done++;
}
// Call the status callback function
m_status_func( (m_current_block - 1 + m_iterations_done/m_total_iterations) / m_total_blocks * 100);
if (m_iterations_done < m_total_iterations)
{
setTimeout(function() { m_this_object.do_PBKDF2_iterations() }, 0);
}
else
{
if (m_current_block < m_total_blocks)
{
// Compute the next block (T_i in RFC 2898)
m_key += sha1.rstr2hex(sha1.binb2rstr(m_buffer));
m_current_block++;
m_buffer = new Array(0x0,0x0,0x0,0x0,0x0);
m_iterations_done = 0;
setTimeout(function() { m_this_object.do_PBKDF2_iterations() }, 0);
}
else
{
// We've computed the final block T_l; we're done.
var tmp = sha1.rstr2hex(sha1.binb2rstr(m_buffer));
m_key += tmp.substr(0, (m_key_length - (m_total_blocks - 1) * m_hash_length) * 2 );
// Call the result callback function
m_result_func(m_key);
}
}
}
}
return function(text, salt, iterations, size) {
var deferred = Q.defer();
Q.when(text, function(text) {
var pbkdf2 = new PBKDF2(text, salt, iterations, size);
pbkdf2.deriveKey(function(bla) {}, function(rv) {
deferred.resolve(rv);
});
})
return deferred.promise;
}
})

337
isso/js/lib/sha1.js Normal file
View File

@ -0,0 +1,337 @@
/*
* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
* in FIPS 180-1
* Version 2.2 Copyright Paul Johnston 2000 - 2009.
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for details.
*/
define(function() {
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_sha1(s) { return rstr2hex(rstr_sha1(str2rstr_utf8(s))); }
function b64_sha1(s) { return rstr2b64(rstr_sha1(str2rstr_utf8(s))); }
function any_sha1(s, e) { return rstr2any(rstr_sha1(str2rstr_utf8(s)), e); }
function hex_hmac_sha1(k, d)
{ return rstr2hex(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
function b64_hmac_sha1(k, d)
{ return rstr2b64(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
function any_hmac_sha1(k, d, e)
{ return rstr2any(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
/*
* Perform a simple self-test to see if the VM is working
*/
function sha1_vm_test()
{
return hex_sha1("abc").toLowerCase() == "a9993e364706816aba3e25717850c26c9cd0d89d";
}
/*
* Calculate the SHA1 of a raw string
*/
function rstr_sha1(s)
{
return binb2rstr(binb_sha1(rstr2binb(s), s.length * 8));
}
/*
* Calculate the HMAC-SHA1 of a key and some data (raw strings)
*/
function rstr_hmac_sha1(key, data)
{
var bkey = rstr2binb(key);
if(bkey.length > 16) bkey = binb_sha1(bkey, key.length * 8);
var ipad = Array(16), opad = Array(16);
for(var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = binb_sha1(ipad.concat(rstr2binb(data)), 512 + data.length * 8);
return binb2rstr(binb_sha1(opad.concat(hash), 512 + 160));
}
/*
* Convert a raw string to a hex string
*/
function rstr2hex(input)
{
try { hexcase } catch(e) { hexcase=0; }
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var output = "";
var x;
for(var i = 0; i < input.length; i++)
{
x = input.charCodeAt(i);
output += hex_tab.charAt((x >>> 4) & 0x0F)
+ hex_tab.charAt( x & 0x0F);
}
return output;
}
/*
* Convert a raw string to a base-64 string
*/
function rstr2b64(input)
{
try { b64pad } catch(e) { b64pad=''; }
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var output = "";
var len = input.length;
for(var i = 0; i < len; i += 3)
{
var triplet = (input.charCodeAt(i) << 16)
| (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
| (i + 2 < len ? input.charCodeAt(i+2) : 0);
for(var j = 0; j < 4; j++)
{
if(i * 8 + j * 6 > input.length * 8) output += b64pad;
else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
}
}
return output;
}
/*
* Convert a raw string to an arbitrary string encoding
*/
function rstr2any(input, encoding)
{
var divisor = encoding.length;
var remainders = Array();
var i, q, x, quotient;
/* Convert to an array of 16-bit big-endian values, forming the dividend */
var dividend = Array(Math.ceil(input.length / 2));
for(i = 0; i < dividend.length; i++)
{
dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
}
/*
* Repeatedly perform a long division. The binary array forms the dividend,
* the length of the encoding is the divisor. Once computed, the quotient
* forms the dividend for the next step. We stop when the dividend is zero.
* All remainders are stored for later use.
*/
while(dividend.length > 0)
{
quotient = Array();
x = 0;
for(i = 0; i < dividend.length; i++)
{
x = (x << 16) + dividend[i];
q = Math.floor(x / divisor);
x -= q * divisor;
if(quotient.length > 0 || q > 0)
quotient[quotient.length] = q;
}
remainders[remainders.length] = x;
dividend = quotient;
}
/* Convert the remainders to the output string */
var output = "";
for(i = remainders.length - 1; i >= 0; i--)
output += encoding.charAt(remainders[i]);
/* Append leading zero equivalents */
var full_length = Math.ceil(input.length * 8 /
(Math.log(encoding.length) / Math.log(2)))
for(i = output.length; i < full_length; i++)
output = encoding[0] + output;
return output;
}
/*
* Encode a string as utf-8.
* For efficiency, this assumes the input is valid utf-16.
*/
function str2rstr_utf8(input)
{
var output = "";
var i = -1;
var x, y;
while(++i < input.length)
{
/* Decode utf-16 surrogate pairs */
x = input.charCodeAt(i);
y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
{
x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
i++;
}
/* Encode output as utf-8 */
if(x <= 0x7F)
output += String.fromCharCode(x);
else if(x <= 0x7FF)
output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
0x80 | ( x & 0x3F));
else if(x <= 0xFFFF)
output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
else if(x <= 0x1FFFFF)
output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
0x80 | ((x >>> 12) & 0x3F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
}
return output;
}
/*
* Encode a string as utf-16
*/
function str2rstr_utf16le(input)
{
var output = "";
for(var i = 0; i < input.length; i++)
output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
(input.charCodeAt(i) >>> 8) & 0xFF);
return output;
}
function str2rstr_utf16be(input)
{
var output = "";
for(var i = 0; i < input.length; i++)
output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
input.charCodeAt(i) & 0xFF);
return output;
}
/*
* Convert a raw string to an array of big-endian words
* Characters >255 have their high-byte silently ignored.
*/
function rstr2binb(input)
{
var output = Array(input.length >> 2);
for(var i = 0; i < output.length; i++)
output[i] = 0;
for(var i = 0; i < input.length * 8; i += 8)
output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32);
return output;
}
/*
* Convert an array of big-endian words to a string
*/
function binb2rstr(input)
{
var output = "";
for(var i = 0; i < input.length * 32; i += 8)
output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF);
return output;
}
/*
* Calculate the SHA-1 of an array of big-endian words, and a bit length
*/
function binb_sha1(x, len)
{
/* append padding */
x[len >> 5] |= 0x80 << (24 - len % 32);
x[((len + 64 >> 9) << 4) + 15] = len;
var w = Array(80);
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
var e = -1009589776;
for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
var olde = e;
for(var j = 0; j < 80; j++)
{
if(j < 16) w[j] = x[i + j];
else w[j] = bit_rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
var t = safe_add(safe_add(bit_rol(a, 5), sha1_ft(j, b, c, d)),
safe_add(safe_add(e, w[j]), sha1_kt(j)));
e = d;
d = c;
c = bit_rol(b, 30);
b = a;
a = t;
}
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
e = safe_add(e, olde);
}
return Array(a, b, c, d, e);
}
/*
* Perform the appropriate triplet combination function for the current
* iteration
*/
function sha1_ft(t, b, c, d)
{
if(t < 20) return (b & c) | ((~b) & d);
if(t < 40) return b ^ c ^ d;
if(t < 60) return (b & c) | (b & d) | (c & d);
return b ^ c ^ d;
}
/*
* Determine the appropriate additive constant for the current iteration
*/
function sha1_kt(t)
{
return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
(t < 60) ? -1894007588 : -899497514;
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function bit_rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
return {
rstr2hex: rstr2hex, binb2rstr: binb2rstr,
binb_sha1: binb_sha1, rstr2binb: rstr2binb
}
})

386
isso/js/text.js Normal file
View File

@ -0,0 +1,386 @@
/**
* @license RequireJS text 2.0.10 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/requirejs/text for details
*/
/*jslint regexp: true */
/*global require, XMLHttpRequest, ActiveXObject,
define, window, process, Packages,
java, location, Components, FileUtils */
define(['module'], function (module) {
'use strict';
var text, fs, Cc, Ci, xpcIsWindows,
progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],
xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,
bodyRegExp = /<body[^>]*>\s*([\s\S]+)\s*<\/body>/im,
hasLocation = typeof location !== 'undefined' && location.href,
defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''),
defaultHostName = hasLocation && location.hostname,
defaultPort = hasLocation && (location.port || undefined),
buildMap = {},
masterConfig = (module.config && module.config()) || {};
text = {
version: '2.0.10',
strip: function (content) {
//Strips <?xml ...?> declarations so that external SVG and XML
//documents can be added to a document without worry. Also, if the string
//is an HTML document, only the part inside the body tag is returned.
if (content) {
content = content.replace(xmlRegExp, "");
var matches = content.match(bodyRegExp);
if (matches) {
content = matches[1];
}
} else {
content = "";
}
return content;
},
jsEscape: function (content) {
return content.replace(/(['\\])/g, '\\$1')
.replace(/[\f]/g, "\\f")
.replace(/[\b]/g, "\\b")
.replace(/[\n]/g, "\\n")
.replace(/[\t]/g, "\\t")
.replace(/[\r]/g, "\\r")
.replace(/[\u2028]/g, "\\u2028")
.replace(/[\u2029]/g, "\\u2029");
},
createXhr: masterConfig.createXhr || function () {
//Would love to dump the ActiveX crap in here. Need IE 6 to die first.
var xhr, i, progId;
if (typeof XMLHttpRequest !== "undefined") {
return new XMLHttpRequest();
} else if (typeof ActiveXObject !== "undefined") {
for (i = 0; i < 3; i += 1) {
progId = progIds[i];
try {
xhr = new ActiveXObject(progId);
} catch (e) {}
if (xhr) {
progIds = [progId]; // so faster next time
break;
}
}
}
return xhr;
},
/**
* Parses a resource name into its component parts. Resource names
* look like: module/name.ext!strip, where the !strip part is
* optional.
* @param {String} name the resource name
* @returns {Object} with properties "moduleName", "ext" and "strip"
* where strip is a boolean.
*/
parseName: function (name) {
var modName, ext, temp,
strip = false,
index = name.indexOf("."),
isRelative = name.indexOf('./') === 0 ||
name.indexOf('../') === 0;
if (index !== -1 && (!isRelative || index > 1)) {
modName = name.substring(0, index);
ext = name.substring(index + 1, name.length);
} else {
modName = name;
}
temp = ext || modName;
index = temp.indexOf("!");
if (index !== -1) {
//Pull off the strip arg.
strip = temp.substring(index + 1) === "strip";
temp = temp.substring(0, index);
if (ext) {
ext = temp;
} else {
modName = temp;
}
}
return {
moduleName: modName,
ext: ext,
strip: strip
};
},
xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/,
/**
* Is an URL on another domain. Only works for browser use, returns
* false in non-browser environments. Only used to know if an
* optimized .js version of a text resource should be loaded
* instead.
* @param {String} url
* @returns Boolean
*/
useXhr: function (url, protocol, hostname, port) {
var uProtocol, uHostName, uPort,
match = text.xdRegExp.exec(url);
if (!match) {
return true;
}
uProtocol = match[2];
uHostName = match[3];
uHostName = uHostName.split(':');
uPort = uHostName[1];
uHostName = uHostName[0];
return (!uProtocol || uProtocol === protocol) &&
(!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) &&
((!uPort && !uHostName) || uPort === port);
},
finishLoad: function (name, strip, content, onLoad) {
content = strip ? text.strip(content) : content;
if (masterConfig.isBuild) {
buildMap[name] = content;
}
onLoad(content);
},
load: function (name, req, onLoad, config) {
//Name has format: some.module.filext!strip
//The strip part is optional.
//if strip is present, then that means only get the string contents
//inside a body tag in an HTML string. For XML/SVG content it means
//removing the <?xml ...?> declarations so the content can be inserted
//into the current doc without problems.
// Do not bother with the work if a build and text will
// not be inlined.
if (config.isBuild && !config.inlineText) {
onLoad();
return;
}
masterConfig.isBuild = config.isBuild;
var parsed = text.parseName(name),
nonStripName = parsed.moduleName +
(parsed.ext ? '.' + parsed.ext : ''),
url = req.toUrl(nonStripName),
useXhr = (masterConfig.useXhr) ||
text.useXhr;
// Do not load if it is an empty: url
if (url.indexOf('empty:') === 0) {
onLoad();
return;
}
//Load the text. Use XHR if possible and in a browser.
if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) {
text.get(url, function (content) {
text.finishLoad(name, parsed.strip, content, onLoad);
}, function (err) {
if (onLoad.error) {
onLoad.error(err);
}
});
} else {
//Need to fetch the resource across domains. Assume
//the resource has been optimized into a JS module. Fetch
//by the module name + extension, but do not include the
//!strip part to avoid file system issues.
req([nonStripName], function (content) {
text.finishLoad(parsed.moduleName + '.' + parsed.ext,
parsed.strip, content, onLoad);
});
}
},
write: function (pluginName, moduleName, write, config) {
if (buildMap.hasOwnProperty(moduleName)) {
var content = text.jsEscape(buildMap[moduleName]);
write.asModule(pluginName + "!" + moduleName,
"define(function () { return '" +
content +
"';});\n");
}
},
writeFile: function (pluginName, moduleName, req, write, config) {
var parsed = text.parseName(moduleName),
extPart = parsed.ext ? '.' + parsed.ext : '',
nonStripName = parsed.moduleName + extPart,
//Use a '.js' file name so that it indicates it is a
//script that can be loaded across domains.
fileName = req.toUrl(parsed.moduleName + extPart) + '.js';
//Leverage own load() method to load plugin value, but only
//write out values that do not have the strip argument,
//to avoid any potential issues with ! in file names.
text.load(nonStripName, req, function (value) {
//Use own write() method to construct full module value.
//But need to create shell that translates writeFile's
//write() to the right interface.
var textWrite = function (contents) {
return write(fileName, contents);
};
textWrite.asModule = function (moduleName, contents) {
return write.asModule(moduleName, fileName, contents);
};
text.write(pluginName, nonStripName, textWrite, config);
}, config);
}
};
if (masterConfig.env === 'node' || (!masterConfig.env &&
typeof process !== "undefined" &&
process.versions &&
!!process.versions.node &&
!process.versions['node-webkit'])) {
//Using special require.nodeRequire, something added by r.js.
fs = require.nodeRequire('fs');
text.get = function (url, callback, errback) {
try {
var file = fs.readFileSync(url, 'utf8');
//Remove BOM (Byte Mark Order) from utf8 files if it is there.
if (file.indexOf('\uFEFF') === 0) {
file = file.substring(1);
}
callback(file);
} catch (e) {
errback(e);
}
};
} else if (masterConfig.env === 'xhr' || (!masterConfig.env &&
text.createXhr())) {
text.get = function (url, callback, errback, headers) {
var xhr = text.createXhr(), header;
xhr.open('GET', url, true);
//Allow plugins direct access to xhr headers
if (headers) {
for (header in headers) {
if (headers.hasOwnProperty(header)) {
xhr.setRequestHeader(header.toLowerCase(), headers[header]);
}
}
}
//Allow overrides specified in config
if (masterConfig.onXhr) {
masterConfig.onXhr(xhr, url);
}
xhr.onreadystatechange = function (evt) {
var status, err;
//Do not explicitly handle errors, those should be
//visible via console output in the browser.
if (xhr.readyState === 4) {
status = xhr.status;
if (status > 399 && status < 600) {
//An http 4xx or 5xx error. Signal an error.
err = new Error(url + ' HTTP status: ' + status);
err.xhr = xhr;
errback(err);
} else {
callback(xhr.responseText);
}
if (masterConfig.onXhrComplete) {
masterConfig.onXhrComplete(xhr, url);
}
}
};
xhr.send(null);
};
} else if (masterConfig.env === 'rhino' || (!masterConfig.env &&
typeof Packages !== 'undefined' && typeof java !== 'undefined')) {
//Why Java, why is this so awkward?
text.get = function (url, callback) {
var stringBuffer, line,
encoding = "utf-8",
file = new java.io.File(url),
lineSeparator = java.lang.System.getProperty("line.separator"),
input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)),
content = '';
try {
stringBuffer = new java.lang.StringBuffer();
line = input.readLine();
// Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324
// http://www.unicode.org/faq/utf_bom.html
// Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK:
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058
if (line && line.length() && line.charAt(0) === 0xfeff) {
// Eat the BOM, since we've already found the encoding on this file,
// and we plan to concatenating this buffer with others; the BOM should
// only appear at the top of a file.
line = line.substring(1);
}
if (line !== null) {
stringBuffer.append(line);
}
while ((line = input.readLine()) !== null) {
stringBuffer.append(lineSeparator);
stringBuffer.append(line);
}
//Make sure we return a JavaScript string and not a Java string.
content = String(stringBuffer.toString()); //String
} finally {
input.close();
}
callback(content);
};
} else if (masterConfig.env === 'xpconnect' || (!masterConfig.env &&
typeof Components !== 'undefined' && Components.classes &&
Components.interfaces)) {
//Avert your gaze!
Cc = Components.classes,
Ci = Components.interfaces;
Components.utils['import']('resource://gre/modules/FileUtils.jsm');
xpcIsWindows = ('@mozilla.org/windows-registry-key;1' in Cc);
text.get = function (url, callback) {
var inStream, convertStream, fileObj,
readData = {};
if (xpcIsWindows) {
url = url.replace(/\//g, '\\');
}
fileObj = new FileUtils.File(url);
//XPCOM, you so crazy
try {
inStream = Cc['@mozilla.org/network/file-input-stream;1']
.createInstance(Ci.nsIFileInputStream);
inStream.init(fileObj, 1, 0, false);
convertStream = Cc['@mozilla.org/intl/converter-input-stream;1']
.createInstance(Ci.nsIConverterInputStream);
convertStream.init(inStream, "utf-8", inStream.available(),
Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
convertStream.readString(inStream.available(), readData);
convertStream.close();
inStream.close();
callback(readData.value);
} catch (e) {
throw new Error((fileObj && fileObj.path || '') + ': ' + e);
}
};
}
return text;
});

6
isso/js/text/arrow-down.svg Executable file
View File

@ -0,0 +1,6 @@
<!-- Generator: IcoMoon.io --><svg width="16" height="16" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="gray">
<g>
<path d="M 24.773,13.701c-0.651,0.669-7.512,7.205-7.512,7.205C 16.912,21.262, 16.456,21.44, 16,21.44c-0.458,0-0.914-0.178-1.261-0.534 c0,0-6.861-6.536-7.514-7.205c-0.651-0.669-0.696-1.87,0-2.586c 0.698-0.714, 1.669-0.77, 2.522,0L 16,17.112l 6.251-5.995 c 0.854-0.77, 1.827-0.714, 2.522,0C 25.47,11.83, 25.427,13.034, 24.773,13.701z">
</path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 539 B

6
isso/js/text/arrow-up.svg Executable file
View File

@ -0,0 +1,6 @@
<!-- Generator: IcoMoon.io --><svg width="16" height="16" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="gray">
<g>
<path d="M 24.773,18.299c-0.651-0.669-7.512-7.203-7.512-7.203C 16.912,10.739, 16.456,10.56, 16,10.56c-0.458,0-0.914,0.179-1.261,0.536 c0,0-6.861,6.534-7.514,7.203c-0.651,0.669-0.696,1.872,0,2.586c 0.698,0.712, 1.669,0.77, 2.522,0L 16,14.89l 6.251,5.995 c 0.854,0.77, 1.827,0.712, 2.522,0C 25.47,20.17, 25.427,18.966, 24.773,18.299z">
</path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 539 B

61
isso/js/text/comment.html Normal file
View File

@ -0,0 +1,61 @@
<article class="isso-comment" id="isso-{{ id | blank }}">
<div class="avatar">
<canvas data-hash="{{ hash }}" height="48px" width="48px"></canvas>
</div>
<div class="text-wrapper">
<header>
{{ if bool(website) }}
<a class="author" href="{{ website }}" rel="nofollow">
{{ author | blank : Anonymous }}
</a>
{{ else }}
<span class="author">
{{ author | blank : Anonymous }}
</span>
{{ /if }}
{{ if parent }}
<span class="spacer"></span>
<a class="parent" href="#isso-{{ parent }}"><i>{{ svg-forward }}</i>{{ replyto }}</a>
{{ /if }}
<span class="spacer"></span>
<a class="permalink" href="#isso-{{ id }}">
<date datetime="{{ created | datetime }}"></date>
</a>
<span class="note">
{{ if mode | equals : 2 }}
Kommentar muss noch freigeschaltet werden.
{{ /if }}
{{ if mode | equals : 4 }}
Kommentar gelöscht.
{{ /if }}
</span>
</header>
<div class="text">
{{ if mode | equals : 4 }}
<p>&nbsp;</p>
{{ else }}
{{ text }}
{{ /if }}
</div>
<footer>
{{ if likes | substract : `dislikes` | notequals : 0 }}
<span class="votes">{{ likes | substract : `dislikes` }}</span>
{{ /if }}
<a class="upvote" href="#"><i>{{ svg-arrow-up}}</i></a>
<span class="spacer">|</span>
<a class="downvote" href="#"><i>{{ svg-arrow-down}}</i></a>
<a class="reply" href="#">{{ i18n-comment-reply }}</a>
<!--<a class="edit" href="#">Bearbeiten</a>-->
<a class="delete" href="#">{{ i18n-comment-delete }}</a>
</footer>
<div class="isso-follow-up">
</div>
</div>
</article>

6
isso/js/text/forward.svg Executable file
View File

@ -0,0 +1,6 @@
<!-- Generator: IcoMoon.io --><svg width="10" height="10" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="gray">
<g>
<path d="M 17.961,11.954L 17.961,2 L 32,16L 17.961,30L 17.961,19.958 C 10.826,19.958, 3.784,21.2,0,27.094 C 0.394,16.353, 8.43,13.796, 17.961,11.954z">
</path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 357 B

6
isso/js/text/html.js Normal file
View File

@ -0,0 +1,6 @@
define(["text!./postbox.html", "text!./comment.html"], function (postbox, comment) {
return {
postbox: postbox,
comment: comment
};
});

21
isso/js/text/postbox.html Normal file
View File

@ -0,0 +1,21 @@
<div class="postbox">
<div class="avatar">
<canvas class="placeholder" height="48px" width="48px"></canvas>
</div>
<div class="form-wrapper">
<div class="textarea-wrapper">
<textarea name="text" rows="3" placeholder="{{ i18n-postbox-text }}"></textarea>
</div>
<section class="auth-section">
<p class="input-wrapper">
<input type="text" name="author" placeholder="{{ i18n-postbox-author }}"/>
</p>
<p class="input-wrapper">
<input type="email" name="email" placeholder="{{ i18n-postbox-email }}"/>
</p>
<p class="post-action">
<input type="submit" value="{{ i18n-postbox-submit }}"/>
</p>
</section>
</div>
</div>

7
isso/js/text/svg.js Normal file
View File

@ -0,0 +1,7 @@
define(["text!./forward.svg", "text!./arrow-down.svg", "text!./arrow-up.svg"], function (forward, arrdown, arrup) {
return {
"forward": forward,
"arrow-down": arrdown,
"arrow-up": arrup
};
});

View File

@ -31,6 +31,10 @@
text-shadow: #aaaaaa 0px 0px 1px; text-shadow: #aaaaaa 0px 0px 1px;
} }
span.votes, a.like, a.dislike {
color: gray;
}
.isso-comment > header, .isso-comment > footer { .isso-comment > header, .isso-comment > footer {
font-family: "Helvetica", Arial, sans-serif; font-family: "Helvetica", Arial, sans-serif;
font-size: 0.80em; font-size: 0.80em;

View File

@ -3,8 +3,10 @@
<title>Hello World</title> <title>Hello World</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" href="/static/isso.css" /> <link rel="stylesheet" type="text/css" href="/static/sass/new.css" />
<script data-main="/js/embed" src="/js/require.js"></script> <!--<script src="/static/less.js"></script>-->
<script data-main="/isso/js/embed" src="/isso/js/require.js"></script>
<!--<script src="/isso/js/embed.min.js"></script>-->
<style type="text/css"> <style type="text/css">
body { body {
margin: 0 auto; margin: 0 auto;

View File

@ -0,0 +1,13 @@
//************************************************************************//
// These mixins/functions are deprecated
// They will be removed in the next MAJOR version release
//************************************************************************//
@mixin box-shadow ($shadows...) {
@include prefixer(box-shadow, $shadows, spec);
@warn "box-shadow is deprecated and will be removed in the next major version release";
}
@mixin background-size ($lengths...) {
@include prefixer(background-size, $lengths, spec);
@warn "background-size is deprecated and will be removed in the next major version release";
}

59
isso/static/sass/bourbon/_bourbon.scss vendored Normal file
View File

@ -0,0 +1,59 @@
// Custom Helpers
@import "helpers/deprecated-webkit-gradient";
@import "helpers/gradient-positions-parser";
@import "helpers/linear-positions-parser";
@import "helpers/radial-arg-parser";
@import "helpers/radial-positions-parser";
@import "helpers/render-gradients";
@import "helpers/shape-size-stripper";
// Custom Functions
@import "functions/compact";
@import "functions/flex-grid";
@import "functions/grid-width";
@import "functions/linear-gradient";
@import "functions/modular-scale";
@import "functions/px-to-em";
@import "functions/radial-gradient";
@import "functions/tint-shade";
@import "functions/transition-property-name";
// CSS3 Mixins
@import "css3/animation";
@import "css3/appearance";
@import "css3/backface-visibility";
@import "css3/background";
@import "css3/background-image";
@import "css3/border-image";
@import "css3/border-radius";
@import "css3/box-sizing";
@import "css3/columns";
@import "css3/flex-box";
@import "css3/font-face";
@import "css3/hidpi-media-query";
@import "css3/image-rendering";
@import "css3/inline-block";
@import "css3/keyframes";
@import "css3/linear-gradient";
@import "css3/perspective";
@import "css3/radial-gradient";
@import "css3/transform";
@import "css3/transition";
@import "css3/user-select";
@import "css3/placeholder";
// Addons & other mixins
@import "addons/button";
@import "addons/clearfix";
@import "addons/font-family";
@import "addons/hide-text";
@import "addons/html5-input-types";
@import "addons/position";
@import "addons/prefixer";
@import "addons/retina-image";
@import "addons/size";
@import "addons/timing-functions";
@import "addons/triangle";
// Soon to be deprecated Mixins
@import "bourbon-deprecated-upcoming";

View File

@ -0,0 +1,273 @@
@mixin button ($style: simple, $base-color: #4294f0) {
@if type-of($style) == color {
$base-color: $style;
$style: simple;
}
// Grayscale button
@if $base-color == grayscale($base-color) {
@if $style == simple {
@include simple($base-color, $grayscale: true);
}
@else if $style == shiny {
@include shiny($base-color, $grayscale: true);
}
@else if $style == pill {
@include pill($base-color, $grayscale: true);
}
}
// Colored button
@else {
@if $style == simple {
@include simple($base-color);
}
@else if $style == shiny {
@include shiny($base-color);
}
@else if $style == pill {
@include pill($base-color);
}
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
// Simple Button
//************************************************************************//
@mixin simple($base-color, $grayscale: false) {
$color: hsl(0, 0, 100%);
$border: adjust-color($base-color, $saturation: 9%, $lightness: -14%);
$inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%);
$stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%);
$text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%);
@if lightness($base-color) > 70% {
$color: hsl(0, 0, 20%);
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
}
@if $grayscale == true {
$border: grayscale($border);
$inset-shadow: grayscale($inset-shadow);
$stop-gradient: grayscale($stop-gradient);
$text-shadow: grayscale($text-shadow);
}
border: 1px solid $border;
border-radius: 3px;
box-shadow: inset 0 1px 0 0 $inset-shadow;
color: $color;
display: inline-block;
font-size: 11px;
font-weight: bold;
@include linear-gradient ($base-color, $stop-gradient);
padding: 7px 18px;
text-decoration: none;
text-shadow: 0 1px 0 $text-shadow;
background-clip: padding-box;
&:hover:not(:disabled) {
$base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%);
$inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%);
$stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%);
@if $grayscale == true {
$base-color-hover: grayscale($base-color-hover);
$inset-shadow-hover: grayscale($inset-shadow-hover);
$stop-gradient-hover: grayscale($stop-gradient-hover);
}
box-shadow: inset 0 1px 0 0 $inset-shadow-hover;
cursor: pointer;
@include linear-gradient ($base-color-hover, $stop-gradient-hover);
}
&:active:not(:disabled) {
$border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%);
$inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%);
@if $grayscale == true {
$border-active: grayscale($border-active);
$inset-shadow-active: grayscale($inset-shadow-active);
}
border: 1px solid $border-active;
box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active, 0 1px 1px 0 #eee;
}
}
// Shiny Button
//************************************************************************//
@mixin shiny($base-color, $grayscale: false) {
$color: hsl(0, 0, 100%);
$border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81);
$border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122);
$fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46);
$inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12);
$second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33);
$text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114);
$third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48);
@if lightness($base-color) > 70% {
$color: hsl(0, 0, 20%);
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
}
@if $grayscale == true {
$border: grayscale($border);
$border-bottom: grayscale($border-bottom);
$fourth-stop: grayscale($fourth-stop);
$inset-shadow: grayscale($inset-shadow);
$second-stop: grayscale($second-stop);
$text-shadow: grayscale($text-shadow);
$third-stop: grayscale($third-stop);
}
border: 1px solid $border;
border-bottom: 1px solid $border-bottom;
border-radius: 5px;
box-shadow: inset 0 1px 0 0 $inset-shadow;
color: $color;
display: inline-block;
font-size: 14px;
font-weight: bold;
@include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%);
padding: 8px 20px;
text-align: center;
text-decoration: none;
text-shadow: 0 -1px 1px $text-shadow;
&:hover:not(:disabled) {
$first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18);
$second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51);
$third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66);
$fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63);
@if $grayscale == true {
$first-stop-hover: grayscale($first-stop-hover);
$second-stop-hover: grayscale($second-stop-hover);
$third-stop-hover: grayscale($third-stop-hover);
$fourth-stop-hover: grayscale($fourth-stop-hover);
}
cursor: pointer;
@include linear-gradient(top, $first-stop-hover 0%,
$second-stop-hover 50%,
$third-stop-hover 50%,
$fourth-stop-hover 100%);
}
&:active:not(:disabled) {
$inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122);
@if $grayscale == true {
$inset-shadow-active: grayscale($inset-shadow-active);
}
box-shadow: inset 0 0 20px 0 $inset-shadow-active, 0 1px 0 #fff;
}
}
// Pill Button
//************************************************************************//
@mixin pill($base-color, $grayscale: false) {
$color: hsl(0, 0, 100%);
$border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%);
$border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%);
$border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%);
$inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%);
$stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%);
$text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%);
@if lightness($base-color) > 70% {
$color: hsl(0, 0, 20%);
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
}
@if $grayscale == true {
$border-bottom: grayscale($border-bottom);
$border-sides: grayscale($border-sides);
$border-top: grayscale($border-top);
$inset-shadow: grayscale($inset-shadow);
$stop-gradient: grayscale($stop-gradient);
$text-shadow: grayscale($text-shadow);
}
border: 1px solid $border-top;
border-color: $border-top $border-sides $border-bottom;
border-radius: 16px;
box-shadow: inset 0 1px 0 0 $inset-shadow, 0 1px 2px 0 #b3b3b3;
color: $color;
display: inline-block;
font-size: 11px;
font-weight: normal;
line-height: 1;
@include linear-gradient ($base-color, $stop-gradient);
padding: 5px 16px;
text-align: center;
text-decoration: none;
text-shadow: 0 -1px 1px $text-shadow;
background-clip: padding-box;
&:hover:not(:disabled) {
$base-color-hover: adjust-color($base-color, $lightness: -4.5%);
$border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%);
$border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%);
$border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%);
$inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%);
$stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%);
$text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%);
@if $grayscale == true {
$base-color-hover: grayscale($base-color-hover);
$border-bottom: grayscale($border-bottom);
$border-sides: grayscale($border-sides);
$border-top: grayscale($border-top);
$inset-shadow-hover: grayscale($inset-shadow-hover);
$stop-gradient-hover: grayscale($stop-gradient-hover);
$text-shadow-hover: grayscale($text-shadow-hover);
}
border: 1px solid $border-top;
border-color: $border-top $border-sides $border-bottom;
box-shadow: inset 0 1px 0 0 $inset-shadow-hover;
cursor: pointer;
@include linear-gradient ($base-color-hover, $stop-gradient-hover);
text-shadow: 0 -1px 1px $text-shadow-hover;
background-clip: padding-box;
}
&:active:not(:disabled) {
$active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%);
$border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%);
$border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%);
$inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%);
$text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%);
@if $grayscale == true {
$active-color: grayscale($active-color);
$border-active: grayscale($border-active);
$border-bottom-active: grayscale($border-bottom-active);
$inset-shadow-active: grayscale($inset-shadow-active);
$text-shadow-active: grayscale($text-shadow-active);
}
background: $active-color;
border: 1px solid $border-active;
border-bottom: 1px solid $border-bottom-active;
box-shadow: inset 0 0 6px 3px $inset-shadow-active, 0 1px 0 0 #fff;
text-shadow: 0 -1px 1px $text-shadow-active;
}
}

View File

@ -0,0 +1,29 @@
// Micro clearfix provides an easy way to contain floats without adding additional markup
//
// Example usage:
//
// // Contain all floats within .wrapper
// .wrapper {
// @include clearfix;
// .content,
// .sidebar {
// float : left;
// }
// }
@mixin clearfix {
*zoom: 1;
&:before,
&:after {
content: " ";
display: table;
}
&:after {
clear: both;
}
}
// Acknowledgements
// Micro clearfix: [Nicolas Gallagher](http://nicolasgallagher.com/micro-clearfix-hack/)

View File

@ -0,0 +1,5 @@
$georgia: Georgia, Cambria, "Times New Roman", Times, serif;
$helvetica: "Helvetica Neue", Helvetica, Arial, sans-serif;
$lucida-grande: "Lucida Grande", Tahoma, Verdana, Arial, sans-serif;
$monospace: "Bitstream Vera Sans Mono", Consolas, Courier, monospace;
$verdana: Verdana, Geneva, sans-serif;

View File

@ -0,0 +1,5 @@
@mixin hide-text {
color: transparent;
font: 0/0 a;
text-shadow: none;
}

View File

@ -0,0 +1,56 @@
//************************************************************************//
// Generate a variable ($all-text-inputs) with a list of all html5
// input types that have a text-based input, excluding textarea.
// http://diveintohtml5.org/forms.html
//************************************************************************//
$inputs-list: 'input[type="email"]',
'input[type="number"]',
'input[type="password"]',
'input[type="search"]',
'input[type="tel"]',
'input[type="text"]',
'input[type="url"]',
// Webkit & Gecko may change the display of these in the future
'input[type="color"]',
'input[type="date"]',
'input[type="datetime"]',
'input[type="datetime-local"]',
'input[type="month"]',
'input[type="time"]',
'input[type="week"]';
$unquoted-inputs-list: ();
@each $input-type in $inputs-list {
$unquoted-inputs-list: append($unquoted-inputs-list, unquote($input-type), comma);
}
$all-text-inputs: $unquoted-inputs-list;
// Hover Pseudo-class
//************************************************************************//
$all-text-inputs-hover: ();
@each $input-type in $unquoted-inputs-list {
$input-type-hover: $input-type + ":hover";
$all-text-inputs-hover: append($all-text-inputs-hover, $input-type-hover, comma);
}
// Focus Pseudo-class
//************************************************************************//
$all-text-inputs-focus: ();
@each $input-type in $unquoted-inputs-list {
$input-type-focus: $input-type + ":focus";
$all-text-inputs-focus: append($all-text-inputs-focus, $input-type-focus, comma);
}
// You must use interpolation on the variable:
// #{$all-text-inputs}
// #{$all-text-inputs-hover}
// #{$all-text-inputs-focus}
// Example
//************************************************************************//
// #{$all-text-inputs}, textarea {
// border: 1px solid red;
// }

View File

@ -0,0 +1,42 @@
@mixin position ($position: relative, $coordinates: 0 0 0 0) {
@if type-of($position) == list {
$coordinates: $position;
$position: relative;
}
$top: nth($coordinates, 1);
$right: nth($coordinates, 2);
$bottom: nth($coordinates, 3);
$left: nth($coordinates, 4);
position: $position;
@if $top == auto {
top: $top;
}
@else if not(unitless($top)) {
top: $top;
}
@if $right == auto {
right: $right;
}
@else if not(unitless($right)) {
right: $right;
}
@if $bottom == auto {
bottom: $bottom;
}
@else if not(unitless($bottom)) {
bottom: $bottom;
}
@if $left == auto {
left: $left;
}
@else if not(unitless($left)) {
left: $left;
}
}

View File

@ -0,0 +1,49 @@
//************************************************************************//
// Example: @include prefixer(border-radius, $radii, webkit ms spec);
//************************************************************************//
$prefix-for-webkit: true !default;
$prefix-for-mozilla: true !default;
$prefix-for-microsoft: true !default;
$prefix-for-opera: true !default;
$prefix-for-spec: true !default; // required for keyframe mixin
@mixin prefixer ($property, $value, $prefixes) {
@each $prefix in $prefixes {
@if $prefix == webkit {
@if $prefix-for-webkit {
-webkit-#{$property}: $value;
}
}
@else if $prefix == moz {
@if $prefix-for-mozilla {
-moz-#{$property}: $value;
}
}
@else if $prefix == ms {
@if $prefix-for-microsoft {
-ms-#{$property}: $value;
}
}
@else if $prefix == o {
@if $prefix-for-opera {
-o-#{$property}: $value;
}
}
@else if $prefix == spec {
@if $prefix-for-spec {
#{$property}: $value;
}
}
@else {
@warn "Unrecognized prefix: #{$prefix}";
}
}
}
@mixin disable-prefix-for-all() {
$prefix-for-webkit: false;
$prefix-for-mozilla: false;
$prefix-for-microsoft: false;
$prefix-for-opera: false;
$prefix-for-spec: false;
}

View File

@ -0,0 +1,32 @@
@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $asset-pipeline: false) {
@if $asset-pipeline {
background-image: image-url("#{$filename}.#{$extension}");
}
@else {
background-image: url("#{$filename}.#{$extension}");
}
@include hidpi {
@if $asset-pipeline {
@if $retina-filename {
background-image: image-url("#{$retina-filename}.#{$extension}");
}
@else {
background-image: image-url("#{$filename}@2x.#{$extension}");
}
}
@else {
@if $retina-filename {
background-image: url("#{$retina-filename}.#{$extension}");
}
@else {
background-image: url("#{$filename}@2x.#{$extension}");
}
}
background-size: $background-size;
}
}

View File

@ -0,0 +1,44 @@
@mixin size($size) {
@if length($size) == 1 {
@if $size == auto {
width: $size;
height: $size;
}
@else if unitless($size) {
width: $size + px;
height: $size + px;
}
@else if not(unitless($size)) {
width: $size;
height: $size;
}
}
// Width x Height
@if length($size) == 2 {
$width: nth($size, 1);
$height: nth($size, 2);
@if $width == auto {
width: $width;
}
@else if not(unitless($width)) {
width: $width;
}
@else if unitless($width) {
width: $width + px;
}
@if $height == auto {
height: $height;
}
@else if not(unitless($height)) {
height: $height;
}
@else if unitless($height) {
height: $height + px;
}
}
}

View File

@ -0,0 +1,32 @@
// CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie)
// Timing functions are the same as demo'ed here: http://jqueryui.com/demos/effect/easing.html
// EASE IN
$ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530);
$ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190);
$ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220);
$ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060);
$ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715);
$ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035);
$ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335);
$ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045);
// EASE OUT
$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940);
$ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000);
$ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000);
$ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000);
$ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000);
$ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000);
$ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000);
$ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275);
// EASE IN OUT
$ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955);
$ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000);
$ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000);
$ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000);
$ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950);
$ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000);
$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860);
$ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550);

View File

@ -0,0 +1,45 @@
@mixin triangle ($size, $color, $direction) {
height: 0;
width: 0;
@if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) {
border-color: transparent;
border-style: solid;
border-width: $size / 2;
@if $direction == up {
border-bottom-color: $color;
} @else if $direction == right {
border-left-color: $color;
} @else if $direction == down {
border-top-color: $color;
} @else if $direction == left {
border-right-color: $color;
}
}
@else if ($direction == up-right) or ($direction == up-left) {
border-top: $size solid $color;
@if $direction == up-right {
border-left: $size solid transparent;
} @else if $direction == up-left {
border-right: $size solid transparent;
}
}
@else if ($direction == down-right) or ($direction == down-left) {
border-bottom: $size solid $color;
@if $direction == down-right {
border-left: $size solid transparent;
} @else if $direction == down-left {
border-right: $size solid transparent;
}
}
}

View File

@ -0,0 +1,52 @@
// http://www.w3.org/TR/css3-animations/#the-animation-name-property-
// Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties.
// Official animation shorthand property.
@mixin animation ($animations...) {
@include prefixer(animation, $animations, webkit moz spec);
}
// Individual Animation Properties
@mixin animation-name ($names...) {
@include prefixer(animation-name, $names, webkit moz spec);
}
@mixin animation-duration ($times...) {
@include prefixer(animation-duration, $times, webkit moz spec);
}
@mixin animation-timing-function ($motions...) {
// ease | linear | ease-in | ease-out | ease-in-out
@include prefixer(animation-timing-function, $motions, webkit moz spec);
}
@mixin animation-iteration-count ($values...) {
// infinite | <number>
@include prefixer(animation-iteration-count, $values, webkit moz spec);
}
@mixin animation-direction ($directions...) {
// normal | alternate
@include prefixer(animation-direction, $directions, webkit moz spec);
}
@mixin animation-play-state ($states...) {
// running | paused
@include prefixer(animation-play-state, $states, webkit moz spec);
}
@mixin animation-delay ($times...) {
@include prefixer(animation-delay, $times, webkit moz spec);
}
@mixin animation-fill-mode ($modes...) {
// none | forwards | backwards | both
@include prefixer(animation-fill-mode, $modes, webkit moz spec);
}

View File

@ -0,0 +1,3 @@
@mixin appearance ($value) {
@include prefixer(appearance, $value, webkit moz ms o spec);
}

View File

@ -0,0 +1,6 @@
//************************************************************************//
// Backface-visibility mixin
//************************************************************************//
@mixin backface-visibility($visibility) {
@include prefixer(backface-visibility, $visibility, webkit spec);
}

View File

@ -0,0 +1,48 @@
//************************************************************************//
// Background-image property for adding multiple background images with
// gradients, or for stringing multiple gradients together.
//************************************************************************//
@mixin background-image($images...) {
background-image: _add-prefix($images, webkit);
background-image: _add-prefix($images);
}
@function _add-prefix($images, $vendor: false) {
$images-prefixed: ();
$gradient-positions: false;
@for $i from 1 through length($images) {
$type: type-of(nth($images, $i)); // Get type of variable - List or String
// If variable is a list - Gradient
@if $type == list {
$gradient-type: nth(nth($images, $i), 1); // linear or radial
$gradient-pos: null;
$gradient-args: null;
@if ($gradient-type == linear) or ($gradient-type == radial) {
$gradient-pos: nth(nth($images, $i), 2); // Get gradient position
$gradient-args: nth(nth($images, $i), 3); // Get actual gradient (red, blue)
}
@else {
$gradient-args: nth(nth($images, $i), 2); // Get actual gradient (red, blue)
}
$gradient-positions: _gradient-positions-parser($gradient-type, $gradient-pos);
$gradient: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor);
$images-prefixed: append($images-prefixed, $gradient, comma);
}
// If variable is a string - Image
@else if $type == string {
$images-prefixed: join($images-prefixed, nth($images, $i), comma);
}
}
@return $images-prefixed;
}
//Examples:
//@include background-image(linear-gradient(top, orange, red));
//@include background-image(radial-gradient(50% 50%, cover circle, orange, red));
//@include background-image(url("/images/a.png"), linear-gradient(orange, red));
//@include background-image(url("image.png"), linear-gradient(orange, red), url("image.png"));
//@include background-image(linear-gradient(hsla(0, 100%, 100%, 0.25) 0%, hsla(0, 100%, 100%, 0.08) 50%, transparent 50%), linear-gradient(orange, red));

View File

@ -0,0 +1,103 @@
//************************************************************************//
// Background property for adding multiple backgrounds using shorthand
// notation.
//************************************************************************//
@mixin background(
$background-1 , $background-2: false,
$background-3: false, $background-4: false,
$background-5: false, $background-6: false,
$background-7: false, $background-8: false,
$background-9: false, $background-10: false,
$fallback: false
) {
$backgrounds: compact($background-1, $background-2,
$background-3, $background-4,
$background-5, $background-6,
$background-7, $background-8,
$background-9, $background-10);
$fallback-color: false;
@if (type-of($fallback) == color) or ($fallback == "transparent") {
$fallback-color: $fallback;
}
@else {
$fallback-color: _extract-background-color($backgrounds);
}
@if $fallback-color {
background-color: $fallback-color;
}
background: _background-add-prefix($backgrounds, webkit);
background: _background-add-prefix($backgrounds);
}
@function _extract-background-color($backgrounds) {
$final-bg-layer: nth($backgrounds, length($backgrounds));
@if type-of($final-bg-layer) == list {
@for $i from 1 through length($final-bg-layer) {
$value: nth($final-bg-layer, $i);
@if type-of($value) == color {
@return $value;
}
}
}
@return false;
}
@function _background-add-prefix($backgrounds, $vendor: false) {
$backgrounds-prefixed: ();
@for $i from 1 through length($backgrounds) {
$shorthand: nth($backgrounds, $i); // Get member for current index
$type: type-of($shorthand); // Get type of variable - List (gradient) or String (image)
// If shorthand is a list (gradient)
@if $type == list {
$first-member: nth($shorthand, 1); // Get first member of shorthand
// Linear Gradient
@if index(linear radial, nth($first-member, 1)) {
$gradient-type: nth($first-member, 1); // linear || radial
$gradient-args: false;
$gradient-positions: false;
$shorthand-start: false;
@if type-of($first-member) == list { // Linear gradient plus additional shorthand values - lg(red,orange)repeat,...
$gradient-positions: nth($first-member, 2);
$gradient-args: nth($first-member, 3);
$shorthand-start: 2;
}
@else { // Linear gradient only - lg(red,orange),...
$gradient-positions: nth($shorthand, 2);
$gradient-args: nth($shorthand, 3); // Get gradient (red, blue)
}
$gradient-positions: _gradient-positions-parser($gradient-type, $gradient-positions);
$gradient: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor);
// Append any additional shorthand args to gradient
@if $shorthand-start {
@for $j from $shorthand-start through length($shorthand) {
$gradient: join($gradient, nth($shorthand, $j), space);
}
}
$backgrounds-prefixed: append($backgrounds-prefixed, $gradient, comma);
}
// Image with additional properties
@else {
$backgrounds-prefixed: append($backgrounds-prefixed, $shorthand, comma);
}
}
// If shorthand is a simple string (color or image)
@else if $type == string {
$backgrounds-prefixed: join($backgrounds-prefixed, $shorthand, comma);
}
}
@return $backgrounds-prefixed;
}
//Examples:
//@include background(linear-gradient(top, orange, red));
//@include background(radial-gradient(circle at 40% 40%, orange, red));
//@include background(url("/images/a.png") no-repeat, linear-gradient(orange, red));
//@include background(url("image.png") center center, linear-gradient(orange, red), url("image.png"));

View File

@ -0,0 +1,55 @@
@mixin border-image($images) {
-webkit-border-image: _border-add-prefix($images, webkit);
-moz-border-image: _border-add-prefix($images, moz);
-o-border-image: _border-add-prefix($images, o);
border-image: _border-add-prefix($images);
}
@function _border-add-prefix($images, $vendor: false) {
$border-image: null;
$images-type: type-of(nth($images, 1));
$first-var: nth(nth($images, 1), 1); // Get type of Gradient (Linear || radial)
// If input is a gradient
@if $images-type == string {
@if ($first-var == "linear") or ($first-var == "radial") {
$gradient-type: nth($images, 1); // Get type of gradient (linear || radial)
$gradient-pos: nth($images, 2); // Get gradient position
$gradient-args: nth($images, 3); // Get actual gradient (red, blue)
$gradient-positions: _gradient-positions-parser($gradient-type, $gradient-pos);
$border-image: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor);
}
// If input is a URL
@else {
$border-image: $images;
}
}
// If input is gradient or url + additional args
@else if $images-type == list {
$type: type-of(nth($images, 1)); // Get type of variable - List or String
// If variable is a list - Gradient
@if $type == list {
$gradient: nth($images, 1);
$gradient-type: nth($gradient, 1); // Get type of gradient (linear || radial)
$gradient-pos: nth($gradient, 2); // Get gradient position
$gradient-args: nth($gradient, 3); // Get actual gradient (red, blue)
$gradient-positions: _gradient-positions-parser($gradient-type, $gradient-pos);
$border-image: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor);
@for $i from 2 through length($images) {
$border-image: append($border-image, nth($images, $i));
}
}
}
@return $border-image;
}
//Examples:
// @include border-image(url("image.png"));
// @include border-image(url("image.png") 20 stretch);
// @include border-image(linear-gradient(45deg, orange, yellow));
// @include border-image(linear-gradient(45deg, orange, yellow) stretch);
// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round);
// @include border-image(radial-gradient(top, cover, orange, yellow, orange));

View File

@ -0,0 +1,22 @@
//************************************************************************//
// Shorthand Border-radius mixins
//************************************************************************//
@mixin border-top-radius($radii) {
@include prefixer(border-top-left-radius, $radii, spec);
@include prefixer(border-top-right-radius, $radii, spec);
}
@mixin border-bottom-radius($radii) {
@include prefixer(border-bottom-left-radius, $radii, spec);
@include prefixer(border-bottom-right-radius, $radii, spec);
}
@mixin border-left-radius($radii) {
@include prefixer(border-top-left-radius, $radii, spec);
@include prefixer(border-bottom-left-radius, $radii, spec);
}
@mixin border-right-radius($radii) {
@include prefixer(border-top-right-radius, $radii, spec);
@include prefixer(border-bottom-right-radius, $radii, spec);
}

View File

@ -0,0 +1,4 @@
@mixin box-sizing ($box) {
// content-box | border-box | inherit
@include prefixer(box-sizing, $box, webkit moz spec);
}

View File

@ -0,0 +1,47 @@
@mixin columns($arg: auto) {
// <column-count> || <column-width>
@include prefixer(columns, $arg, webkit moz spec);
}
@mixin column-count($int: auto) {
// auto || integer
@include prefixer(column-count, $int, webkit moz spec);
}
@mixin column-gap($length: normal) {
// normal || length
@include prefixer(column-gap, $length, webkit moz spec);
}
@mixin column-fill($arg: auto) {
// auto || length
@include prefixer(columns-fill, $arg, webkit moz spec);
}
@mixin column-rule($arg) {
// <border-width> || <border-style> || <color>
@include prefixer(column-rule, $arg, webkit moz spec);
}
@mixin column-rule-color($color) {
@include prefixer(column-rule-color, $color, webkit moz spec);
}
@mixin column-rule-style($style: none) {
// none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid
@include prefixer(column-rule-style, $style, webkit moz spec);
}
@mixin column-rule-width ($width: none) {
@include prefixer(column-rule-width, $width, webkit moz spec);
}
@mixin column-span($arg: none) {
// none || all
@include prefixer(column-span, $arg, webkit moz spec);
}
@mixin column-width($length: auto) {
// auto || length
@include prefixer(column-width, $length, webkit moz spec);
}

View File

@ -0,0 +1,52 @@
// CSS3 Flexible Box Model and property defaults
// Custom shorthand notation for flexbox
@mixin box($orient: inline-axis, $pack: start, $align: stretch) {
@include display-box;
@include box-orient($orient);
@include box-pack($pack);
@include box-align($align);
}
@mixin display-box {
display: -webkit-box;
display: -moz-box;
display: box;
}
@mixin box-orient($orient: inline-axis) {
// horizontal|vertical|inline-axis|block-axis|inherit
@include prefixer(box-orient, $orient, webkit moz spec);
}
@mixin box-pack($pack: start) {
// start|end|center|justify
@include prefixer(box-pack, $pack, webkit moz spec);
}
@mixin box-align($align: stretch) {
// start|end|center|baseline|stretch
@include prefixer(box-align, $align, webkit moz spec);
}
@mixin box-direction($direction: normal) {
// normal|reverse|inherit
@include prefixer(box-direction, $direction, webkit moz spec);
}
@mixin box-lines($lines: single) {
// single|multiple
@include prefixer(box-lines, $lines, webkit moz spec);
}
@mixin box-ordinal-group($int: 1) {
@include prefixer(box-ordinal-group, $int, webkit moz spec);
}
@mixin box-flex($value: 0.0) {
@include prefixer(box-flex, $value, webkit moz spec);
}
@mixin box-flex-group($int: 1) {
@include prefixer(box-flex-group, $int, webkit moz spec);
}

View File

@ -0,0 +1,23 @@
// Order of the includes matters, and it is: normal, bold, italic, bold+italic.
@mixin font-face($font-family, $file-path, $weight: normal, $style: normal, $asset-pipeline: false ) {
@font-face {
font-family: $font-family;
font-weight: $weight;
font-style: $style;
@if $asset-pipeline == true {
src: font-url('#{$file-path}.eot');
src: font-url('#{$file-path}.eot?#iefix') format('embedded-opentype'),
font-url('#{$file-path}.woff') format('woff'),
font-url('#{$file-path}.ttf') format('truetype'),
font-url('#{$file-path}.svg##{$font-family}') format('svg');
} @else {
src: url('#{$file-path}.eot');
src: url('#{$file-path}.eot?#iefix') format('embedded-opentype'),
url('#{$file-path}.woff') format('woff'),
url('#{$file-path}.ttf') format('truetype'),
url('#{$file-path}.svg##{$font-family}') format('svg');
}
}
}

View File

@ -0,0 +1,10 @@
// HiDPI mixin. Default value set to 1.3 to target Google Nexus 7 (http://bjango.com/articles/min-device-pixel-ratio/)
@mixin hidpi($ratio: 1.3) {
@media only screen and (-webkit-min-device-pixel-ratio: $ratio),
only screen and (min--moz-device-pixel-ratio: $ratio),
only screen and (-o-min-device-pixel-ratio: #{$ratio}/1),
only screen and (min-resolution: #{round($ratio*96)}dpi),
only screen and (min-resolution: #{$ratio}dppx) {
@content;
}
}

View File

@ -0,0 +1,13 @@
@mixin image-rendering ($mode:optimizeQuality) {
@if ($mode == optimize-contrast) {
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: optimize-contrast;
}
@else {
image-rendering: $mode;
}
}

View File

@ -0,0 +1,8 @@
// Legacy support for inline-block in IE7 (maybe IE6)
@mixin inline-block {
display: inline-block;
vertical-align: baseline;
zoom: 1;
*display: inline;
*vertical-align: auto;
}

View File

@ -0,0 +1,43 @@
// Adds keyframes blocks for supported prefixes, removing redundant prefixes in the block's content
@mixin keyframes($name) {
$original-prefix-for-webkit: $prefix-for-webkit;
$original-prefix-for-mozilla: $prefix-for-mozilla;
$original-prefix-for-microsoft: $prefix-for-microsoft;
$original-prefix-for-opera: $prefix-for-opera;
$original-prefix-for-spec: $prefix-for-spec;
@if $original-prefix-for-webkit {
@include disable-prefix-for-all();
$prefix-for-webkit: true;
@-webkit-keyframes #{$name} {
@content;
}
}
@if $original-prefix-for-mozilla {
@include disable-prefix-for-all();
$prefix-for-mozilla: true;
@-moz-keyframes #{$name} {
@content;
}
}
@if $original-prefix-for-opera {
@include disable-prefix-for-all();
$prefix-for-opera: true;
@-o-keyframes #{$name} {
@content;
}
}
@if $original-prefix-for-spec {
@include disable-prefix-for-all();
$prefix-for-spec: true;
@keyframes #{$name} {
@content;
}
}
$prefix-for-webkit: $original-prefix-for-webkit;
$prefix-for-mozilla: $original-prefix-for-mozilla;
$prefix-for-microsoft: $original-prefix-for-microsoft;
$prefix-for-opera: $original-prefix-for-opera;
$prefix-for-spec: $original-prefix-for-spec;
}

View File

@ -0,0 +1,41 @@
@mixin linear-gradient($pos, $G1, $G2: false,
$G3: false, $G4: false,
$G5: false, $G6: false,
$G7: false, $G8: false,
$G9: false, $G10: false,
$deprecated-pos1: left top,
$deprecated-pos2: left bottom,
$fallback: false) {
// Detect what type of value exists in $pos
$pos-type: type-of(nth($pos, 1));
$pos-spec: null;
$pos-degree: null;
// If $pos is missing from mixin, reassign vars and add default position
@if ($pos-type == color) or (nth($pos, 1) == "transparent") {
$G10: $G9; $G9: $G8; $G8: $G7; $G7: $G6; $G6: $G5;
$G5: $G4; $G4: $G3; $G3: $G2; $G2: $G1; $G1: $pos;
$pos: null;
}
@if $pos {
$positions: _linear-positions-parser($pos);
$pos-degree: nth($positions, 1);
$pos-spec: nth($positions, 2);
}
$full: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10);
// Set $G1 as the default fallback color
$fallback-color: nth($G1, 1);
// If $fallback is a color use that color as the fallback color
@if (type-of($fallback) == color) or ($fallback == "transparent") {
$fallback-color: $fallback;
}
background-color: $fallback-color;
background-image: _deprecated-webkit-gradient(linear, $deprecated-pos1, $deprecated-pos2, $full); // Safari <= 5.0
background-image: -webkit-linear-gradient($pos-degree $full); // Safari 5.1+, Chrome
background-image: unquote("linear-gradient(#{$pos-spec}#{$full})");
}

View File

@ -0,0 +1,8 @@
@mixin perspective($depth: none) {
// none | <length>
@include prefixer(perspective, $depth, webkit moz spec);
}
@mixin perspective-origin($value: 50% 50%) {
@include prefixer(perspective-origin, $value, webkit moz spec);
}

View File

@ -0,0 +1,29 @@
$placeholders: '-webkit-input-placeholder',
'-moz-placeholder',
'-ms-input-placeholder';
@mixin placeholder {
@each $placeholder in $placeholders {
@if $placeholder == "-webkit-input-placeholder" {
&::#{$placeholder} {
@content;
}
}
@else if $placeholder == "-moz-placeholder" {
// FF 18-
&:#{$placeholder} {
@content;
}
// FF 19+
&::#{$placeholder} {
@content;
}
}
@else {
&:#{$placeholder} {
@content;
}
}
}
}

View File

@ -0,0 +1,44 @@
// Requires Sass 3.1+
@mixin radial-gradient($G1, $G2,
$G3: false, $G4: false,
$G5: false, $G6: false,
$G7: false, $G8: false,
$G9: false, $G10: false,
$pos: null,
$shape-size: null,
$deprecated-pos1: center center,
$deprecated-pos2: center center,
$deprecated-radius1: 0,
$deprecated-radius2: 460,
$fallback: false) {
$data: _radial-arg-parser($G1, $G2, $pos, $shape-size);
$G1: nth($data, 1);
$G2: nth($data, 2);
$pos: nth($data, 3);
$shape-size: nth($data, 4);
$full: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10);
// Strip deprecated cover/contain for spec
$shape-size-spec: _shape-size-stripper($shape-size);
// Set $G1 as the default fallback color
$first-color: nth($full, 1);
$fallback-color: nth($first-color, 1);
@if (type-of($fallback) == color) or ($fallback == "transparent") {
$fallback-color: $fallback;
}
// Add Commas and spaces
$shape-size: if($shape-size, '#{$shape-size}, ', null);
$pos: if($pos, '#{$pos}, ', null);
$pos-spec: if($pos, 'at #{$pos}', null);
$shape-size-spec: if(($shape-size-spec != ' ') and ($pos == null), '#{$shape-size-spec}, ', '#{$shape-size-spec} ');
background-color: $fallback-color;
background-image: _deprecated-webkit-gradient(radial, $deprecated-pos1, $deprecated-pos2, $full, $deprecated-radius1, $deprecated-radius2); // Safari <= 5.0 && IOS 4
background-image: -webkit-radial-gradient(unquote(#{$pos}#{$shape-size}#{$full}));
background-image: unquote("radial-gradient(#{$shape-size-spec}#{$pos-spec}#{$full})");
}

View File

@ -0,0 +1,15 @@
@mixin transform($property: none) {
// none | <transform-function>
@include prefixer(transform, $property, webkit moz ms o spec);
}
@mixin transform-origin($axes: 50%) {
// x-axis - left | center | right | length | %
// y-axis - top | center | bottom | length | %
// z-axis - length
@include prefixer(transform-origin, $axes, webkit moz ms o spec);
}
@mixin transform-style ($style: flat) {
@include prefixer(transform-style, $style, webkit moz ms o spec);
}

View File

@ -0,0 +1,34 @@
// Shorthand mixin. Supports multiple parentheses-deliminated values for each variable.
// Example: @include transition (all, 2.0s, ease-in-out);
// @include transition ((opacity, width), (1.0s, 2.0s), ease-in, (0, 2s));
// @include transition ($property:(opacity, width), $delay: (1.5s, 2.5s));
@mixin transition ($properties...) {
@if length($properties) >= 1 {
@include prefixer(transition, $properties, webkit moz spec);
}
@else {
$properties: all 0.15s ease-out 0;
@include prefixer(transition, $properties, webkit moz spec);
}
}
@mixin transition-property ($properties...) {
-webkit-transition-property: transition-property-names($properties, 'webkit');
-moz-transition-property: transition-property-names($properties, 'moz');
transition-property: transition-property-names($properties, false);
}
@mixin transition-duration ($times...) {
@include prefixer(transition-duration, $times, webkit moz spec);
}
@mixin transition-timing-function ($motions...) {
// ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier()
@include prefixer(transition-timing-function, $motions, webkit moz spec);
}
@mixin transition-delay ($times...) {
@include prefixer(transition-delay, $times, webkit moz spec);
}

View File

@ -0,0 +1,3 @@
@mixin user-select($arg: none) {
@include prefixer(user-select, $arg, webkit moz ms spec);
}

View File

@ -0,0 +1,11 @@
// Remove `false` values from a list
@function compact($vars...) {
$list: ();
@each $var in $vars {
@if $var {
$list: append($list, $var, comma);
}
}
@return $list;
}

View File

@ -0,0 +1,39 @@
// Flexible grid
@function flex-grid($columns, $container-columns: $fg-max-columns) {
$width: $columns * $fg-column + ($columns - 1) * $fg-gutter;
$container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
@return percentage($width / $container-width);
}
// Flexible gutter
@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) {
$container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
@return percentage($gutter / $container-width);
}
// The $fg-column, $fg-gutter and $fg-max-columns variables must be defined in your base stylesheet to properly use the flex-grid function.
// This function takes the fluid grid equation (target / context = result) and uses columns to help define each.
//
// The calculation presumes that your column structure will be missing the last gutter:
//
// -- column -- gutter -- column -- gutter -- column
//
// $fg-column: 60px; // Column Width
// $fg-gutter: 25px; // Gutter Width
// $fg-max-columns: 12; // Total Columns For Main Container
//
// div {
// width: flex-grid(4); // returns (315px / 995px) = 31.65829%;
// margin-left: flex-gutter(); // returns (25px / 995px) = 2.51256%;
//
// p {
// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%;
// float: left;
// margin: flex-gutter(4); // returns (25px / 315px) = 7.936508%;
// }
//
// blockquote {
// float: left;
// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%;
// }
// }

View File

@ -0,0 +1,13 @@
@function grid-width($n) {
@return $n * $gw-column + ($n - 1) * $gw-gutter;
}
// The $gw-column and $gw-gutter variables must be defined in your base stylesheet to properly use the grid-width function.
//
// $gw-column: 100px; // Column Width
// $gw-gutter: 40px; // Gutter Width
//
// div {
// width: grid-width(4); // returns 520px;
// margin-left: $gw-gutter; // returns 40px;
// }

View File

@ -0,0 +1,13 @@
@function linear-gradient($pos, $gradients...) {
$type: linear;
$pos-type: type-of(nth($pos, 1));
// if $pos doesn't exist, fix $gradient
@if ($pos-type == color) or (nth($pos, 1) == "transparent") {
$gradients: zip($pos $gradients);
$pos: false;
}
$type-gradient: $type, $pos, $gradients;
@return $type-gradient;
}

View File

@ -0,0 +1,40 @@
@function modular-scale($value, $increment, $ratio) {
@if $increment > 0 {
@for $i from 1 through $increment {
$value: ($value * $ratio);
}
}
@if $increment < 0 {
$increment: abs($increment);
@for $i from 1 through $increment {
$value: ($value / $ratio);
}
}
@return $value;
}
// div {
// Increment Up GR with positive value
// font-size: modular-scale(14px, 1, 1.618); // returns: 22.652px
//
// Increment Down GR with negative value
// font-size: modular-scale(14px, -1, 1.618); // returns: 8.653px
//
// Can be used with ceil(round up) or floor(round down)
// font-size: floor( modular-scale(14px, 1, 1.618) ); // returns: 22px
// font-size: ceil( modular-scale(14px, 1, 1.618) ); // returns: 23px
// }
//
// modularscale.com
@function golden-ratio($value, $increment) {
@return modular-scale($value, $increment, 1.618)
}
// div {
// font-size: golden-ratio(14px, 1); // returns: 22.652px
// }
//
// goldenratiocalculator.com

View File

@ -0,0 +1,8 @@
// Convert pixels to ems
// eg. for a relational value of 12px write em(12) when the parent is 16px
// if the parent is another value say 24px write em(12, 24)
@function em($pxval, $base: 16) {
@return ($pxval / $base) * 1em;
}

View File

@ -0,0 +1,23 @@
// This function is required and used by the background-image mixin.
@function radial-gradient($G1, $G2,
$G3: false, $G4: false,
$G5: false, $G6: false,
$G7: false, $G8: false,
$G9: false, $G10: false,
$pos: null,
$shape-size: null) {
$data: _radial-arg-parser($G1, $G2, $pos, $shape-size);
$G1: nth($data, 1);
$G2: nth($data, 2);
$pos: nth($data, 3);
$shape-size: nth($data, 4);
$type: radial;
$gradient: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10);
$type-gradient: $type, $shape-size $pos, $gradient;
@return $type-gradient;
}

View File

@ -0,0 +1,9 @@
// Add percentage of white to a color
@function tint($color, $percent){
@return mix(white, $color, $percent);
}
// Add percentage of black to a color
@function shade($color, $percent){
@return mix(black, $color, $percent);
}

View File

@ -0,0 +1,22 @@
// Return vendor-prefixed property names if appropriate
// Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background
//************************************************************************//
@function transition-property-names($props, $vendor: false) {
$new-props: ();
@each $prop in $props {
$new-props: append($new-props, transition-property-name($prop, $vendor), comma);
}
@return $new-props;
}
@function transition-property-name($prop, $vendor: false) {
// put other properties that need to be prefixed here aswell
@if $vendor and $prop == transform {
@return unquote('-'+$vendor+'-'+$prop);
}
@else {
@return $prop;
}
}

View File

@ -0,0 +1,39 @@
// Render Deprecated Webkit Gradient - Linear || Radial
//************************************************************************//
@function _deprecated-webkit-gradient($type,
$deprecated-pos1, $deprecated-pos2,
$full,
$deprecated-radius1: false, $deprecated-radius2: false) {
$gradient-list: ();
$gradient: false;
$full-length: length($full);
$percentage: false;
$gradient-type: $type;
@for $i from 1 through $full-length {
$gradient: nth($full, $i);
@if length($gradient) == 2 {
$color-stop: color-stop(nth($gradient, 2), nth($gradient, 1));
$gradient-list: join($gradient-list, $color-stop, comma);
}
@else if $gradient != null {
@if $i == $full-length {
$percentage: 100%;
}
@else {
$percentage: ($i - 1) * (100 / ($full-length - 1)) + "%";
}
$color-stop: color-stop(unquote($percentage), $gradient);
$gradient-list: join($gradient-list, $color-stop, comma);
}
}
@if $type == radial {
$gradient: -webkit-gradient(radial, $deprecated-pos1, $deprecated-radius1, $deprecated-pos2, $deprecated-radius2, $gradient-list);
}
@else if $type == linear {
$gradient: -webkit-gradient(linear, $deprecated-pos1, $deprecated-pos2, $gradient-list);
}
@return $gradient;
}

View File

@ -0,0 +1,13 @@
@function _gradient-positions-parser($gradient-type, $gradient-positions) {
@if $gradient-positions
and ($gradient-type == linear)
and (type-of($gradient-positions) != color) {
$gradient-positions: _linear-positions-parser($gradient-positions);
}
@else if $gradient-positions
and ($gradient-type == radial)
and (type-of($gradient-positions) != color) {
$gradient-positions: _radial-positions-parser($gradient-positions);
}
@return $gradient-positions;
}

View File

@ -0,0 +1,61 @@
@function _linear-positions-parser($pos) {
$type: type-of(nth($pos, 1));
$spec: null;
$degree: null;
$side: null;
$corner: null;
$length: length($pos);
// Parse Side and corner positions
@if ($length > 1) {
@if nth($pos, 1) == "to" { // Newer syntax
$side: nth($pos, 2);
@if $length == 2 { // eg. to top
// Swap for backwards compatability
$degree: _position-flipper(nth($pos, 2));
}
@else if $length == 3 { // eg. to top left
$corner: nth($pos, 3);
}
}
@else if $length == 2 { // Older syntax ("top left")
$side: _position-flipper(nth($pos, 1));
$corner: _position-flipper(nth($pos, 2));
}
@if ("#{$side} #{$corner}" == "left top") or ("#{$side} #{$corner}" == "top left") {
$degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
}
@else if ("#{$side} #{$corner}" == "right top") or ("#{$side} #{$corner}" == "top right") {
$degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
}
@else if ("#{$side} #{$corner}" == "right bottom") or ("#{$side} #{$corner}" == "bottom right") {
$degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
}
@else if ("#{$side} #{$corner}" == "left bottom") or ("#{$side} #{$corner}" == "bottom left") {
$degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
}
$spec: to $side $corner;
}
@else if $length == 1 {
// Swap for backwards compatability
@if $type == string {
$degree: $pos;
$spec: to _position-flipper($pos);
}
@else {
$degree: -270 - $pos; //rotate the gradient opposite from spec
$spec: $pos;
}
}
$degree: unquote($degree + ",");
$spec: unquote($spec + ",");
@return $degree $spec;
}
@function _position-flipper($pos) {
@return if($pos == left, right, null)
if($pos == right, left, null)
if($pos == top, bottom, null)
if($pos == bottom, top, null);
}

View File

@ -0,0 +1,69 @@
@function _radial-arg-parser($G1, $G2, $pos, $shape-size) {
@each $value in $G1, $G2 {
$first-val: nth($value, 1);
$pos-type: type-of($first-val);
$spec-at-index: null;
// Determine if spec was passed to mixin
@if type-of($value) == list {
$spec-at-index: if(index($value, at), index($value, at), false);
}
@if $spec-at-index {
@if $spec-at-index > 1 {
@for $i from 1 through ($spec-at-index - 1) {
$shape-size: $shape-size nth($value, $i);
}
@for $i from ($spec-at-index + 1) through length($value) {
$pos: $pos nth($value, $i);
}
}
@else if $spec-at-index == 1 {
@for $i from ($spec-at-index + 1) through length($value) {
$pos: $pos nth($value, $i);
}
}
$G1: false;
}
// If not spec calculate correct values
@else {
@if ($pos-type != color) or ($first-val != "transparent") {
@if ($pos-type == number)
or ($first-val == "center")
or ($first-val == "top")
or ($first-val == "right")
or ($first-val == "bottom")
or ($first-val == "left") {
$pos: $value;
@if $pos == $G1 {
$G1: false;
}
}
@else if
($first-val == "ellipse")
or ($first-val == "circle")
or ($first-val == "closest-side")
or ($first-val == "closest-corner")
or ($first-val == "farthest-side")
or ($first-val == "farthest-corner")
or ($first-val == "contain")
or ($first-val == "cover") {
$shape-size: $value;
@if $value == $G1 {
$G1: false;
}
@else if $value == $G2 {
$G2: false;
}
}
}
}
}
@return $G1, $G2, $pos, $shape-size;
}

View File

@ -0,0 +1,18 @@
@function _radial-positions-parser($gradient-pos) {
$shape-size: nth($gradient-pos, 1);
$pos: nth($gradient-pos, 2);
$shape-size-spec: _shape-size-stripper($shape-size);
$pre-spec: unquote(if($pos, "#{$pos}, ", null))
unquote(if($shape-size, "#{$shape-size},", null));
$pos-spec: if($pos, "at #{$pos}", null);
$spec: "#{$shape-size-spec} #{$pos-spec}";
// Add comma
@if ($spec != ' ') {
$spec: "#{$spec},"
}
@return $pre-spec $spec;
}

View File

@ -0,0 +1,26 @@
// User for linear and radial gradients within background-image or border-image properties
@function _render-gradients($gradient-positions, $gradients, $gradient-type, $vendor: false) {
$pre-spec: null;
$spec: null;
$vendor-gradients: null;
@if $gradient-type == linear {
@if $gradient-positions {
$pre-spec: nth($gradient-positions, 1);
$spec: nth($gradient-positions, 2);
}
}
@else if $gradient-type == radial {
$pre-spec: nth($gradient-positions, 1);
$spec: nth($gradient-positions, 2);
}
@if $vendor {
$vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} $gradients);
}
@else if $vendor == false {
$vendor-gradients: "#{$gradient-type}-gradient(#{$spec} #{$gradients})";
$vendor-gradients: unquote($vendor-gradients);
}
@return $vendor-gradients;
}

View File

@ -0,0 +1,10 @@
@function _shape-size-stripper($shape-size) {
$shape-size-spec: null;
@each $value in $shape-size {
@if ($value == "cover") or ($value == "contain") {
$value: null;
}
$shape-size-spec: "#{$shape-size-spec} #{$value}";
}
@return $shape-size-spec;
}

View File

@ -0,0 +1,8 @@
// Functions
@import "functions/private";
@import "functions/new-breakpoint";
@import "functions/px-to-em";
// Settings
@import "settings/grid";
@import "settings/visual-grid";

View File

@ -0,0 +1,21 @@
// Bourbon Neat
// MIT Licensed
// Copyright (c) 2012-2013 thoughtbot, inc.
// Helpers
@import "neat-helpers";
// Grid
@import "grid/private";
@import "grid/reset";
@import "grid/grid";
@import "grid/omega";
@import "grid/outer-container";
@import "grid/span-columns";
@import "grid/row";
@import "grid/shift";
@import "grid/pad";
@import "grid/fill-parent";
@import "grid/media";
@import "grid/to-deprecate";
@import "grid/visual-grid";

View File

@ -0,0 +1,16 @@
@function new-breakpoint($query:$feature $value $columns, $total-columns: $grid-columns) {
@if length($query) == 1 {
$query: $default-feature nth($query, 1) $total-columns;
}
@else if length($query) == 2 or length($query) == 4 {
$query: append($query, $total-columns);
}
@if not belongs-to($query, $visual-grid-breakpoints) {
$visual-grid-breakpoints: append($visual-grid-breakpoints, $query, comma);
}
@return $query;
}

View File

@ -0,0 +1,107 @@
// Checks if a number is even
@function is-even($int) {
@if $int%2 == 0 {
@return true;
}
@return false;
}
// Checks if an element belongs to a list
@function belongs-to($tested-item, $list) {
@each $item in $list {
@if $item == $tested-item {
@return true;
}
}
@return false;
}
// Contains display value
@function contains-display-value($query) {
@if belongs-to(table, $query) or belongs-to(block, $query) or belongs-to(inline-block, $query) or belongs-to(inline, $query) {
@return true;
}
@return false;
}
// Parses the first argument of span-columns()
@function container-span($span: $span) {
@if length($span) == 3 {
$container-columns: nth($span, 3);
@return $container-columns;
}
@else if length($span) == 2 {
$container-columns: nth($span, 2);
@return $container-columns;
}
@else {
@return $grid-columns;
}
}
// Generates a striped background
@function gradient-stops($grid-columns, $color: $visual-grid-color) {
$transparent: rgba(0,0,0,0);
$column-width: flex-grid(1, $grid-columns);
$gutter-width: flex-gutter($grid-columns);
$column-offset: $column-width;
$values: ($transparent 0, $color 0);
@for $i from 1 to $grid-columns*2 {
@if is-even($i) {
$values: append($values, $transparent $column-offset, comma);
$values: append($values, $color $column-offset, comma);
$column-offset: $column-offset + $column-width;
}
@else {
$values: append($values, $color $column-offset, comma);
$values: append($values, $transparent $column-offset, comma);
$column-offset: $column-offset + $gutter-width;
}
}
@return $values;
}
// Layout direction
@function get-direction($layout, $default) {
$direction: nil;
@if $layout == LTR or $layout == RTL {
$direction: direction-from-layout($layout);
} @else {
$direction: direction-from-layout($default);
}
@return $direction;
}
@function direction-from-layout($layout) {
$direction: nil;
@if $layout == LTR {
$direction: right;
} @else {
$direction: left;
}
@return $direction;
}
@function get-opposite-direction($direction) {
$opposite-direction: left;
@if $direction == left {
$opposite-direction: right;
}
@return $opposite-direction;
}

View File

@ -0,0 +1,3 @@
@function em($pxval, $base: 16) {
@return ($pxval / $base) * 1em;
}

View File

@ -0,0 +1,7 @@
@mixin fill-parent() {
width: 100%;
@if $border-box-sizing == false {
@include box-sizing(border-box);
}
}

View File

@ -0,0 +1,5 @@
@if $border-box-sizing == true {
* {
@include box-sizing(border-box);
}
}

View File

@ -0,0 +1,51 @@
@mixin media($query:$feature $value $columns, $total-columns: $grid-columns) {
@if length($query) == 1 {
@media screen and ($default-feature: nth($query, 1)) {
$default-grid-columns: $grid-columns;
$grid-columns: $total-columns;
@content;
$grid-columns: $default-grid-columns;
}
}
@else if length($query) == 2 {
@media screen and (nth($query, 1): nth($query, 2)) {
$default-grid-columns: $grid-columns;
$grid-columns: $total-columns;
@content;
$grid-columns: $default-grid-columns;
}
}
@else if length($query) == 3 {
@media screen and (nth($query, 1): nth($query, 2)) {
$default-grid-columns: $grid-columns;
$grid-columns: nth($query, 3);
@content;
$grid-columns: $default-grid-columns;
}
}
@else if length($query) == 4 {
@media screen and (nth($query, 1): nth($query, 2)) and (nth($query, 3): nth($query, 4)) {
$default-grid-columns: $grid-columns;
$grid-columns: $total-columns;
@content;
$grid-columns: $default-grid-columns;
}
}
@else if length($query) == 5 {
@media screen and (nth($query, 1): nth($query, 2)) and (nth($query, 3): nth($query, 4)) {
$default-grid-columns: $grid-columns;
$grid-columns: nth($query, 5);
@content;
$grid-columns: $default-grid-columns;
}
}
@else {
@warn "Wrong number of arguments for breakpoint(). Read the documentation for more details.";
}
}

View File

@ -0,0 +1,79 @@
// Remove last element gutter
@mixin omega($query: block, $direction: default) {
$table: if(belongs-to(table, $query), true, false);
$auto: if(belongs-to(auto, $query), true, false);
@if $direction != default {
@warn "The omega mixin will no longer take a $direction argument. To change the layout direction, use row($direction) or set $default-layout-direction instead."
} @else {
$direction: get-direction($layout-direction, $default-layout-direction);
}
@if length($query) == 1 {
@if $auto {
&:last-child {
margin-#{$direction}: 0;
}
}
@else if contains-display-value($query) {
@if $table {
padding-#{$direction}: 0;
}
@else {
margin-#{$direction}: 0;
}
}
@else {
@include nth-child($query, $direction);
}
}
@else if length($query) == 2 {
@if $table {
@if $auto {
&:last-child {
padding-#{$direction}: 0;
}
}
@else {
&:nth-child(#{nth($query, 1)}) {
padding-#{$direction}: 0;
}
}
}
@else {
@if $auto {
&:last-child {
margin-#{$direction}: 0;
}
}
@else {
@include nth-child(nth($query, 1), $direction);
}
}
}
@else {
@warn "Too many arguments passed to the omega() mixin."
}
}
@mixin nth-child($query, $direction) {
$opposite-direction: get-opposite-direction($direction);
&:nth-child(#{$query}) {
margin-#{$direction}: 0;
}
@if type-of($query) == number {
&:nth-child(#{$query}+1) {
clear: $opposite-direction;
}
}
}

View File

@ -0,0 +1,8 @@
@mixin outer-container {
@include clearfix;
max-width: $max-width;
margin: {
left: auto;
right: auto;
}
}

View File

@ -0,0 +1,8 @@
@mixin pad($padding: flex-gutter()) {
$padding-list: null;
@each $value in $padding {
$value: if($value == 'default', flex-gutter(), $value);
$padding-list: join($padding-list, $value);
}
padding: $padding-list;
}

View File

@ -0,0 +1,50 @@
$parent-columns: $grid-columns !default;
$fg-column: $column;
$fg-gutter: $gutter;
$fg-max-columns: $grid-columns;
$container-display-table: false !default;
$layout-direction: nil !default;
@function flex-grid($columns, $container-columns: $fg-max-columns) {
$width: $columns * $fg-column + ($columns - 1) * $fg-gutter;
$container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
@return percentage($width / $container-width);
}
@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) {
$container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
@return percentage($gutter / $container-width);
}
@function grid-width($n) {
@return $n * $gw-column + ($n - 1) * $gw-gutter;
}
@function get-parent-columns($columns) {
@if $columns != $grid-columns {
$parent-columns: $columns;
} @else {
$parent-columns: $grid-columns;
}
@return $parent-columns;
}
@function is-display-table($container-is-display-table, $display) {
$display-table: false;
@if $container-is-display-table == true {
$display-table: true;
} @else if $display == table {
$display-table: true;
}
@return $display-table;
}
@function get-padding-for-table-layout($columns, $total-columns) {
$total-padding: flex-gutter($total-columns) * ($columns - 1);
$padding: $total-padding / $columns;
@return $padding;
}

View File

@ -0,0 +1,12 @@
@mixin reset-display {
$container-display-table: false;
}
@mixin reset-layout-direction {
$layout-direction: $default-layout-direction;
}
@mixin reset-all {
@include reset-display;
@include reset-layout-direction;
}

View File

@ -0,0 +1,17 @@
@mixin row($display: block, $direction: $default-layout-direction) {
@include clearfix;
$layout-direction: $direction;
@if $display == table {
display: table;
@include fill-parent;
table-layout: fixed;
$container-display-table: true;
}
@else {
display: block;
$container-display-table: false;
}
}

View File

@ -0,0 +1,9 @@
@mixin shift($n-columns: 1) {
$direction: get-direction($layout-direction, $default-layout-direction);
$opposite-direction: get-opposite-direction($direction);
margin-#{$opposite-direction}: $n-columns * flex-grid(1, $parent-columns) + $n-columns * flex-gutter($parent-columns);
// Reset nesting context
$parent-columns: $grid-columns;
}

View File

@ -0,0 +1,38 @@
@mixin span-columns($span: $columns of $container-columns, $display: block) {
$columns: nth($span, 1);
$container-columns: container-span($span);
// Set nesting context (used by shift())
$parent-columns: get-parent-columns($container-columns);
$direction: get-direction($layout-direction, $default-layout-direction);
$opposite-direction: get-opposite-direction($direction);
$display-table: is-display-table($container-display-table, $display);
@if $display-table {
$padding: get-padding-for-table-layout($columns, $container-columns);
display: table-cell;
padding-#{$direction}: $padding;
width: flex-grid($columns, $container-columns) + $padding;
} @else {
display: block;
float: #{$opposite-direction};
@if $display == collapse {
width: flex-grid($columns, $container-columns) + flex-gutter($container-columns);
&:last-child {
width: flex-grid($columns, $container-columns);
}
} @else {
margin-#{$direction}: flex-gutter($container-columns);
width: flex-grid($columns, $container-columns);
&:last-child {
margin-#{$direction}: 0;
}
}
}
}

View File

@ -0,0 +1,57 @@
@mixin breakpoint($query:$feature $value $columns, $total-columns: $grid-columns) {
@warn "The breakpoint() mixin was renamed to media() in Neat 1.0. Please update your project with the new syntax before the next version bump.";
@if length($query) == 1 {
@media screen and ($default-feature: nth($query, 1)) {
$default-grid-columns: $grid-columns;
$grid-columns: $total-columns;
@content;
$grid-columns: $default-grid-columns;
}
}
@else if length($query) == 2 {
@media screen and (nth($query, 1): nth($query, 2)) {
$default-grid-columns: $grid-columns;
$grid-columns: $total-columns;
@content;
$grid-columns: $default-grid-columns;
}
}
@else if length($query) == 3 {
@media screen and (nth($query, 1): nth($query, 2)) {
$default-grid-columns: $grid-columns;
$grid-columns: nth($query, 3);
@content;
$grid-columns: $default-grid-columns;
}
}
@else if length($query) == 4 {
@media screen and (nth($query, 1): nth($query, 2)) and (nth($query, 3): nth($query, 4)) {
$default-grid-columns: $grid-columns;
$grid-columns: $total-columns;
@content;
$grid-columns: $default-grid-columns;
}
}
@else if length($query) == 5 {
@media screen and (nth($query, 1): nth($query, 2)) and (nth($query, 3): nth($query, 4)) {
$default-grid-columns: $grid-columns;
$grid-columns: nth($query, 5);
@content;
$grid-columns: $default-grid-columns;
}
}
@else {
@warn "Wrong number of arguments for breakpoint(). Read the documentation for more details.";
}
}
@mixin nth-omega($nth, $display: block, $direction: default) {
@warn "The nth-omega() mixin is deprecated. Please use omega() instead.";
@include omega($nth $display, $direction);
}

View File

@ -0,0 +1,41 @@
@mixin grid-column-gradient($values...) {
background-image: deprecated-webkit-gradient(linear, left top, left bottom, $values);
background-image: -webkit-linear-gradient(left, $values);
background-image: -moz-linear-gradient(left, $values);
background-image: -ms-linear-gradient(left, $values);
background-image: -o-linear-gradient(left, $values);
background-image: unquote("linear-gradient(left, #{$values})");
}
@if $visual-grid == true or $visual-grid == yes {
body:before {
content: '';
display: inline-block;
@include grid-column-gradient(gradient-stops($grid-columns));
height: 100%;
left: 0;
margin: 0 auto;
max-width: $max-width;
opacity: $visual-grid-opacity;
position: fixed;
right: 0;
width: 100%;
pointer-events: none;
@if $visual-grid-index == back {
z-index: -1;
}
@else if $visual-grid-index == front {
z-index: 9999;
}
@each $breakpoint in $visual-grid-breakpoints {
@if $breakpoint != nil {
@include media($breakpoint) {
@include grid-column-gradient(gradient-stops($grid-columns));
}
}
}
}
}

View File

@ -0,0 +1,7 @@
$column: golden-ratio(1em, 3) !default; // Column width
$gutter: golden-ratio(1em, 1) !default; // Gutter between each two columns
$grid-columns: 12 !default; // Total number of columns in the grid
$max-width: em(1088) !default; // Max-width of the outer container
$border-box-sizing: true !default; // Makes all elements have a border-box layout
$default-feature: min-width; // Default @media feature for the breakpoint() mixin
$default-layout-direction: LTR !default;

View File

@ -0,0 +1,5 @@
$visual-grid: false !default; // Display the base grid
$visual-grid-color: #EEE !default;
$visual-grid-index: back !default; // Show grid behind content (back) or overlay it over the content (front)
$visual-grid-opacity: 0.4 !default;
$visual-grid-breakpoints: () !default;

200
isso/static/sass/new.scss Normal file
View File

@ -0,0 +1,200 @@
@import "bourbon/bourbon";
@import "neat/neat";
$avatar-bg-color: #F0F0F0;
@mixin isso-shadow {
border: 1px solid $avatar-bg-color;
border-radius: 2px;
box-shadow: 0px 0px 2px #888;
}
@mixin reset {
padding: 0;
margin: 0;
}
a {
text-decoration: none;
}
.isso-popup {
font-family: Helvetica, Arial, sans-serif;
font-size: 0.8em;
padding: 6px 8px;
margin-left: 20px;
border: 1px solid rgb(242, 122, 122);
border-radius: 2px;
background-color: rgb(242, 222, 222);
z-index: 9002;
}
#isso-thread {
@include reset;
> h4 {
color: #555;
font-weight: bold;
font-family: "Helvetica", Arial, sans-serif;
}
}
.parent-highlight {
background-color: #EFEFEF;
}
.isso-comment {
@include outer-container;
margin: 0.95em 0px;
> div.avatar {
@include span-columns(1 of 11);
> canvas {
@include isso-shadow;
}
}
> div.text-wrapper {
@include span-columns(10 of 11);
> header, > footer {
font-size: 0.95em;
}
> header {
font-family: "Helvetica", Arial, sans-serif;
font-size: 0.85em;
.spacer {
padding-left: 6px;
padding-right: 6px;
}
.spacer, a.permalink, .note, a.parent {
color: gray !important;
font-weight: normal;
text-shadow: none !important;
&:hover {
color: #606060 !important;
}
}
.note { float: right }
.author {
font-weight: bold;
color: #555;
}
}
> div.text {
p {
margin-top: 0.2em;
&:last-child {
margin-bottom: 0.2em;
}
}
}
> footer {
font-family: "Helvetica", Arial, sans-serif;
font-size: 0.80em;
color: gray !important;
a {
font-weight: bold;
text-decoration: none;
&:hover {
color: #111111 !important;
text-shadow: #aaaaaa 0px 0px 1px !important;
}
}
a.reply, a.delete {
padding-left: 1em;
}
.votes {
color: gray;
}
.upvote svg, .downvote svg {
margin-bottom: -0.2em;
}
}
}
.postbox {
margin-top: 0.8em;
}
}
.postbox {
@include outer-container;
> .avatar {
@include span-columns(1 of 11);
> canvas {
@include isso-shadow;
}
}
> .form-wrapper {
@include span-columns(10 of 11);
> .auth-section {
@include outer-container;
.input-wrapper {
@include span-columns(2 of 5);
margin-top: 0.1em;
input {
@include fill-parent;
@include isso-shadow;
@include pad(0.4em);
}
}
.post-action {
@include span-columns(1 of 5);
margin-top: 0.1em;
> input {
padding: 0.4em 1em;
border-radius: 2px;
border: #CCC solid 1px;
background-color: #DDD;
cursor: pointer;
&:hover {
background-color: #CCC;
}
&:active {
background-color: #BBB;
}
}
}
}
textarea {
@include fill-parent;
@include isso-shadow;
min-height: 48px;
}
}
}

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