Compare commits

...

5 commits

Author SHA1 Message Date
cc6091c7a0 oops
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-11-11 23:37:38 -10:00
906f9638b3 repro-run: rm 2023-11-08 05:30:21 -03:00
f2ae37b059 self contain htmls 2023-11-08 05:17:15 -03:00
423920637c actualizar hooks git 2023-11-06 15:43:27 -03:00
e76296938b limpiar 2023-09-10 17:31:55 -03:00
203 changed files with 1902 additions and 1020 deletions

50
.eleventy.js Normal file
View file

@ -0,0 +1,50 @@
const { basename } = require("path");
const { EleventyHtmlBasePlugin } = require("@11ty/eleventy");
const automaticNoopener = require("eleventy-plugin-automatic-noopener");
const markdownItWikilinks = require("markdown-it-wikilinks")({
uriSuffix: "",
postProcessPagePath: (path) => path,
postProcessLabel: (path) => basename(path).match(/^(?:(?:\d{4})-(?:\d{2})-(?:\d{2})-)?(.+)$/)[1],
relativeBaseURL: "../",
});
const { formatDate } = require("./helpers/date");
/**
* @param {import("@11ty/eleventy").UserConfig} eleventyConfig
*/
module.exports = function config(eleventyConfig) {
eleventyConfig.addPassthroughCopy("drip.css");
eleventyConfig.addPassthroughCopy("cowboy.svg");
eleventyConfig.addPassthroughCopy("status/*");
eleventyConfig.addPassthroughCopy("x/**/*.png");
eleventyConfig.addPassthroughCopy("x/**/*.jpg");
eleventyConfig.addPassthroughCopy("x/**/*.mp4");
eleventyConfig.addPassthroughCopy("bookmarks/**/*.png");
eleventyConfig.addPassthroughCopy("bookmarks/**/*.jpg");
eleventyConfig.addPassthroughCopy("bookmarks/**/*.mp4");
eleventyConfig.addPlugin(EleventyHtmlBasePlugin);
eleventyConfig.addPlugin(automaticNoopener, {
noreferrer: false,
});
eleventyConfig.amendLibrary("md", (mdLib) => mdLib.use(markdownItWikilinks));
eleventyConfig.addCollection("x", (collectionApi) => collectionApi.getFilteredByGlob("x/**/*"));
eleventyConfig.addShortcode(
"dateToISO",
/** @param {Date} date */ (date) => date.toISOString().slice(0, 10)
);
eleventyConfig.addShortcode("formatDate", formatDate);
eleventyConfig.addShortcode("relativeLink", (link, baseUrl) => new URL(link, baseUrl).toString());
// eleventyConfig.addShortcode("formatDateish", formatDateish);
// eleventyConfig.addShortcode("dateishToElement", dateishToElement);
return {
dir: {
output: "build",
},
};
};

View file

@ -1,3 +1,3 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-checkout'.\n"; exit 2; }
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-checkout' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; }
git lfs post-checkout "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-commit'.\n"; exit 2; }
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-commit' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; }
git lfs post-commit "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-merge'.\n"; exit 2; }
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-merge' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; }
git lfs post-merge "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/pre-push'.\n"; exit 2; }
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'pre-push' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; }
git lfs pre-push "$@"

3
.gitignore vendored
View file

@ -1,7 +1,4 @@
build/
node_modules/
build-js/
.env
cache/
rootfs/
.pnpm-store/

1
.npmrc
View file

@ -1 +0,0 @@
@nulo:registry=https://gitea.nulo.in/api/packages/nulo/npm/

View file

@ -1,38 +0,0 @@
_[Parte 2](2023-04-30%20Donweb%20quiere%20tu%20cripto.html)_
Hace unos días compre el "Cloud Server" (VPS) más barato que ofrece [[Donweb]] para experimentar ya que no había visto hosting en Argentina tan barato antes. No ofrecían la posibilidad de bootear distros alternativas a las que te ofrecen (Debian, Ubuntu, CentOS y Rocky) que me pareció raro, pero todo se puede hackear.
Me pareció raro/sospechoso que la instalación de Debian por defecto venía con las llaves SSH de muchisimxs empleadxs de Donweb en el authorized_keys de root dandoles acceso de superusuario, por lo que las borré.
Bootié una imágen de otro de mis proyectos experimentales, [define-alpine](https://gitea.nulo.in/Nulo/define-alpine), ya que quería ver que tan complicado era hacerlo funcionar en un entorno poco flexible como este. Por las 23 horas, lo logré, mandando esta captura de pantalla de victoria a un amigo:
![Captura de pantalla de una terminal de Linux con una sesión recién iniciada](2022-11-18%20Donweb%20es%20ridículo.md-captura-pre.jpg)
Me fui a dormir victorioso. Al día siguiente, por las 10 de la mañana, recibo este mail:
>Buenos días! como estas? espero que te encuentres muy bien! Tomo contacto contigo en esta ocasión para informarte que deberás re-crear el cloud correspondiente ya que has Instalado otro SO arriba de la imagen provista y no esta permitido.
>
>Saludos cordiales,
>
>Quedo a tu disposición y te agradezco califiques mi respuesta porque nos ayudará a mejorar la calidad de atención.
>
><pre>---------------------------------------------</pre><br>
>[CENSURADO]<br>
>Cloud & IaaS Technical Support - Donweb.cloud<br>
><pre>---------------------------------------------</pre>
En ese momento estaba encerrado en una institución educativa, pero mi amigo descubrió que efectivamente sus terminos y condiciones lo prohibian entre otras cosas.
>Se incluye en este punto también cualquier otra información que DonWeb by Dattatec considere inapropiada según su absoluto y exclusivo criterio. Cualquier uso indebido de los servicios autorizará a DonWeb by Dattatec a la suspensión o eliminación de los servicios contratados y sus contenidos sin previo aviso, no haciéndose responsable DonWeb by Dattatec por cualquier pérdida que esto implique.
>El servidor deberá responder a SNMP (http://es.wikipedia.org/wiki/Simple_Network_Management_Protocol) desde ciertas IPs utilizadas por DonWeb by Dattatec para monitoreo del servicio las cuales son asignadas en la configuracion al momento del alta. El cliente no deberá desinstalar el servicio SNMP ni modificar su configuracion.
>DonWeb by Dattatec se reserva el derecho de suspender el servicio en cualquier momento en caso de detectar alguna anormalidad en las configuraciones antes mencionadas y no poder acceder al servidor para realizar las correcciones necesarias.
>IMPORTANTE: Una vez adquirido el Cloud Server, el cliente tiene la posibilidad de instalar cualquier imagen de los SO ofrecidos que se ajuste a sus necesidades. No obstante, el cliente debe abstenerse de instalar (o pisar una instalación) con una distribución y/o Sistema Operativo distinto a los ofrecidos en el catálogo de imágenes de DonWeb. DonWeb se reserva el derecho de suspender sin previo aviso el servicio en caso que se detecte lo antes mencionado o cualquier acción que comprometa la seguridad e integridad del servicio o la compañía.
DonYuta o RatiWeb, bue. Cuando logre escapar de dicha institución educativa y llegué a mi computador portable, descubrí que Donweb se había tomado la libertad de entrar al VNC de mi servidor a clavarse unos comandos:
![Captura de pantalla de una terminal de Linux con varios comandos consultando información sobre el sistema operativo que corría, entre otras cosas](2022-11-18%20Donweb%20es%20ridículo.md-captura-post.jpg)
No recomiendo.

1
404.md
View file

@ -1 +0,0 @@
No encontré esta página... Quizás me podrías [avisar](Contacto.html). o/

View file

@ -1,3 +0,0 @@
![Foto del Atreus v1 de frente](Atreus%20v1.md-frente.jpg)
![Foto del Atreus v1 de atrás, exponiendo sus cables interiores](Atreus%20v1.md-atrás.jpg)

View file

@ -1,10 +0,0 @@
[MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme)
Es una propiedad de CSS que indica en que colores se puede renderizar tu sitio. Si especificas `color-scheme: dark`, los navegadores automáticamente cambian los colores del fondo, el texto, los inputs, etc para ajustarse a eso.
Sin embargo, Safari elije colores pésimos:
<figure>
<img src="./CSS:%20color-scheme-nulo.ar%20coso.jpg">
<figcaption>nulo.ar antes de que especifique colores para los links</figcaption>
</figure>

156
Caddyfile Normal file
View file

@ -0,0 +1,156 @@
{
http_port 8080
}
localhost:8443 {
log
file_server browse
root * ./build
redir "/Microcontrolador (teclados mecánicos).html" "/x/Microcontrolador%20(teclados%20mec%C3%A1nicos)/"
redir "/Monitoreo de censura de Internet.html" "/x/Monitoreo%20de%20censura%20de%20Internet/"
redir "/Mozilla.html" "/x/Mozilla/"
redir "/2022-06-08 Necesitamos seguridad colectiva.html" "/x/2022-06-08-Necesitamos%20seguridad%20colectiva/"
redir "/2022-07-13.html" "/x/2022-07-13/"
redir "/2022-07-16.html" "/x/2022-07-16/"
redir "/2022-10-15 Analisis de la extracción de datos del teléfono de Fernando André Sabag Montiel.html" "/x/2022-10-15-Analisis%20de%20la%20extracci%C3%B3n%20de%20datos%20del%20tel%C3%A9fono%20de%20Fernando%20Andr%C3%A9%20Sabag%20Montiel/"
redir "/2022-10-30 Bugs de accesibilidad.html" "/x/2022-10-30-Bugs%20de%20accesibilidad/"
redir "/2022-11-18 Donweb es ridículo.html" "/x/2022-11-18-Donweb%20es%20rid%C3%ADculo/"
redir "/2023-04-30 Donweb quiere tu cripto.html" "/x/2023-04-30-Donweb%20quiere%20tu%20cripto/"
redir "/404.html" "/x/404/"
redir "/Activismo Gordo.html" "/x/Activismo%20Gordo/"
redir "/Aesthetic.html" "/x/Aesthetic/"
redir "/Alimentación.html" "/x/Alimentaci%C3%B3n/"
redir "/Alpine Linux.html" "/x/Alpine%20Linux/"
redir "/Android.html" "/x/Android/"
redir "/Android Auto.html" "/x/Android%20Auto/"
redir "/Android seguro.html" "/x/Android%20seguro/"
redir "/Antiderechos de autorx.html" "/x/Antiderechos%20de%20autorx/"
redir "/Aprender.html" "/x/Aprender/"
redir "/Archivar los archivos de la dictadura militar.html" "/x/Archivar%20los%20archivos%20de%20la%20dictadura%20militar/"
redir "/2023-02-05 Vergüenza algorítmica.html" "/x/2023-02-05-Verg%C3%BCenza%20algor%C3%ADtmica/"
redir "/Arreglando bugs ajenos.html" "/x/2021-10-11-Arreglando%20bugs%20ajenos/"
redir "/Atreus v1.html" "/x/Atreus%20v1/"
redir "/Atreus v2.html" "/x/Atreus%20v2/"
redir "/BitTorrent.html" "/x/BitTorrent/"
redir "/BitTorrent v2.html" "/x/BitTorrent%20v2/"
redir "/Bluetooth.html" "/x/Bluetooth/"
redir "/Bookmarklets.html" "/x/Bookmarklets/"
redir "/Booteables.html" "/x/Booteables/"
redir "/Boox T68.html" "/x/Boox%20T68/"
redir "/Burn Book.html" "/x/Burn%20Book/"
redir "/C.html" "/x/C/"
redir "/CHINESE GOD OIL.html" "/x/CHINESE%20GOD%20OIL/"
redir "/Chromium.html" "/x/Chromium/"
redir "/Cocina.html" "/x/Cocina/"
redir "/Códigos QR.html" "/x/C%C3%B3digos%20QR/"
redir "/Comics.html" "/x/Comics/"
redir "/Cooperativas.html" "/x/Cooperativas/"
redir "/Cosas.html" "/x/Cosas/"
redir "/CRDT.html" "/x/CRDT/"
redir "/CSS: color-scheme.html" "/x/CSS/"
redir "/curl.html" "/x/curl/"
redir "/DecSync.html" "/x/DecSync/"
redir "/Diseño.html" "/x/Dise%C3%B1o/"
redir "/Disroot.html" "/x/Disroot/"
redir "/DNS.html" "/x/DNS/"
redir "/Donweb.html" "/x/Donweb/"
redir "/Dropbear.html" "/x/Dropbear/"
redir "/E-ink.html" "/x/E-ink/"
redir "/Electronica.html" "/x/Electronica/"
redir "/Email.html" "/x/Email/"
redir "/EPUB.html" "/x/EPUB/"
redir "/Este sitio.html" "/x/Este%20sitio/"
redir "/Experiencing harmful behavior in Alpine.html" "/x/Experiencing%20harmful%20behavior%20in%20Alpine/"
redir "/Fabricación de circuitos impresos.html" "/x/Fabricaci%C3%B3n%20de%20circuitos%20impresos/"
redir "/Facebook.html" "/x/Facebook/"
redir "/Faircamp.html" "/x/Faircamp/"
redir "/FakeSMTP.html" "/x/FakeSMTP/"
redir "/Firecracker.html" "/x/Firecracker/"
redir "/Forgejo.html" "/x/Forgejo/"
redir "/Formatos de texto.html" "/x/Formatos%20de%20texto/"
redir "/Fotografía.html" "/x/Fotograf%C3%ADa/"
redir "/Fuck WebRTC.html" "/x/Fuck%20WebRTC/"
redir "/Git.html" "/x/Git/"
redir "/Gitea.html" "/x/Gitea/"
redir "/GNOME.html" "/x/GNOME/"
redir "/Go.html" "/x/Go/"
redir "/GraphHopper.html" "/x/GraphHopper/"
redir "/Hacks: limpiar servidor.html" "/x/Hacks:%20limpiar%20servidor/"
redir "/HedgeDoc.html" "/x/HedgeDoc/"
redir "/Herramienta de monitoreo de medios.html" "/x/Herramienta%20de%20monitoreo%20de%20medios/"
redir "/HTML.html" "/x/HTML/"
redir "/Ideas.html" "/x/Ideas/"
redir "/Ideas para un sistema operativo propio en Chromebooks.html" "/x/Ideas%20para%20un%20sistema%20operativo%20propio%20en%20Chromebooks/"
redir "/Ideas para una web distribuida.html" "/x/Ideas%20para%20una%20web%20distribuida/"
redir "/Infraestructura.html" "/x/Infraestructura/"
redir "/Internet censurado en escuelas con Plan Sarmiento.html" "/x/Internet%20censurado%20en%20escuelas%20con%20Plan%20Sarmiento/"
redir "/Inversiones.html" "/x/Inversiones/"
redir "/Jackson Burns - SKIN PURIFYING TREATMENT: Underscore's Melodrama.html" "/x/Jackson%20Burns%20-%20SKIN%20PURIFYING%20TREATMENT:%20Underscore's%20Melodrama/"
redir "/Jardin digital.html" "/x/Jardin%20digital/"
redir "/JavaScript.html" "/x/JavaScript/"
redir "/Javier Milei.html" "/x/Javier%20Milei/"
redir "/Keyboard layouts de pocas teclas.html" "/x/Keyboard%20layouts%20de%20pocas%20teclas/"
redir "/Leak OSDE 2022-08.html" "/x/Leak%20OSDE%202022-08/"
redir "/Lenguajes de marcado.html" "/x/Lenguajes%20de%20marcado/"
redir "/Lenguajes de programación.html" "/x/Lenguajes%20de%20programaci%C3%B3n/"
redir "/Lua.html" "/x/Lua/"
redir "/Lua funcional.html" "/x/Lua/"
redir "/Magnets o torrents reproducibles.html" "/x/Magnets%20o%20torrents%20reproducibles/"
redir "/Manjaro.html" "/x/Manjaro/"
redir "/Markdown.html" "/x/Markdown/"
redir "/Marketing.html" "/x/Marketing/"
redir "/Measured boot.html" "/x/Measured%20boot/"
redir "/Menú artístico.gen.html" "/x/Men%C3%BA%20art%C3%ADstico.gen/"
redir "/Mi webring.gen.html" "/"
redir "/Multimetro.html" "/x/Multimetro/"
redir "/NeoMutt.html" "/x/NeoMutt/"
redir "/Nix.html" "/x/Nix/"
redir "/Njalla caído.html" "/x/Njalla%20ca%C3%ADdo/"
redir "/Not So Shoujo Love Story.html" "/x/Not%20So%20Shoujo%20Love%20Story/"
redir "/Nullificación.html" "/bookmarks/2021-12-27-kayak-null/"
redir "/Nutrición.html" "/x/Nutrici%C3%B3n/"
redir "/Olla a presión.html" "/x/Olla%20a%20presi%C3%B3n/"
redir "/OnePlus 5T.html" "/x/OnePlus%205T/"
redir "/Opus Encoding.html" "/x/Opus%20Encoding/"
redir "/PDF.html" "/x/PDF/"
redir "/README.html" "/x/README/"
redir "/Salud mental.html" "/x/Salud%20mental/"
redir "/Seguridad de la infraestructura de llaves pública (PKI).html" "/x/Seguridad%20de%20la%20infraestructura%20de%20llaves%20p%C3%BAblica%20(PKI)/"
redir "/Signal.html" "/x/Signal/"
redir "/simplegit.html" "/x/simplegit/"
redir "/SONG® Music LLC.html" "/bookmarks/2021-12-17-SONG® Music LLC: NEWSONG® Pro+/"
redir "/SQLite.html" "/x/SQLite/"
redir "/Subdivx.html" "/x/Subdivx/"
redir "/Switches.html" "/x/Switches/"
redir "/SyncedStore.html" "/x/SyncedStore/"
redir "/Tailwind CSS.html" "/x/CSS/"
redir "/Teclados.html" "/x/Teclados/"
redir "/Teclados mecánicos.html" "/x/Teclados%20mec%C3%A1nicos/"
redir "/Thinkpad X230.html" "/x/Thinkpad%20X230/"
redir "/Tipear con una mano.html" "/x/Tipear%20con%20una%20mano/"
redir "/To-Do lists.html" "/x/To-Do%20lists/"
redir "/Permacomputación.html" "/x/Permacomputaci%C3%B3n/"
redir "/Piratería.html" "/x/Pirater%C3%ADa/"
redir "/Pleroma.html" "/x/Pleroma/"
redir "/Producción de música.html" "/x/Producci%C3%B3n%20de%20m%C3%BAsica/"
redir "/Programación.html" "/x/Programaci%C3%B3n/"
redir "/ProleText.html" "/x/ProleText/"
redir "/Protocolo de toma de decisiones Sutty v0.1.html" "/x/Protocolo%20de%20toma%20de%20decisiones%20Sutty%20v0.1/"
redir "/Proyectos.html" "/x/Proyectos/"
redir "/PWA.html" "/x/PWA/"
redir "/PXE.html" "/x/PXE/"
redir "/Python.html" "/x/Python/"
redir "/QEMU.html" "/x/QEMU/"
redir "/Twitter.html" "/x/Twitter/"
redir "/txt2txt.html" "/x/txt2txt/"
redir "/underscores - skin purifying treatment.html" "/x/underscores%20-%20skin%20purifying%20treatment/"
redir "/underscores - skin purifying treatment b sides.html" "/x/underscores%20-%20skin%20purifying%20treatment%20b%20sides/"
redir "/VPS.html" "/x/VPS/"
redir "/Web.html" "/x/Web/"
redir "/Wikimedia.html" "/x/Wikimedia/"
redir "/XMPP.html" "/x/XMPP/"
redir "/YouTube.html" "/x/YouTube/"
redir "/Quién soy.html" "/x/Qui%C3%A9n%20soy/"
redir "/YouTube Restricted Mode.html" "/x/YouTube%20Restricted%20Mode/"
}

View file

@ -1,5 +0,0 @@
- [words that cost nothing to scrub from your vocabulary, and maybe you get a tiny sliver of your soul back:<br>
content -> art/video/music/comics/games etc, whatever the actual media is<br>
consume -> watch/listen/read/play etc<br>
IP -> franchise, series, universe, property](https://twitter.com/vectorpoem/status/1575183167497023490)
- ![Gráfico si deberías hacer proyecto](Cosas.md-grafico-hacer-proyecto.jpg)

View file

@ -1,4 +0,0 @@
- [luafun/luafun](https://github.com/luafun/luafun) (enfoque en performance dentro de LuaJIT)
- [lua-stdlib/functional](https://github.com/lua-stdlib/functional)
- [ezerfernandes/funkmoon](https://github.com/ezerfernandes/funkmoon)
- [jhoonb/functional-lua](https://github.com/jhoonb/functional-lua)

4
Lua.md
View file

@ -1,4 +0,0 @@
- [[Lua funcional]]
- [lua-stdlib/lua-stdlib](https://github.com/lua-stdlib/lua-stdlib)
- [gvvaughan/typecheck](https://github.com/gvvaughan/typecheck)
- [lua-stdlib/strict](https://github.com/lua-stdlib/strict) chequea que hayas definido las variables

View file

@ -1,3 +0,0 @@
- ![Una captura de pantalla de un sitio de pasajes de avión (KAYAK) que dice "Alright! You're off to null." (¡Listo! Ya estás para ir a nulo.)](Nullificación.md-kayak.jpg)
[twitter.com/RReverser](https://twitter.com/RReverser/status/1466813271000981508)

View file

@ -1,13 +1,5 @@
Este es el README para [el repositorio del sitio](https://gitea.nulo.in/Nulo/sitio).
## Compilar
Require [Zig](https://ziglang.org), shell, cmark (con cmark-dev) y Lua 5.1.
```
zig run compilar.zig -lc -lcmark
```
---
[![Una captura de pantalla de unos mensajes diciendo "pero ponele css nulo, una linea tirate"](pero%20ponele%20css.png)](https://copiona.com)

View file

@ -1 +0,0 @@
- [Pines - An Alpine and Tailwind UI Library](https://devdojo.com/pines)

15
_includes/base.hbs Normal file
View file

@ -0,0 +1,15 @@
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="author" content="Nulo" />
<meta property="og:title" content="{{page.title}}" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://nulo.ar{{{page.url}}}" />
<meta property="og:image" content="/cowboy.svg" />
<link rel="stylesheet" href="/drip.css" />
<link rel="icon" href="/cowboy.svg" />
<title>{{page.title}}</title>
</head>
{{{content}}}
</html>

19
_includes/bookmark.hbs Normal file
View file

@ -0,0 +1,19 @@
---
layout: base.hbs
---
<nav>
<a href="/">☚ Volver al inicio</a> | En: bookmarks
{{> buscador}}
</nav>
<article itemscope="" itemtype="https://schema.org/Article">
<header>
<time datetime="{{dateToISO page.date}}" itemprop="datePublished">
{{formatDate page.date false}}
</time>
<h1><a href="{{link}}">{{link}}</a></h1>
<h3>{{page.fileSlug}}</h3>
</header>
<main itemprop="articleBody" data-pagefind-body>
{{{content}}}
</main>
</article>

View file

@ -1,4 +1,4 @@
<script src="_pagefind/pagefind-ui.js" type="text/javascript" defer="defer"></script>
<script src="/_pagefind/pagefind-ui.js" type="text/javascript" defer="defer"></script>
<div id="search"></div>
<script>
window.addEventListener("DOMContentLoaded", (event) => {

36
_includes/post.hbs Normal file
View file

@ -0,0 +1,36 @@
---
layout: base.hbs
---
<nav>
<a href="/">☚ Volver al inicio</a>
{{> buscador}}
</nav>
<article itemscope="" itemtype="https://schema.org/Article">
<header>
<h1>{{title}}</h1>
{{#unless dateInTitle}}
<time datetime="{{dateToISO page.date}}" itemprop="datePublished">
{{formatDate page.date false}}
</time> /
{{/unless}}
<a
href="https://gitea.nulo.in/Nulo/sitio/commits/branch/ANTIFASCISTA/{{{page.inputPath}}}"
>Historial</a>
{{#if backlinks.length}}
/ <a href="#conexiones">Conexiones</a>
{{/if}}
</header>
<main itemprop="articleBody" data-pagefind-body>
{{{content}}}
</main>
{{#if backlinks.length}}
<section id="conexiones">
<h2>⥆ Conexiones ({{backlinks.length}})</h2>
<ul>
{{#each backlinks as |link|}}
<li><a href="{{link.url}}">{{link.title}}</a></li>
{{/each}}
</ul>
</section>
{{/if}}
</article>

View file

@ -1,43 +0,0 @@
<!DOCTYPE html>
<meta charset="utf8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="aaaaaaaaaaaaaaaaaaaaaaaaaaarainbow.html.css" />
<title>atr</title>
<div id="controls">
<div class="coso">
<label for="rect-width">rect width</label>
<input name="rect-width" id="rect-width" type="range" min="2" max="100" />
</div>
<div class="coso">
<label for="rect-height">rect height</label>
<input name="rect-height" id="rect-height" type="range" min="2" max="100" />
</div>
<div class="coso">
<label for="diff-x">diff x</label>
<input
name="diff-x"
id="diff-x"
type="range"
min="-100"
max="100"
step="0.1"
value="10"
/>
</div>
<div class="coso">
<label for="diff-y">diff y</label>
<input
name="diff-y"
id="diff-y"
type="range"
min="-100"
max="100"
step="0.1"
value="10"
/>
</div>
</div>
<canvas></canvas>
<script src="aaaaaaaaaaaaaaaaaaaaaaaaaaarainbow.html.js"></script>

View file

@ -1,17 +0,0 @@
body {
background: white;
color: #111;
font-family: sans-serif;
}
* {
margin: 0;
padding: 0;
}
html {
overflow: hidden;
}
#controls {
position: absolute;
}

View file

@ -1,49 +0,0 @@
// @ts-nocheck
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d", {
alpha: false,
});
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener("resize", resizeCanvas);
resizeCanvas();
function setupInput(inputId, setFunc) {
const input = document.getElementById(inputId);
input.addEventListener("input", (event) => {
setFunc(parseFloat(event.target.value));
});
setFunc(parseFloat(input.value));
}
let rect = { width: 50, height: 100 };
let diff = { x: 1, y: 1 };
setupInput("rect-width", (val) => (rect.width = val));
setupInput("rect-height", (val) => (rect.height = val));
setupInput("diff-x", (val) => (diff.x = val));
setupInput("diff-y", (val) => (diff.y = val));
function draw(time) {
const i = time / 10;
ctx.clearRect(0, 0, canvas.width, canvas.height);
const actualDiff = { x: diff.x / rect.width, y: diff.y / rect.height };
for (let x = 0; x < canvas.width; x += rect.width) {
for (let y = 0; y < canvas.height; y += rect.height) {
ctx.fillStyle = `hsl(${
i + (x / rect.width) * actualDiff.x + (y / rect.height) * actualDiff.y
}, 100%, 50%)`;
ctx.fillRect(x, y, rect.width, rect.height);
}
}
window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);

View file

@ -1,3 +1,8 @@
---
link: "https://newso.ng/musiccypher"
tags: diseño
---
SONG® Music LLC. es "un sello discográfico y una plataforma de distriverificacíon con sede en Nueva York, NY". Es una subsidiaria de Parent Company©.<sup>[songm.us][songm.us]</sup>
[songm.us]: https://songm.us "El sitio de SONG® Music LLC"
@ -7,7 +12,7 @@ SONG® Music LLC. es "un sello discográfico y una plataforma de distriverificac
El sitio para elegir donde escuchar canciones de la discográfica genera opciones exclusivas para desbloquear con "NEWSONG® Pro+".<sup>[newso.ng/musiccypher][newso.ng/musiccypher]</sup>
<figure>
<img alt="Unlock with NEWSONG® Pro+ with three options: Paulstretch this song, Pitch to Zedd and H&M In-Store Playlister" src="SONG® Music LLC.md-NEWSONG_Pro_ejemplo1.png">
<img alt="Unlock with NEWSONG® Pro+ with three options: Paulstretch this song, Pitch to Zedd and H&M In-Store Playlister" src="NEWSONG_Pro_ejemplo1.png">
<figcaption>
- "Paulestirar" esta canción ([un método de estirar una canción sin cambiarle el tono](https://manual.audacityteam.org/man/paulstretch.html))
@ -17,7 +22,7 @@ El sitio para elegir donde escuchar canciones de la discográfica genera opcione
</figcaption>
</figure>
<figure>
<img alt="Unlock with NEWSONG® Pro+ with five options: Review on RateYourMusic, Rebirth in Correct Generation, Direct-to-CDJ Sync (sponsored), Create TikTok Audio (No Attribution) and thissongissick.com Premiere (sponsored)" src="SONG® Music LLC.md-NEWSONG_Pro_ejemplo2.png">
<img alt="Unlock with NEWSONG® Pro+ with five options: Review on RateYourMusic, Rebirth in Correct Generation, Direct-to-CDJ Sync (sponsored), Create TikTok Audio (No Attribution) and thissongissick.com Premiere (sponsored)" src="NEWSONG_Pro_ejemplo2.png">
<figcaption>
- Opinar en RateYourMusic
@ -29,7 +34,7 @@ El sitio para elegir donde escuchar canciones de la discográfica genera opcione
</figcaption>
</figure>
<figure>
<img alt="Unlock with NEWSONG® Pro+ with three options: Sync to iPod Nano, AirDrop (Contacts Only) (sponsored) and Generate Type Beat of this audio" src="SONG® Music LLC.md-NEWSONG_Pro_ejemplo3.png">
<img alt="Unlock with NEWSONG® Pro+ with three options: Sync to iPod Nano, AirDrop (Contacts Only) (sponsored) and Generate Type Beat of this audio" src="NEWSONG_Pro_ejemplo3.png">
<figcaption>
- Sincronizar a iPod Nano
@ -39,7 +44,7 @@ El sitio para elegir donde escuchar canciones de la discográfica genera opcione
</figcaption>
</figure>
<figure>
<img alt="Unlock with NEWSONG® Pro+ with four options: Request a Masterclass from this artist, Send to Kindle (sponsored), Archive this track on Discogs.com and Guitar Tabs" src="SONG® Music LLC.md-NEWSONG_Pro_ejemplo4.png">
<img alt="Unlock with NEWSONG® Pro+ with four options: Request a Masterclass from this artist, Send to Kindle (sponsored), Archive this track on Discogs.com and Guitar Tabs" src="NEWSONG_Pro_ejemplo4.png">
<figcaption>
- Pedir una Masterclass de este artista (Masterclass es una empresa en donde artistas reconocidxs pueden dar clases en línea por precios absurdos)

View file

@ -0,0 +1,6 @@
---
link: https://twitter.com/RReverser/status/1466813271000981508
tags: nullificación
---
![Una captura de pantalla de un sitio de pasajes de avión (KAYAK) que dice "Alright! You're off to null." (¡Listo! Ya estás para ir a nulo.)](kayak.jpg)

3
bookmarks/bookmarks.json Normal file
View file

@ -0,0 +1,3 @@
{
"layout": "bookmark.hbs"
}

View file

@ -1,3 +0,0 @@
body {
margin: auto;
}

View file

@ -1,387 +0,0 @@
import { copyFile, mkdir, opendir, readFile, readdir, writeFile } from "fs/promises";
import { basename, extname, join } from "path";
import * as commonmark from "commonmark";
import {
a,
doctype,
h1,
header,
section,
li,
link,
meta,
metaUtf8,
render,
Renderable,
title,
ul,
h2,
raw,
p,
VirtualElement,
time,
article,
main,
img,
script,
basicElement,
nav,
source,
} from "@nulo/html.js";
const div = basicElement("div");
const reader = new commonmark.Parser({ smart: true });
const writer = new commonmark.HtmlRenderer({ safe: false, smart: true });
const dateFormatter = new Intl.DateTimeFormat("es-AR", {
weekday: "long",
day: "numeric",
month: "long",
year: "numeric",
});
const wikilinkExp = /\[\[(.+?)\]\]/giu;
interface Config {
sourcePath: string;
buildPath: string;
}
const config: Config = {
sourcePath: ".",
buildPath: "build",
};
const buscadorHtml = await readFile("buscador.htm", "utf-8");
const connections = await scanForConnections(config.sourcePath);
await mkdir(config.buildPath, { recursive: true });
const dir = await readdir(config.sourcePath, { withFileTypes: true });
let pageList: { src: string }[] = [];
for (const entry of dir) {
if (!entry.isFile()) continue;
const { name } = entry;
const extension = extname(name);
if ([".ts", ".md", ".css", ".js", ".png", ".jpg", ".mp4", ".svg", ".html"].includes(extension)) {
await copyFile(join(config.sourcePath, name), join(config.buildPath, name));
}
if ([".md"].includes(extension) || name.endsWith(".gen.js")) {
pageList.push({ src: name });
}
}
await Promise.all(pageList.map(({ src }) => compilePage(config, src)));
await compilePageList(config, pageList);
async function compileFile(
config: Config,
sourceFileName: string,
): Promise<{ contentHtml: string; image?: Image }> {
if (extname(sourceFileName) === ".md") {
const { html: contentHtml, image } = await compileMarkdownHtml(config, sourceFileName);
return { contentHtml, image };
} else if (sourceFileName.endsWith(".gen.js")) {
const contentHtml = await compileJavascript(config, sourceFileName);
return { contentHtml };
} else if (sourceFileName.endsWith(".htm"))
return { contentHtml: await readFile(sourceFileName, "utf-8") };
else throw false;
}
async function compilePage(config: Config, sourceFileName: string) {
const name = basename(sourceFileName, extname(sourceFileName));
const isIndex = name === "index";
const title = isIndex ? "nulo.ar" : formatNameToPlainText(name);
const fileConnections = connections.filter(({ linked }) => linked === name);
const { contentHtml, image } = await compileFile(config, sourceFileName);
const html = renderHtml(
...generateHead(title, name),
article(
{ itemscope: "", itemtype: "https://schema.org/Article" },
...(isIndex ? [] : generateHeader(name, sourceFileName, fileConnections.length > 0, image)),
main({ itemprop: "articleBody", "data-pagefind-body": "" }, raw(contentHtml)),
...generateConnectionsSection(fileConnections),
),
);
const outputPath = join(config.buildPath, name + ".html");
await writeFile(outputPath, html);
}
// ==============================================
// Get HTML
// ==============================================
type Image = {
src: string;
alt: string;
};
async function compileMarkdownHtml(
config: Config,
sourceFileName: string,
): Promise<{ html: string; image?: Image }> {
let markdown = await readFile(join(config.sourcePath, sourceFileName), "utf-8");
let image;
if (markdown.startsWith("!!")) {
const node = reader.parse(markdown.slice(1, markdown.indexOf("\n")));
const imageNode = node.firstChild?.firstChild;
if (!imageNode || !imageNode.destination)
throw new Error("Intenté parsear un ^!! pero no era una imágen");
if (!imageNode.firstChild?.literal) console.warn(`El ^!! de ${sourceFileName} no tiene alt`);
image = {
src: imageNode.destination,
alt: imageNode.firstChild?.literal || "",
};
markdown = markdown.slice(markdown.indexOf("\n"));
}
const parsed = reader.parse(markdown);
const markdownHtml = writer.render(parsed);
checkLinks(sourceFileName, markdownHtml);
const contentHtml = await hackilyTransformHtml(markdownHtml);
return { html: contentHtml, image };
}
async function compileJavascript(config: Config, sourceFileName: string): Promise<string> {
const fn = await import("./" + join(config.sourcePath, sourceFileName));
return await fn.default();
}
// ==============================================
// Generated HTML
// ==============================================
function renderHtml(...world: Renderable[]): string {
return `<!doctype html><html lang="es">` + render(...world) + "</html>";
}
function generateHead(titlee: string, outputName: string): Renderable[] {
// TODO: deshardcodear og:url
return [
metaUtf8,
meta({
name: "viewport",
content: "width=device-width, initial-scale=1.0",
}),
meta({ name: "author", content: "Nulo" }),
meta({ property: "og:title", content: titlee }),
meta({ property: "og:type", content: "website" }),
meta({ property: "og:url", content: `https://nulo.ar/${outputName}.html` }),
meta({ property: "og:image", content: "cowboy.svg" }),
link({ rel: "stylesheet", href: "drip.css" }),
link({ rel: "icon", href: "cowboy.svg" }),
title(titlee),
];
}
function formatDate(dateish: Dateish, upper: boolean = false): string {
const date = new Date(dateish.year, dateish.month - 1, dateish.day);
const formatted = dateFormatter.format(date);
if (upper) {
// no le digan a la policía del unicode!
return formatted[0].toUpperCase() + formatted.slice(1);
} else return formatted;
}
interface Dateish {
year: number;
month: number;
day: number;
}
function dateishToString({ year, month, day }: Dateish): string {
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
}
type TitleMetadata =
| {
// title puede tener length == 0 y por lo tanto ser falseish
title: string;
date?: Dateish;
}
| { date: Dateish };
function parseName(name: string): TitleMetadata {
const titleWithDate = /^((?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2}))? ?(?<title>.*)$/;
const found = name.match(titleWithDate);
if (!found || !found.groups) throw new Error("Algo raro pasó");
const { title } = found.groups;
const date =
(found.groups.year && {
year: parseInt(found.groups.year),
month: parseInt(found.groups.month),
day: parseInt(found.groups.day),
}) ||
undefined;
// no definir title si es length == 0
if (!title && date) return { date };
return { title, date };
}
function dateishToElement(
dateish: Dateish,
{ itemprop, upper }: { itemprop?: string; upper?: boolean } = {},
): VirtualElement {
return time(
{ datetime: dateishToString(dateish), ...(itemprop ? { itemprop } : {}) },
formatDate(dateish, upper),
);
}
function formatNameToInline(name: string): Renderable[] {
const parsed = parseName(name);
if ("title" in parsed) {
const { title, date } = parsed;
return [title, ...(date ? [` (`, dateishToElement(date), `)`] : [])];
} else {
return [dateishToElement(parsed.date, { upper: true })];
}
}
function formatNameToPlainText(name: string): string {
const parsed = parseName(name);
if ("title" in parsed) {
const { title, date } = parsed;
return title + (date ? ` (${formatDate(date)})` : "");
} else {
return formatDate(parsed.date, true);
}
}
function generateHeader(
name: string,
sourceCodePath: string,
linkConexiones = false,
image?: Image,
): Renderable[] {
const parsedTitle = parseName(name);
return [
nav(a({ href: "." }, "☚ Volver al inicio"), raw(buscadorHtml)),
header(
...(image ? [img({ ...image, itemprop: "image" })] : []),
...("title" in parsedTitle
? [
h1(parsedTitle.title),
...(parsedTitle.date
? [
dateishToElement(parsedTitle.date, {
itemprop: "datePublished",
}),
" / ",
]
: []),
]
: [
h1(
dateishToElement(parsedTitle.date, {
itemprop: "datePublished",
upper: true,
}),
),
]),
a(
{
href: `https://gitea.nulo.in/Nulo/sitio/commits/branch/ANTIFASCISTA/${sourceCodePath}`,
},
"Historial",
),
...(linkConexiones ? [" / ", a({ href: "#conexiones" }, "Conexiones")] : []),
),
];
}
function generateConnectionsSection(fileConnections: Connection[]): Renderable[] {
return fileConnections.length > 0
? [
section(
{ id: "conexiones" },
h2(`⥆ Conexiones (${fileConnections.length})`),
ul(...fileConnections.map(({ linker }) => li(internalLink(linker)))),
),
]
: [];
}
async function compilePageList(config: Config, pageList: { src: string }[]) {
const name = "Lista de páginas";
const outputPath = join(config.buildPath, name + ".html");
const html = renderHtml(
...generateHead(name, name),
...generateHeader(name, "compilar.ts"),
ul(
...pageList
.map(({ src: name }) => basename(name, extname(name)))
.sort((a, b) => a.localeCompare(b, "es", { sensitivity: "base" }))
.map((name) => li(internalLink(name))),
),
);
await writeFile(outputPath, html);
}
// ==============================================
// Conexiones
// ==============================================
interface Connection {
linked: string;
linker: string;
}
async function scanForConnections(sourcePath: string): Promise<Connection[]> {
const dir = await opendir(sourcePath);
let connections: Connection[] = [];
for await (const entry of dir) {
const extension = extname(entry.name);
if (extension === ".md") {
const name = basename(entry.name, ".md");
const file = await readFile(join(config.sourcePath, entry.name), "utf-8");
for (const [, linked] of file.matchAll(wikilinkExp)) {
connections.push({ linked, linker: name });
}
}
}
return connections;
}
// ==============================================
// Markdown utils
// ==============================================
async function hackilyTransformHtml(html: string): Promise<string> {
html = html
.replaceAll("<a h", '<a rel="noopener noreferrer" h')
.replaceAll(wikilinkExp, (_, l) => render(internalLink(l)));
for (const [match, archivo] of html.matchAll(/<nulo-sitio-reemplazar-con archivo="(.+?)" \/>/g)) {
html = html.replace(match, (await compileFile(config, archivo)).contentHtml);
}
return html;
}
function checkLinks(srcName: string, html: string) {
const matches = html.matchAll(wikilinkExp);
const list = pageList.map(({ src }) => basename(src, extname(src)));
if (!matches) return;
for (const match of matches) {
if (!list.some((n) => n === match[1])) {
console.warn(`${srcName} linkea a ${match[1]}, pero no existe`);
}
}
}
// ==============================================
// Linking
// ==============================================
function internalLink(path: string): VirtualElement {
const href = encodeURI(`./${path}.html`);
return a({ href }, ...formatNameToInline(path));
}

View file

@ -93,3 +93,6 @@ li {
padding: 1rem;
border: 1px solid;
}
#search form {
margin: 0;
}

View file

@ -1,7 +1,7 @@
import { readFile, writeFile } from "fs/promises";
import { join } from "path";
const { readFile, writeFile } = require("fs/promises");
const { join } = require("path");
export const feeds = {
const feeds = {
fauno: "https://fauno.endefensadelsl.org/feed.xml",
copiona: "https://copiona.com/feed.xml",
j3s: "https://j3s.sh/feed.atom",
@ -11,14 +11,16 @@ export const feeds = {
};
if (process.argv[2] === "refresh") {
(async () => {
await Promise.all(
Object.entries(feeds).map(async ([name, url]) => {
console.log(`Refreshing ${name}`);
const res = await fetch(url);
const txt = await res.text();
await writeFile(join("cached-feeds/", `${name}.xml`), txt);
}),
})
);
})();
}
/**
@ -26,6 +28,8 @@ if (process.argv[2] === "refresh") {
* @param {string} name
* @returns string
*/
export async function readFeed(name) {
async function readFeed(name) {
return await readFile(join("cached-feeds/", name + ".xml"), "utf-8");
}
module.exports = { feeds, readFeed };

65
helpers/date.js Normal file
View file

@ -0,0 +1,65 @@
const z = require("zod");
const dateishExp = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/;
const zDateish = z
.string()
.regex(dateishExp)
.transform((s) => {
const found = s.match(dateishExp);
return {
year: parseInt(found.groups.year),
month: parseInt(found.groups.month),
day: parseInt(found.groups.day),
};
});
/** @typedef {z.infer<zDateish>} Dateish */
const dateFormatter = new Intl.DateTimeFormat("es-AR", {
weekday: "long",
day: "numeric",
month: "long",
year: "numeric",
timeZone: "UTC",
});
/**
* @param {Dateish} dateish
* @returns Date
*/
function dateishToDate(dateish) {
return new Date(dateish.year, dateish.month - 1, dateish.day);
}
/**
* @param {Date} date
* @param {boolean} upper
* @returns string
*/
function formatDate(date, upper = false) {
const formatted = dateFormatter.format(date);
if (upper) {
// no le digan a la policía del unicode!
return formatted[0].toUpperCase() + formatted.slice(1);
} else return formatted;
}
/**
* @param {Dateish} dateis
* @returns string
*/
function dateishToString({ year, month, day }) {
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
}
// /**
// * @param {Dateish} dateish
// * @param {{ itemprop?: string, upper?: boolean }} params
// * @returns string
// */
// function dateishToElement(dateish, { itemprop, upper } = {}) {
// const itempropParam = (itemprop && ` itemprop="${itemprop}"`) || "";
// return `<time datetime="${dateishToString(dateish)}" ${itempropParam}>${formatDate(
// dateishToDate(dateish),
// upper
// )}</time>`;
// }
module.exports = { formatDate, zDateish, dateishToDate };

View file

@ -1,43 +1,19 @@
import path from "node:path";
import { readFile } from "node:fs/promises";
import { parseFeed as _parseFeed } from "htmlparser2";
import { a, li, raw, render, ul } from "@nulo/html.js";
import { parseDocument } from "htmlparser2";
import { getElementsByTagName } from "domutils";
import { feeds, readFeed } from "./feeds.js";
const { parseFeed: _parseFeed } = require("htmlparser2");
const { parseDocument } = require("htmlparser2");
const { getElementsByTagName } = require("domutils");
const { feeds, readFeed } = require("./feeds.js");
export default async () => {
module.exports = async () => {
const articles = [];
for (const [name, baseUrl] of Object.entries(feeds)) {
/**
* @param {string} link
* @returns string
*/
const relativeLink = (link) => new URL(link, baseUrl).toString();
const rawFeed = await readFeed(name);
const { title, item, link } = parseFeed(baseUrl, rawFeed);
articles.push(
li(
{ class: "article" },
a(
{
href: relativeLink(item.link),
target: "_blank",
rel: "noopener",
},
item.title,
),
// TODO: format date
" via ",
a({ href: relativeLink(link), rel: "noopener" }, title),
),
);
articles.push({ title, item, link, baseUrl });
}
return render(ul(...articles));
return { articles };
};
/**
@ -53,19 +29,19 @@ function parseFeed(feedUrl, rawFeed) {
const feedDom = getElementsByTagName(
(n) => n === "rss" || n === "feed" || n === "rdf:RDF",
dom.childNodes,
false,
false
)[0];
const linksDom = getElementsByTagName(
(t) => ["link", "atom:link"].includes(t),
feedDom.childNodes,
false,
false
);
const linkDom = linksDom.find(
(d) =>
d.attribs.rel === "alternate" ||
// https://datatracker.ietf.org/doc/html/rfc4287#section-4.2.7.2
// >If the "rel" attribute is not present, the link element MUST be interpreted as if the link relation type is "alternate".
!("rel" in d.attribs),
!("rel" in d.attribs)
);
const feedUrll = new URL(feedUrl);

View file

@ -1,19 +1,24 @@
---
layout: base.hbs
templateEngineOverride: hbs,md
---
<h1 class="main-title">nulo❥ar</h1>
> What's bizarre? I mean, we're all pretty bizarre.<br>Some of us are just better at hiding it, that's all.
<nulo-sitio-reemplazar-con archivo="buscador.htm" />
{{> buscador}}
¡Buenas! Este es mi mundo, bienvenidx. ¿Que, [[Quién soy]]? Soy Nulo :)
¡Buenas! Este es mi mundo, bienvenidx. ¿Que, [[/x/Quién soy]]? Soy Nulo :)
- Perdete en la [[Lista de páginas]]
- Perdete en la [[/x/Lista de páginas]]
Algunas cosas que escribí:
- [[2023-04-30 Donweb quiere tu cripto]]
- [[2023-02-05 Vergüenza algorítmica]]
- [[2022-10-15 Analisis de la extracción de datos del teléfono de Fernando André Sabag Montiel]]
- [[Arreglando bugs ajenos]]
- [[/x/2023-04-30-Donweb quiere tu cripto]]
- [[/x/2023-02-05-Vergüenza algorítmica]]
- [[/x/2022-10-15-Analisis de la extracción de datos del teléfono de Fernando André Sabag Montiel]]
- [[/x/2021-10-11-Arreglando bugs ajenos]]
Algunas cosas que hice:
@ -24,7 +29,7 @@ Algunas cosas que hice:
- [Schreiben](https://beta.schreiben.nulo.ar): una aplicación para escribir cosas ([código](https://gitea.nulo.in/Nulo/schreiben))
- Laburos:
- [Salvá la costanera](https://salva-la-costanera.netlify.app/), [código](https://gitea.nulo.in/Nulo/salva-la-costanera)
- Y otros [[Proyectos]].
- Y otros [[/x/Proyectos]].
Algunas cosas de las que soy parte:
@ -35,9 +40,17 @@ Algunos links mios:
- <a rel="me noopener noreferrer" href="https://todon.eu/@Nulo">Mastodon</a>
- Mis [repositorios de código en Gitea](https://gitea.nulo.in/Nulo) y en [GitHub](https://github.com/catdevnull)
## Feed de personas que me parecen copadas ([[Mi webring.gen]])
## Feed de personas que me parecen copadas (un webring)
<nulo-sitio-reemplazar-con archivo="Mi webring.gen.js" />
<ul>
{{#each articles as |article|}}
<li class="article">
<a href="{{relativeLink article.item.link article.baseUrl}}" target="_blank" rel="noopener">{{article.item.title}}</a>
via
<a href="{{article.link}}">{{article.title}}</a>
</li>
{{/each}}
</ul>
## Contacto

View file

@ -1,12 +1,13 @@
{
"name": "sitio",
"type": "module",
"//type": "module",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "esbuild compilar.ts *.js --target=node18 --outdir=build-js --sourcemap && node --enable-source-maps --trace-uncaught build-js/compilar.js && pagefind --source build",
"build-bun": "bun build compilar.ts --target bun > build-js/bun.js && bun build-js/bun.js && pagefind --source build",
"pagefind": "pagefind --source build",
"build": "eleventy && pnpm pagefind",
"watch": "eleventy --watch",
"check": "tsc",
"refresh-feeds": "node feeds.js refresh"
},
@ -14,16 +15,17 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@11ty/eleventy": "^2.0.1",
"@types/commonmark": "^0.27.5",
"@types/node": "^18.11.18",
"esbuild": "^0.16.17",
"typescript": "^4.9.4"
"typescript": "^5.2.2"
},
"dependencies": {
"@nulo/html.js": "^0.0.8",
"commonmark": "^0.30.0",
"domutils": "^3.0.1",
"eleventy-plugin-automatic-noopener": "^2.0.2",
"htmlparser2": "^8.0.2",
"pagefind": "^0.12.0"
"markdown-it-wikilinks": "^1.4.0",
"pagefind": "^0.12.0",
"zod": "^3.22.4"
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,8 +0,0 @@
{
"Command": "/src/build-repro",
"Cache": [
"/home/repro/pnpm-store",
"/home/repro/.cache/pnpm",
"/home/repro/.npm"
]
}

1
status/404.md Normal file
View file

@ -0,0 +1 @@
No encontré esta página... [Volver al hogar](/)

View file

@ -5,5 +5,7 @@
<link rel="stylesheet" href="https://nulo.ar/center.css" />
<title>502 me caí :(</title>
<h1>Me caí :( mala puerta de enlace</h1>
<img src="se-cayó.jpg" alt="Estx servidorx caídx al lado de unx que no" />
<body style="margin: auto">
<h1>Me caí :( mala puerta de enlace</h1>
<img src="se-cayó.jpg" alt="Estx servidorx caídx al lado de unx que no" />
</body>

View file

@ -1,21 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://nulo.in/drip.css" />
<link rel="stylesheet" href="timer.html.css" />
<title>Timer de guita</title>
<main>
<h1>Guita: <span id="guita" data-guita="0">$ 0.00</span></h1>
<div>
<label for="por-hora">Guita por hora por persona:</label>
<input value="0" type="number" id="por-hora" />
</div>
<div>
<label for="personas">Cantidad de personas:</label>
<input value="1" type="number" id="personas" />
</div>
<button>Empezar</button>
</main>
<script src="timer.html.js"></script>

View file

@ -1,14 +0,0 @@
body {
display: flex;
min-height: 100vh;
justify-content: center;
align-items: center;
padding: 0;
}
main {
text-align: center;
padding: 0 1em;
}
input {
width: 5em;
}

View file

@ -1,21 +0,0 @@
// @ts-nocheck
const guitaEl = document.querySelector("#guita");
const porHoraEl = document.querySelector("#por-hora");
const personasEl = document.querySelector("#personas");
const buttonEl = document.querySelector("button");
let interval = null;
buttonEl.addEventListener("click", (event) => {
if (interval) {
clearInterval(interval);
buttonEl.textContent = "Empezar";
} else {
interval = setInterval(() => {
guitaEl.dataset.guita =
parseFloat(guitaEl.dataset.guita) + (porHoraEl.value / 60 / 60) * personasEl.value;
guitaEl.textContent = `$ ${parseFloat(guitaEl.dataset.guita).toFixed(2)}`;
}, 1000);
buttonEl.textContent = "Parar";
}
});

View file

@ -8,6 +8,9 @@
"strict": true,
"allowJs": true,
"checkJs": true,
"noEmit": true
"noEmit": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}

View file

@ -98,7 +98,7 @@ Por un tiempo, tuve una de mis muñecas inaccesibles por unas semanas. Esto me o
Recientemente, en el sitio agregaron una solapa de "transcripción" mostrando todo el dialogo que había en el video del ejercicio. Sin embargo, cuando esta solapa estaba cerrada, se podían seguir seleccionando los enlaces dentro de la solapa con el teclado, haciendo la navegación por teclado tediosa.
<video controls src="Arreglando bugs ajenos.md-details.mp4">Tu navegador no soporta video HTML5.</video>
<video controls src="details.mp4">Tu navegador no soporta video HTML5.</video>
La solucion es simplemente usar el elemento de solapa que ya viene con el navegador: [`<details>`](https://developer.mozilla.org/es/docs/Web/HTML/Element/details). (La página de `<details>` en MDN esta desactualizada al momento de escribir este artículo.)

View file

@ -0,0 +1,40 @@
_[Parte 2](/x/2023-04-30-Donweb%20quiere%20tu%20cripto/)_
Hace unos días compre el "Cloud Server" (VPS) más barato que ofrece [[Donweb]] para experimentar ya que no había visto hosting en Argentina tan barato antes. No ofrecían la posibilidad de bootear distros alternativas a las que te ofrecen (Debian, Ubuntu, CentOS y Rocky) que me pareció raro, pero todo se puede hackear.
Me pareció raro/sospechoso que la instalación de Debian por defecto venía con las llaves SSH de muchisimxs empleadxs de Donweb en el authorized_keys de root dandoles acceso de superusuario, por lo que las borré.
Bootié una imágen de otro de mis proyectos experimentales, [define-alpine](https://gitea.nulo.in/Nulo/define-alpine), ya que quería ver que tan complicado era hacerlo funcionar en un entorno poco flexible como este. Por las 23 horas, lo logré, mandando esta captura de pantalla de victoria a un amigo:
![Captura de pantalla de una terminal de Linux con una sesión recién iniciada](captura-pre.jpg)
Me fui a dormir victorioso. Al día siguiente, por las 10 de la mañana, recibo este mail:
> Buenos días! como estas? espero que te encuentres muy bien! Tomo contacto contigo en esta ocasión para informarte que deberás re-crear el cloud correspondiente ya que has Instalado otro SO arriba de la imagen provista y no esta permitido.
>
> Saludos cordiales,
>
> Quedo a tu disposición y te agradezco califiques mi respuesta porque nos ayudará a mejorar la calidad de atención.
>
> <pre>---------------------------------------------</pre><br>
>
> [CENSURADO]<br>
> Cloud & IaaS Technical Support - Donweb.cloud<br>
>
> <pre>---------------------------------------------</pre>
En ese momento estaba encerrado en una institución educativa, pero mi amigo descubrió que efectivamente sus terminos y condiciones lo prohibian entre otras cosas.
> Se incluye en este punto también cualquier otra información que DonWeb by Dattatec considere inapropiada según su absoluto y exclusivo criterio. Cualquier uso indebido de los servicios autorizará a DonWeb by Dattatec a la suspensión o eliminación de los servicios contratados y sus contenidos sin previo aviso, no haciéndose responsable DonWeb by Dattatec por cualquier pérdida que esto implique.
> El servidor deberá responder a SNMP (http://es.wikipedia.org/wiki/Simple_Network_Management_Protocol) desde ciertas IPs utilizadas por DonWeb by Dattatec para monitoreo del servicio las cuales son asignadas en la configuracion al momento del alta. El cliente no deberá desinstalar el servicio SNMP ni modificar su configuracion.
> DonWeb by Dattatec se reserva el derecho de suspender el servicio en cualquier momento en caso de detectar alguna anormalidad en las configuraciones antes mencionadas y no poder acceder al servidor para realizar las correcciones necesarias.
> IMPORTANTE: Una vez adquirido el Cloud Server, el cliente tiene la posibilidad de instalar cualquier imagen de los SO ofrecidos que se ajuste a sus necesidades. No obstante, el cliente debe abstenerse de instalar (o pisar una instalación) con una distribución y/o Sistema Operativo distinto a los ofrecidos en el catálogo de imágenes de DonWeb. DonWeb se reserva el derecho de suspender sin previo aviso el servicio en caso que se detecte lo antes mencionado o cualquier acción que comprometa la seguridad e integridad del servicio o la compañía.
DonYuta o RatiWeb, bue. Cuando logre escapar de dicha institución educativa y llegué a mi computador portable, descubrí que Donweb se había tomado la libertad de entrar al VNC de mi servidor a clavarse unos comandos:
![Captura de pantalla de una terminal de Linux con varios comandos consultando información sobre el sistema operativo que corría, entre otras cosas](captura-post.jpg)
No recomiendo.

View file

@ -1,22 +1,22 @@
_[Parte 1](2022-11-18%20Donweb%20es%20rid%C3%ADculo.html)_
_[Parte 1](/x/2022-11-18-Donweb%20es%20rid%C3%ADculo/)_
Como si no fuera suficiente, [[Donweb]] me sigue mandando mails por más que me desuscribí varias veces. No debería sorprenderme de la empresa que hace _[Email Marketing](https://envialosimple.com/es-ar)_ (Spam as a Service), pero es hinchapelotas.
Ayer recibí un mail un poco raro.
![un email que proveniente de Donweb pero con el nombre de enviador como "metamask" alertandome de que supuestamente el acceso a mi cripto habría sido suspendido](2023-04-30%20Donweb%20quiere%20tu%20cripto.md-Screenshot%20from%202023-04-30%2016-28-20.png)
![un email que proveniente de Donweb pero con el nombre de enviador como "metamask" alertandome de que supuestamente el acceso a mi cripto habría sido suspendido](screenshot-mail.png)
Al principió quise asumir que quizás Doncopado decidió notificarnos de algo que afectaba a MetaMask en general, aunque realmente sospechaba que era una estafa.
>Hola nulo@redacted
> Hola nulo@redacted
>
>Aquí le escribimos para informarle de un alto riesgo potencial para su crypto de criptomonedas debido a un alto volumen de transacciones en la red de Ethereum durante la actualización reciente de Shanghai. Para proteger sus activos, recomendamos encarecidamente que actualice manualmente su billetera antes del 3 de mayo. Si no actualiza antes de la fecha límite, perderá permanentemente todos sus activos.
> Aquí le escribimos para informarle de un alto riesgo potencial para su crypto de criptomonedas debido a un alto volumen de transacciones en la red de Ethereum durante la actualización reciente de Shanghai. Para proteger sus activos, recomendamos encarecidamente que actualice manualmente su billetera antes del 3 de mayo. Si no actualiza antes de la fecha límite, perderá permanentemente todos sus activos.
>
>### ¿Qué sucede si no se actualiza manualmente?
> ### ¿Qué sucede si no se actualiza manualmente?
>
>Tenga en cuenta que no actualizar su crypto antes de la fecha límite resultará en una pérdida permanente de todos sus activos de criptomonedas.
> Tenga en cuenta que no actualizar su crypto antes de la fecha límite resultará en una pérdida permanente de todos sus activos de criptomonedas.
>
><button>Recuperar mi cuenta</button>
> <button>Recuperar mi cuenta</button>
Clickear el link grande que dice "Recuperar mi cuenta", te envía a `hxxps://my-ethupdatemetas.com/held/importz/`. Este link ahora está caído, pero pedía que ponga la llave secreta de recuperación de la billetera cripto (phishing).
@ -26,16 +26,16 @@ No me sorprende, pero me decepciona aún más.
A veces estos mails de spam hacen parecer que vienen de un lugar cuando en realidad vienen de otro. Por eso hay tecnologías que verifican que vengan del lugar de donde dicen. Este mail fue verificado por mi servidor de mail como proveniente de `cio.donweb.com`.
>```
>* 1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist
>* [URIs: donweb-e.com]
>[snip]
>* -0.0 SPF_PASS SPF: sender matches SPF record
>[snip]
>* -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature
>[snip]
>* 2.3 DCC_CHECK Detected as bulk mail by DCC (dcc-servers.net)
>```
> ```
> * 1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist
> * [URIs: donweb-e.com]
> [snip]
> * -0.0 SPF_PASS SPF: sender matches SPF record
> [snip]
> * -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature
> [snip]
> * 2.3 DCC_CHECK Detected as bulk mail by DCC (dcc-servers.net)
> ```
Fue enviado por `customer.io`. El link actualmente ya no apunta al sitio de phishing, porque `customer.io` bloqueo el link de trackeo (que supongo es hospedado por ellxs) y redirige a `https://customer.io/we-hate-spam/`.

3
x/Atreus v1.md Normal file
View file

@ -0,0 +1,3 @@
![Foto del Atreus v1 de frente](frente.jpg)
![Foto del Atreus v1 de atrás, exponiendo sus cables interiores](atrás.jpg)

View file

View file

@ -1,6 +1,6 @@
Término técnico para esta receta. [Viene de TikTok](https://www.tiktok.com/@foodiechina888/video/7071812170157198594). TRENDING **TOFU** RECIPE IN CHINA.
<video src="CHINESE GOD OIL.md-tiktok.h264.mp4" controls></video>
<video src="tiktok.h264.mp4" controls></video>
1. - Harina de almidón de maíz (maicena) (la receta pide "potato starch" pero YOLO)
- Huevos revueltos (sin cocinar lol)

View file

14
x/CSS/index.md Normal file
View file

@ -0,0 +1,14 @@
## Tailwind
El mejor framework del mundo.
- [Pines - An Alpine and Tailwind UI Library](https://devdojo.com/pines)
## color-scheme ([MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme))
Es una propiedad de CSS que indica en que colores se puede renderizar tu sitio. Si especificas `color-scheme: dark`, los navegadores automáticamente cambian los colores del fondo, el texto, los inputs, etc para ajustarse a eso. Sin embargo, Safari elije colores pésimos:
<figure>
<img src="color-scheme-safari-nulo.ar.jpg" />
<figcaption>nulo.ar antes de que especifique colores para los links</figcaption>
</figure>

5
x/Cosas.md Normal file
View file

@ -0,0 +1,5 @@
- [words that cost nothing to scrub from your vocabulary, and maybe you get a tiny sliver of your soul back:<br>
content -> art/video/music/comics/games etc, whatever the actual media is<br>
consume -> watch/listen/read/play etc<br>
IP -> franchise, series, universe, property](https://twitter.com/vectorpoem/status/1575183167497023490)
- ![Gráfico si deberías hacer proyecto](grafico-hacer-proyecto.jpg)

View file

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