Para aquellos que les guste como formateamos los comentarios en Sentido Web, vamos a explicar el código creado por Choan:
El proceso que sigue es el siguiente:
Obtener todos los elementos pre.
Para cada pre leer el primer elemento code que tenga.
Obtenemos el texto que contiene.
Dividimos el texto en líneas.
Por cada línea comprobamos si hay algun tipo de comentario, si hay comentario lo separamos en dos partes para luego darle distintos estilos, diferenciando así el formato del comentario.
Nos creamos un elemento ol, y en cada li insertamos una línea obtenida anteriormente.
Sustituimos el elemento pre por el ol.
Destripemos un poco el código:
Primero obtenemos las etiquetas pre, comprobando primero si se puede ejecutar este script (si existe el método getElementsByTagName)
function lipt() {
/* change to FALSE if you don't want to proccess comments (boogie buggy) */
var PROCCESS_COMMENTS = true;
if (!document.getElementsByTagName) {
return;
}
var ns = document.getElementsByTagName('html')[0].getAttribute('xmlns');
// look for pre tags in the doc
var pres = document.getElementsByTagName('pre');
if (0 == pres.length) {
return; // no pre tags, nothing to do
}
Por cada pre, obtenemos el primer elemento code que tenga y obtenemos el texto que contiene.
for (var i = 0; i < pres.length; i++) {
var pre = pres[i];
// search for the first code element inside the pre
var code = pre.getElementsByTagName('code')[0];
if (null == code) {
continue; // no one here, try with the next pre tag
}
// go for the job
var inMultiLineComment = false;
var inHtmlComment = false;
var content = getText(code);
Se normalizan las saltos de línea (solo habrá \n y no otro tipo de codificación) y obtenemos las líneas para tratarlas individualmente.
// normalize new lines
if (!window.opera) { /* Opera seems to have a nice bug with global replacements */
content = content.replace(/\n|\r|\r\n/g, '\n');
} else {
content = content.replace(/\n|\r|\r\n/, '\n');
}
content = content.replace(/^\n*/, ''); /* trim empty lines at start */
content = content.replace(/\n*$/, ''); /* trim empty lines at the end */
var lines = content.split('\n');
Ya tenemos las líneas, ahora comprobamos si tiene comentarios, empezamos sustituyendo los tabuladores por cuatro espacios y contando cuantos espacios hay para diferenciando la profundidad en la tabulación con distintos estilos (que veremos más tarde).
var ol = createElement('ol', ns);
ol.className = 'code';
for (var j = 0; j < lines.length; j++) {
var line = lines[j];
line = line.replace(/\t/g, ' '); // replace tab with four spaces
var cname = 'tab' + (Math.floor(countSpaces(line) / 4)); // className for this line
var restSpaces = countSpaces(line) % 4;
line = line.replace(/^ +/, '');
if (restSpaces) {
for (var k = 0; k < restSpaces; k++) {
line = '\u00A0' + line; /* equivalent in Unicode */
}
}
Empezamos a tratar las líneas comprobando si hay comentarios.
if (inMultiLineComment || inHtmlComment) {
parts = ['', line];
} else {
parts = [line];
}
if (PROCCESS_COMMENTS) {
var slashSlashPos = line.indexOf('//');
var starSlashPos = line.indexOf('/*');
var slashStarPos = line.indexOf('*/');
var htmlCmtStart = line.indexOf('');
Hora de los comentarios, miramos cada tipo de comentarios y tenemos cuidado de que no se encuentren entre comillas u otros casos que invalide el comentario (por ejemplo http://, // no se trata de un comentario).
labelSlashSlash: if (slashSlashPos != -1) {
switch (line.charAt(slashSlashPos -1)) {
case '"':
case "'":
case ':': /* don't process URIs as comments */
break labelSlashSlash;
}
//parts = line.split('//');
//parts[1] = '//' + parts[1];
parts[0] = line.substring(0, slashSlashPos);
parts[1] = line.substring(slashSlashPos)
} else if (starSlashPos != -1) {
switch (line.charAt(starSlashPos -1)) {
case '"':
case "'":
break labelSlashSlash;
}
if (!inMultiLineComment) {
parts = line.split('/*');
parts[1] = '/*' + parts[1];
}
inMultiLineComment = true;
}
labelHtmlCmt: if (htmlCmtStart != -1) {
switch (line.charAt(htmlCmtStart -1)) {
case '"':
case "'":
break labelHtmlCmt;
}
if (!inHtmlComment) {
parts = line.split('
TableCloth es un javascript no intrusivo que permite cambiar el estilo de las tablas y el comportamiento referente al estilo. Esto quiere decir que le cambia el diseño y que existen estilos asociados a filas y columnas cuando se produce un mouseover y un click.
No es necesario incluir código en las tablas, ya sean id o class, ya que el script detecta automáticamente las tablas y las trata.
Además diferencia filas alternas y deselecciona la fila y la columna elegida cuando se hace click en cualquier parte de la página. Tablecloth
VÃa / WebAppers
Prototype ya dispone de documentación oficial. Aprovechando el lanzamiento de la versión 1.5 de la famosa librerÃa han publicado la documentación de la API, un blog y una sección de tutoriales.
La documentación está realmente bien. Bien organizada y bajo un diseño limpio y claro. Pero se echan de menos dos cosas: la posibilidad de que la gente envÃe comentarios y ejemplos como ocurre con la documentación de PHP y un buscador. Respecto al buscador, proporcionan un bookmarklet que cumple esa función pero parece una solución forzada.
La sección de tutoriales y consejos está todavÃa muy vacÃa pero es una buena idea el que la hayan implementado. Actualmente ofrecen dos documentos: Introducción a AJAX y Cómo Prototype extiende DOM.
Y el blog, genial. TodavÃa esta vacÃo pero es lógico… lo acaban de publicar! Desde luego yo ya me he suscrito a las RSS
VÃa / Anieto2k
Fonts on the web are essentially vector based graphics. That’s why you can display them at 12px or 120px and they remain crisp and relatively sharp-edged. Vector means that their shape is determined by points and mathematics to describe the shape, rather than actual pixel data. Because they are vector, it would make sense if we could do things that …
Hoy toca realizar conexiones con el servidor usando llamadas autenticadas. Para eso usaremos JWT (JSON Web Token), que viene a ser el envío de un token en cada llamada que necesita autenticación. El token está formado por tres cadenas codificadas en base64 y separadas por un punto (header.payload.signature). En el payload se envía toda la info que queramos, pero sin pasarse porque el token no debería ser muy largo y sobre todo sin enviar información sensible, porque el token no está encriptado, es por ello que la comunicación navegador/servidor debe ser mediante HTTPS. Si quieres una explicación más detallada, aquí te lo explican mejor.
En el payload vamos a guardar varios claims, entre ellos el username y un uuid que usaremos para validar que el usuario es correcto. JWT asegura que el token es válido, pero no viene mal añadir cierta seguridad. Cuando el usuario se loguea un nuevo uuid es generado, por lo que se comprobará si coinciden para el username enviado.
Ya tenemos la explicación teórica, ahora vamos con la parte de programación, primero la parte servidor y luego el frontend.
Lo primero que tenemos que hacer es añadir un nuevo plugin a hapi.js que se encargue de la autenticación, registrando un nuevo strategy a server.auth que use JWT. El plugin hapi-auth-jwt2 se encargará de toda la autenticación JWT y añadiremos una capa extra de validación comprobando que el username y el uuid coinciden.
Y por último añadimos una nueva ruta para autenticar (login) el usuario, comprobamos que el usuario y la contraseña coinciden, y si es así, creamos un uuid que guardamos en la bd y generamos el JWT y lo enviamos en la cabecera Authorization:
Vale, ya tenemos la parte del servidor, ahora la parte del frontend. Primero es añadir las rutas para /login, /account y /logout. He añadido un meta que indica si la ruta tiene que ser obligatoriamente autenticada, obligatoriamente no autenticada o como sea. Para ello, para cada ruta comprobará si el JWT token está almacenado o no y según el meta redirigirá a home o continuará:
Para gestionar el almacenamiento en el navegador en vez de cookies vamos a usar sessionStorage y localStorage para guardar el token JWT. Como el formulario de login permite recordar la sesión, vamos a usar ambos storages. Si no se recuerda usaremos sessionStorage, que se borrará cuando se cierra el navegador, en caso contrario usaremos localStorage.
import Config from'@/js/config';
/**
* API methods for sesion/local storage.
* Depending on `this.session` it saves only on `sessionStorage` or also in `localStorage`
*
* @since v0.8.0
*/classstorage{
/**
* Constructor
*
* @param {boolean} session If stored only in session
*/constructor( session = false ) {
this.session = session;
}
/**
* It saves the token in the session (and local storage is this.session === false),
*
* @param {strig} token JWT token
*/
setJWTToken( token ) {
sessionStorage.setItem( Config.jwt.storageKey, token );
if ( ! this.session ) {
localStorage.setItem( Config.jwt.storageKey, token );
}
}
/**
* It gets a value from session storage or in local if session = false
*
* @returns {string}
*/
getJWTToken() {
const sessionValue = sessionStorage.getItem( Config.jwt.storageKey );
if ( sessionValue ) {
return sessionValue;
}
if ( ! this.session ) {
const storedValue = localStorage.getItem( Config.jwt.storageKey );
return storedValue;
}
returnnull;
}
/**
* Removes JWT token from session and local storage
*/
removeJWTToken() {
sessionStorage.removeItem( Config.jwt.storageKey );
localStorage.removeItem( Config.jwt.storageKey );
}
}
exportdefault storage;
También hemos creado una librería para tratar las llamadas a la API. Hay dos métodos, uno para autenticar el usuario (login) que mirará la cabecera Authorization, y otro método que obtiene los datos del usuario actual realizando una llamada autenticada enviando el JWT token en la cabecera Authorization:
import Config from'@/js/config';
import Storage from'@/js/utils/storage';
/**
* API backend methods
*
* @since 0.8.0
*/classapiFetch{
/**
* Authenticate an user, if ok, JWT token is sent by the server in Authorization header
*
* @param {string} username User name
* @param {string} password Password
*
* @returns {Promise}
*/
auth( username, password ) {
return fetch( Config.api.user.auth, {
method: 'POST',
body: JSON.stringify(
{ username, password }
),
mode: 'cors',
} ).then( response => {
const auth = response.headers.get( 'Authorization' );
if ( auth ) {
return {
response: true,
token: auth,
};
}
return {
response: false,
message: 'Username or password not valid',
};
} );
}
getUser( username ) {
return fetch( `${ Config.api.user.get }${ username }`, {
method: 'GET',
headers: {
Authorization: new Storage().getJWTToken(),
},
} ).then( response => response.json() );
}
}
exportdefault apiFetch;
Ahora solo faltan los controladores para login, account y logout. Login realizar la llamada al servidor y si se obtiene el JWT se guarda:
<template><sectionclass="section"><divclass="container"><h1class="title">
Login
</h1><divv-if="error"class="columns is-centered has-margin-bottom-2"
><b-notificationclass="column is-7-tablet is-6-desktop is-5-widescreen"type="is-danger"has-iconaria-close-label="Close notification"role="alert"size="is-small "
>
{{ error }}
</b-notification></div></div><divclass="container"><divclass="columns is-centered"><divclass="column is-5-tablet is-4-desktop is-3-widescreen has-background-light login-form"><b-fieldlabel="Username"><b-inputv-model="username"value=""maxlength="30"icon="account-circle-outline"
/></b-field><b-fieldlabel="Password"><b-inputv-model="password"value=""type="password"icon="lock-outline"
/></b-field><divclass="field"><b-checkboxv-model="remember">
Remember me
</b-checkbox></div><divclass="has-text-right"><b-buttontype="is-primary"
@click="submit"
>
Log in
</b-button></div></div></div></div></section></template><script>import ApiFectch from'@/js/utils/api';
import Storage from'@/js/utils/storage';
exportdefault {
name: 'Login',
// Form data and error messages if login fails
data() {
return {
username: '',
password: '',
remember: false,
error: '',
};
},
methods: {
// It logs in, using the backend API for authenticate the user data.// If user logs in, it saves the JWT token in the browser. If not, shows error message.
submit: function() {
const api = new ApiFectch();
api.auth( this.username, this.password )
.then( response => {
const storage = new Storage( ! this.remember );
if ( !! response.response && !! response.token ) {
storage.setJWTToken( response.token );
this.error = false;
// `go` refreshes the page, so user data is updatedthis.$router.go( '/' );
} else {
storage.removeJWTToken();
this.error = response.message;
}
} );
},
},
};
</script><stylelang="scss"scoped>.login-form {
border-radius: 4px;
}
</style>
Logout borra los datos JWT del navegador y redirige a home:
<template><sectionclass="hero is-medium is-primary is-bold"><divclass="hero-body"><divclass="container has-text-centered"><h1class="title">
{{ message }}
</h1><h2class="subtitle">
Miss you 💛
</h2></div></div></section></template><script>import User from'@/js/utils/user';
exportdefault {
name: 'Logout',
// Dummy data
data() {
return {
message: 'Bye',
};
},
// After being created it logs out and go to home
created() {
new User().logout();
// `go` instead of `push` because refreshing the page updates the user data// Maybe using vuex is a better way to do it, or not...this.$router.go( '/' );
},
};
</script>
Y Account recupera los datos del usuario una vez que ha creado el controlador:
<template><divid="account"><sectionclass="hero is-primary is-bold"><divclass="hero-body"><divclass="has-text-centered"><h1class="title">
{{ message }}
</h1><h2class="subtitle">
Your data
</h2></div></div></section><sectionclass="section"><divclass="container"><divclass="tile is-ancestor"><divclass="tile is-parent"><pclass="tile is-child notification">
Some content
</p></div><divclass="tile is-8 is-parent"><divclass="tile is-child notification is-info"><ulid="data"><liv-for="( value, key ) in user":key="key"
>
{{ key }} : {{ value }}
</li></ul></div></div></div></div></section></div></template><script>// Dummy componentimport ApiFectch from'@/js/utils/api';
import User from'@/js/utils/user';
exportdefault {
name: 'Account',
data() {
return {
message: 'Account',
user: {},
};
},
created() {
const user = new User().getCurrentUser();
new ApiFectch().getUser( user.username )
.then( response =>this.user = response );
},
};
</script>
Una gran colección de scripts que nos ayudarán a incluir nuestras galerÃas de fotos, presentaciones y lightboxes. 30 scripts en total, organizadas por:
GalerÃas y lightboxes en Ajax.
GalerÃas de imágenes basadas en Ajax.
GalerÃas CSS+Javascript.
De las 30 me gustarÃa destacar unas cuantas que son las que más me han gustado:
JonDesign’s SmoothGallery : usando mootools v1.0 para su desarrollo, se trata de un script de tan solo 16k, que añade fade, transparencias, thumbnails, vistas preliminares y todo ello con un diseño muy sencillo y llamativo.
Couloir.org: otra galerÃa de imágenes en forma de presentación, con fade, redimensionamiento de la ventana y sonido.
Lightbox2: uno de los mejores y de los más conocidos, transparencias, fade y posibilidad de navegar entre las diferentes imágenes.
Cross Browser Multi-Page Photograph Gallery: Increible galerÃa basada totalmente en CSS de la que ya hemos hecho referencia anteriormente, pero que siempre merece una mención.