forked from Screem/yarn-docs
parent
e01466f641
commit
0aef42600d
@ -0,0 +1,9 @@
|
||||
*~
|
||||
*.bak
|
||||
|
||||
/.pub
|
||||
/.git
|
||||
/README.md
|
||||
/.drone.yml
|
||||
/.gitignore
|
||||
/.dockerignore
|
@ -0,0 +1,35 @@
|
||||
---
|
||||
kind: pipeline
|
||||
name: 🐳 Docker
|
||||
|
||||
steps:
|
||||
- name: 📦 Image
|
||||
image: plugins/kaniko
|
||||
settings:
|
||||
repo: r.mills.io/prologic/docs.yarn.social
|
||||
tags: latest
|
||||
when:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: 🥳 Done
|
||||
|
||||
steps:
|
||||
- name: 🔔 Notify
|
||||
image: plugins/webhook
|
||||
settings:
|
||||
urls:
|
||||
- https://msgbus.mills.io/ci.mills.io
|
||||
|
||||
depends_on:
|
||||
- 🐳 Docker
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
JS="live"
|
||||
|
||||
ext="js"
|
||||
if [ -n "$ZS_PRODUCTION" ]; then
|
||||
ext="min.js"
|
||||
fi
|
||||
|
||||
for js in $JS; do
|
||||
printf "<script type=\"application/javascript\" src=\"/js/%s.%s\"></script>\n" "$js" "$ext"
|
||||
done
|
@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
CSS="style"
|
||||
|
||||
ext="css"
|
||||
if [ -n "$ZS_PRODUCTION" ]; then
|
||||
ext="min.css"
|
||||
fi
|
||||
|
||||
for css in $CSS; do
|
||||
printf "<link rel=\"stylesheet\" href=\"/css/%s.%s\">\n" "$css" "$ext"
|
||||
done
|
@ -0,0 +1,8 @@
|
||||
*~
|
||||
*.bak
|
||||
|
||||
COPYING
|
||||
Dockerfile
|
||||
LICENSE
|
||||
Makefile
|
||||
README.md
|
@ -0,0 +1,17 @@
|
||||
# Build
|
||||
FROM prologic/zs AS build
|
||||
|
||||
RUN mkdir -p /src
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
# Copy content
|
||||
COPY . .
|
||||
|
||||
# Build the site
|
||||
RUN zs build
|
||||
|
||||
# Runtime
|
||||
FROM prologic/zs AS runtime
|
||||
|
||||
COPY --from=build /src/.pub /data
|
@ -0,0 +1,29 @@
|
||||
.PHONY: deps dev build image clean
|
||||
|
||||
GOCMD=go
|
||||
IMAGE := r.mills.io/prologic/docs.yarn.social
|
||||
TAG := latest
|
||||
|
||||
all: build
|
||||
|
||||
deps:
|
||||
@$(GOCMD) install go.mills.io/zs@latest
|
||||
@$(GOCMD) install github.com/tdewolff/minify/v2/cmd/minify@latest
|
||||
|
||||
dev : DEBUG=1
|
||||
dev : build
|
||||
@zs serve
|
||||
|
||||
build:
|
||||
@zs build
|
||||
|
||||
ifeq ($(PUBLISH), 1)
|
||||
image:
|
||||
@docker buildx build --platform linux/amd64,linux/arm64 --push -t $(IMAGE):$(TAG) .
|
||||
else
|
||||
image:
|
||||
@docker build -t $(IMAGE):$(TAG) .
|
||||
endif
|
||||
|
||||
clean:
|
||||
@git clean -f -d -X
|
@ -1,271 +1,260 @@
|
||||
:root {
|
||||
--primary-color: #11191f;
|
||||
--secondary-color: #141e26;
|
||||
--primary-text-color: #1095c1;
|
||||
--hovered-text-color: #1ab3e6;
|
||||
--text-color: #bbc6ce;
|
||||
--primary-border-color: #233543;
|
||||
--default-margin: 1rem;
|
||||
--primary-color: #11191f;
|
||||
--secondary-color: #141e26;
|
||||
--primary-text-color: #1095c1;
|
||||
--hovered-text-color: #1ab3e6;
|
||||
--text-color: #bbc6ce;
|
||||
--primary-border-color: #233543;
|
||||
--default-margin: 1rem;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--text-color);
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
text-decoration: none;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'egoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
|
||||
h1, p {
|
||||
margin:0;
|
||||
background-color: var(--primary-color);
|
||||
color: var(--text-color);
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
text-decoration: none;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'egoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: inherit;
|
||||
color: var(--primary-text-color);
|
||||
transition: all 0.2s ease-in-out;
|
||||
text-decoration: inherit;
|
||||
color: var(--primary-text-color);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--hovered-text-color);
|
||||
text-decoration: underline;
|
||||
color: var(--hovered-text-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--primary-border-color);
|
||||
border: none;
|
||||
border-top: 1px solid var(--primary-border-color);
|
||||
}
|
||||
|
||||
ul, li {
|
||||
padding:0;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
ul,
|
||||
li {
|
||||
padding: 0;
|
||||
list-style: disc;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
.container-flex {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.dropdown-title {
|
||||
padding: 0.5rem 0 0.5rem 0;
|
||||
font-weight: bold;
|
||||
padding: 0.5rem 0 0.5rem 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#responsive-sidebar {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#responsive-sidebar-label-1 {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
margin: -0.2rem 0 0 0.5rem;
|
||||
font-size: 2.5rem;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
margin: -0.2rem 0 0 0.5rem;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
#responsive-sidebar-label-2 {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
margin: -0.9rem 0 0 0.5rem;
|
||||
font-size: 4.5rem;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
margin: -0.9rem 0 0 0.5rem;
|
||||
font-size: 4.5rem;
|
||||
}
|
||||
|
||||
.sidebar-left {
|
||||
background-color: var(--secondary-color);
|
||||
height: 100vh;
|
||||
width: 15vw;
|
||||
float: left;
|
||||
border-right: 1px solid var(--primary-border-color);
|
||||
padding-left: var(--default-margin);
|
||||
position: fixed;
|
||||
background-color: var(--secondary-color);
|
||||
height: 100vh;
|
||||
width: 15vw;
|
||||
float: left;
|
||||
border-right: 1px solid var(--primary-border-color);
|
||||
padding-left: var(--default-margin);
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.sidebar-left > #site-logo {
|
||||
display: block;
|
||||
color: var(--primary-text-color);
|
||||
width: 75px;
|
||||
margin: auto;
|
||||
.sidebar-left>#site-logo {
|
||||
display: block;
|
||||
color: var(--primary-text-color);
|
||||
width: 75px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.sidebar-left hr {
|
||||
width: 100vw;
|
||||
border: none;
|
||||
border-top: 1px solid var(--primary-border-color);
|
||||
border: none;
|
||||
border-top: 1px solid var(--primary-border-color);
|
||||
}
|
||||
|
||||
.sidebar-left > nav, .sidebar-left > nav > .dropdown, .sidebar-left > nav > .dropdown > .dropdown-item, .sidebar-left > nav > .dropdown > li > a {
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.sidebar-left > nav > .dropdown-button {
|
||||
display: none;
|
||||
.sidebar-left>nav>.dropdown-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-left a {
|
||||
display: block;
|
||||
padding: 0.5rem 0 0.5rem 0;
|
||||
display: block;
|
||||
padding: 0.5rem 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.sidebar-left a:hover {
|
||||
background-color: var(--primary-color);
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
#search-bar {
|
||||
width: 100vw;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 17vw;
|
||||
width: 65vw;
|
||||
margin-left: 17vw;
|
||||
width: 65vw;
|
||||
}
|
||||
|
||||
.main-content #site-header {
|
||||
text-align: center;
|
||||
padding: 0.5rem 0 0.5rem 0;
|
||||
text-align: center;
|
||||
padding: 0.5rem 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 0 1rem 0 1rem;
|
||||
}
|
||||
|
||||
.content h1, .content h2, .content h3, .content h4, .content h5, .content h6 {
|
||||
text-align: center;
|
||||
margin: 0 1rem 0 1rem;
|
||||
}
|
||||
|
||||
.content > table {
|
||||
width: 100%;
|
||||
border-radius: var(--default-margin);
|
||||
background-color: var(--secondary-color);
|
||||
border: 1px solid var(--primary-border-color);
|
||||
padding: var(--default-margin);
|
||||
.content>table {
|
||||
width: 100%;
|
||||
border-radius: var(--default-margin);
|
||||
background-color: var(--secondary-color);
|
||||
border: 1px solid var(--primary-border-color);
|
||||
padding: var(--default-margin);
|
||||
}
|
||||
|
||||
.content > table > thead > tr > th {
|
||||
border-bottom: 1px solid var(--primary-border-color);
|
||||
.content>table>thead>tr>th {
|
||||
border-bottom: 1px solid var(--primary-border-color);
|
||||
}
|
||||
|
||||
.content > table > tbody > tr > td {
|
||||
padding: var(--default-margin);
|
||||
.content>table>tbody>tr>td {
|
||||
padding: var(--default-margin);
|
||||
}
|
||||
|
||||
.content > table > tbody > tr:nth-child(even) {
|
||||
background-color: var(--primary-color);
|
||||
.content>table>tbody>tr:nth-child(even) {
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.content > table > tbody > tr > td:nth-child(1) {
|
||||
text-align: center;
|
||||
.content>table>tbody>tr>td:nth-child(1) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content > table > tbody > tr:last-child > td {
|
||||
padding-bottom: 0;
|
||||
.content>table>tbody>tr:last-child>td {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
width: 100vw;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer p {
|
||||
padding: 0.5rem 0 1rem 0;
|
||||
padding: 0.5rem 0 1rem 0;
|
||||
}
|
||||
|
||||
/*1200px (no extra white space on the right) -> 800px (responsive menu)*/
|
||||
/* 1200px (no extra white space on the right) -> 800px (responsive menu) */
|
||||
|
||||
@media only screen and (min-width: 801px) and (max-width: 1200px) {
|
||||
.main-content {
|
||||
margin-left: 18vw;
|
||||
width: 80vw;
|
||||
}
|
||||
.main-content {
|
||||
margin-left: 18vw;
|
||||
width: 80vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
#responsive-sidebar {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#responsive-sidebar-label-1 {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#responsive-sidebar-label-2 {
|
||||
display: block;
|
||||
left: -103vw;
|
||||
transition: left 0.25s ease-out;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#responsive-sidebar:checked ~ .sidebar-left {
|
||||
left: 0;
|
||||
position: fixed;
|
||||
transition: left 0.25s ease-in;
|
||||
}
|
||||
|
||||
#responsive-sidebar:checked ~ #responsive-sidebar-label-2 {
|
||||
left: 0;
|
||||
transition: left 0.25s ease-in;
|
||||
}
|
||||
|
||||
.sidebar-left {
|
||||
left: -103vw;
|
||||
width: 100vw;
|
||||
transition: left 0.25s ease-out;
|
||||
text-align: center;
|
||||
padding-left: 0;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.sidebar-left > #site-logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-left > nav {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.sidebar-left > nav > .dropdown-button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar-left > nav > .dropdown-button {
|
||||
left: -9999px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.sidebar-left > nav > .dropdown {
|
||||
width: 100vw;
|
||||
display: block;
|
||||
padding: 1rem 0 1rem 0;
|
||||
}
|
||||
|
||||
.sidebar-left > nav > .dropdown > .dropdown-item {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-left > nav > #dropdown-button-1:checked ~ #dropdown-1 > .dropdown-item {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar-left > nav > #dropdown-button-2:checked ~ #dropdown-2 > .dropdown-item {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar-left > nav > #dropdown-button-3:checked ~ #dropdown-3 > .dropdown-item {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
width: 100vw;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
#content-title, #content-main {
|
||||
margin-left: var(--default-margin);
|
||||
margin-right: var(--default-margin);
|
||||
}
|
||||
#responsive-sidebar {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#responsive-sidebar-label-1 {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#responsive-sidebar-label-2 {
|
||||
display: block;
|
||||
left: -103vw;
|
||||
transition: left 0.25s ease-out;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#responsive-sidebar:checked~.sidebar-left {
|
||||
left: 0;
|
||||
position: fixed;
|
||||
transition: left 0.25s ease-in;
|
||||
}
|
||||
|
||||
#responsive-sidebar:checked~#responsive-sidebar-label-2 {
|
||||
left: 0;
|
||||
transition: left 0.25s ease-in;
|
||||
}
|
||||
|
||||
.sidebar-left {
|
||||
left: -103vw;
|
||||
width: 100vw;
|
||||
transition: left 0.25s ease-out;
|
||||
text-align: center;
|
||||
padding-left: 0;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.sidebar-left>#site-logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-left>nav {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.sidebar-left>nav>.dropdown-button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar-left>nav>.dropdown-button {
|
||||
left: -9999px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.sidebar-left>nav>.dropdown {
|
||||
width: 100vw;
|
||||
display: block;
|
||||
padding: 1rem 0 1rem 0;
|
||||
}
|
||||
|
||||
.sidebar-left>nav>.dropdown>.dropdown-item {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-left>nav>#dropdown-button-1:checked~#dropdown-1>.dropdown-item {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar-left>nav>#dropdown-button-2:checked~#dropdown-2>.dropdown-item {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar-left>nav>#dropdown-button-3:checked~#dropdown-3>.dropdown-item {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
width: 100vw;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
#content-title,
|
||||
#content-main {
|
||||
margin-left: var(--default-margin);
|
||||
margin-right: var(--default-margin);
|
||||
}
|
||||
}
|
@ -1,8 +1,59 @@
|
||||
---
|
||||
title: Documentation
|
||||
layout: layout.html
|
||||
title: Yarn.social Documentation
|
||||
description: Documentation for Yarn.social's Pod backend, API, Mobile App, Tools and Services.
|
||||
---
|
||||
|
||||
## Overview
|
||||
# Welcome to Yarn.social
|
||||
|
||||
Blah blah blah...
|
||||
Hey there 👋 Welcome to Yarn.social 🤗
|
||||
|
||||
## What is Yarn.social? {#what}
|
||||
|
||||
[Yarn.social](https://yarn.social/) is a non-social social media 🤣 At least that's what some in the community call it.
|
||||
|
||||
Seriously though Yarn.social is a privacy first micro-blogging social media ecosystem. Why do we call it an ecosystem? Well because unlike many traditional "big-tech" social media platforms, Yarn.social is not a "single place" you can go. It is fully decentralised and joining Yarn.social can be as simple as joining an existing Pod to running your own to building your own client and hosting your own feed however you wish!
|
||||
|
||||
Also unlike other social media solutions such as [Mastodon](https://mastodon.social/explore) that uses the [ActivityPub](https://en.wikipedia.org/wiki/ActivityPub) protocol (*often referred to as the "Fediverse")* Yarn.social is also not a protocol, but a set of specifications for how to build clients and publish feeds that clients can consume. Yarn.social is thereby strictly decentralised in the strictest sense where most ActivityPub-based social media software really forms a "distributed network" of nodes.
|
||||
|
||||
Probably the most important thing to remember of all is that Yarn.social is first and foremost privacy first and 100% decentralised and has no interest in any advertising business models and so therefore will never have any ads.
|
||||
|
||||
If you are curious about the history of Yarn.social and how it was formed please have a read of [About Yarn.social](https://yarn.social/about.html)
|
||||
|
||||
## Why Yarn.social? {#why}
|
||||
|
||||
## Technical Stuff {#technical}
|
||||
|
||||
For the technical minded folks that are curious and want to know every little detail of how things work (*and we don't blame you!*) [Yarn.social](https://yarn.social/) is actually just a "brand" name we've given to a project that originally started out as a simple [Twtxt](https://twtxt.readthedocs.org/) Web Client/App. It was then later rebranded (*pretty quickly*) into Twt.social (now gone) and finally what you see here now today.
|
||||
|
||||
As stated before, Yarn.social is an ecosystem
|
||||
|
||||
- Clients such as the ones found on [https://yarn.social#clients](https://yarn.social/#clients)
|
||||
- A set of [specifications](https://dev.twtxt.net/) for how to build clients and publish feeds and interact with others
|
||||
- A collection of supporting tools and [services](https://yarn.social/#services) that support the greater ecosystem.
|
||||
|
||||
And of course Yarn.social has its own client and backend called [yarnd](https://git.mills.io/yarnsocial/yarn) which provides:
|
||||
|
||||
- A (by default) multi-user Web Application that primarily uses Server-side-rendering (SSR) technology with minimal Javascript (*only to enhance the experience*).
|
||||
- Is based on the and uses the [Twtxt](https://twtxt.readthedocs.org/) spec/format itself.
|
||||
- Has a JSON API for interacting with a Pod programatically
|
||||
- And a [Mobile App](https://yarn.social/#mobile-app)!
|
||||
|
||||
### The Stack {#stack}
|
||||
|
||||
For the extra curious and developer in you, here's the tech stack that powers all Yarn.social pods (*instances of yarnd*):
|
||||
|
||||
#### Backend {#backend}
|
||||
|
||||
- Written in [Go](https://golang.org/)
|
||||
- No framework(s).
|
||||
|
||||
#### Frontend {#frontend}
|
||||
|
||||
- Is a Server-side Render Web App (SSR)
|
||||
- Using Go's [html/template](https://pkg.go.dev/html/template)(s)
|
||||
- Very minimal use of Javascript (*only to enhance the User eXperience slightly with graceful degradation*)
|
||||
|
||||
#### Mobile App {#app}
|
||||
|
||||
- Written in [Dart](https://dart.dev/) using the [Flutter](https://flutter.dev/) framework
|
||||
- Talks to `yarnd`'s Backend via its JSON API.
|
@ -0,0 +1,233 @@
|
||||
/*
|
||||
Live.js - One script closer to Designing in the Browser
|
||||
Written for Handcraft.com by Martin Kool (@mrtnkl).
|
||||
|
||||
Version 4.
|
||||
Recent change: Made stylesheet and mimetype checks case insensitive.
|
||||
|
||||
http://livejs.com
|
||||
http://livejs.com/license (MIT)
|
||||
@livejs
|
||||
|
||||
Include live.js#css to monitor css changes only.
|
||||
Include live.js#js to monitor js changes only.
|
||||
Include live.js#html to monitor html changes only.
|
||||
Mix and match to monitor a preferred combination such as live.js#html,css
|
||||
|
||||
By default, just include live.js to monitor all css, js and html changes.
|
||||
|
||||
Live.js can also be loaded as a bookmarklet. It is best to only use it for CSS then,
|
||||
as a page reload due to a change in html or css would not re-include the bookmarklet.
|
||||
To monitor CSS and be notified that it has loaded, include it as: live.js#css,notify
|
||||
*/
|
||||
(function () {
|
||||
|
||||
var headers = { "Etag": 1, "Last-Modified": 1, "Content-Length": 1, "Content-Type": 1 },
|
||||
resources = {},
|
||||
pendingRequests = {},
|
||||
currentLinkElements = {},
|
||||
oldLinkElements = {},
|
||||
interval = 1000,
|
||||
loaded = false,
|
||||
active = { "html": 1, "css": 1, "js": 1 };
|
||||
|
||||
var Live = {
|
||||
|
||||
// performs a cycle per interval
|
||||
heartbeat: function () {
|
||||
if (document.body) {
|
||||
// make sure all resources are loaded on first activation
|
||||
if (!loaded) Live.loadresources();
|
||||
Live.checkForChanges();
|
||||
}
|
||||
setTimeout(Live.heartbeat, interval);
|
||||
},
|
||||
|
||||
// loads all local css and js resources upon first activation
|
||||
loadresources: function () {
|
||||
|
||||
// helper method to assert if a given url is local
|
||||
function isLocal(url) {
|
||||
var loc = document.location,
|
||||
reg = new RegExp("^\\.|^\/(?!\/)|^[\\w]((?!://).)*$|" + loc.protocol + "//" + loc.host);
|
||||
return url.match(reg);
|
||||
}
|
||||
|
||||
// gather all resources
|
||||
var scripts = document.getElementsByTagName("script"),
|
||||
links = document.getElementsByTagName("link"),
|
||||
uris = [];
|
||||
|
||||
// track local js urls
|
||||
for (var i = 0; i < scripts.length; i++) {
|
||||
var script = scripts[i], src = script.getAttribute("src");
|
||||
if (src && isLocal(src))
|
||||
uris.push(src);
|
||||
if (src && src.match(/\blive.js#/)) {
|
||||
for (var type in active)
|
||||
active[type] = src.match("[#,|]" + type) != null
|
||||
if (src.match("notify"))
|
||||
alert("Live.js is loaded.");
|
||||
}
|
||||
}
|
||||
if (!active.js) uris = [];
|
||||
if (active.html) uris.push(document.location.href);
|
||||
|
||||
// track local css urls
|
||||
for (var i = 0; i < links.length && active.css; i++) {
|
||||
var link = links[i], rel = link.getAttribute("rel"), href = link.getAttribute("href", 2);
|
||||
if (href && rel && rel.match(new RegExp("stylesheet", "i")) && isLocal(href)) {
|
||||
uris.push(href);
|
||||
currentLinkElements[href] = link;
|
||||
}
|
||||
}
|
||||
|
||||
// initialize the resources info
|
||||
for (var i = 0; i < uris.length; i++) {
|
||||
var url = uris[i];
|
||||
Live.getHead(url, function (url, info) {
|
||||
resources[url] = info;
|
||||
});
|
||||
}
|
||||
|
||||
// add rule for morphing between old and new css files
|
||||
var head = document.getElementsByTagName("head")[0],
|
||||
style = document.createElement("style"),
|
||||
rule = "transition: all .3s ease-out;"
|
||||
css = [".livejs-loading * { ", rule, " -webkit-", rule, "-moz-", rule, "-o-", rule, "}"].join('');
|
||||
style.setAttribute("type", "text/css");
|
||||
head.appendChild(style);
|
||||
style.styleSheet ? style.styleSheet.cssText = css : style.appendChild(document.createTextNode(css));
|
||||
|
||||
// yep
|
||||
loaded = true;
|
||||
},
|
||||
|
||||
// check all tracking resources for changes
|
||||
checkForChanges: function () {
|
||||
for (var url in resources) {
|
||||
if (pendingRequests[url])
|
||||
continue;
|
||||
|
||||
Live.getHead(url, function (url, newInfo) {
|
||||
var oldInfo = resources[url],
|
||||
hasChanged = false;
|
||||
resources[url] = newInfo;
|
||||
for (var header in oldInfo) {
|
||||
// do verification based on the header type
|
||||
var oldValue = oldInfo[header],
|
||||
newValue = newInfo[header],
|
||||
contentType = newInfo["Content-Type"];
|
||||
switch (header.toLowerCase()) {
|
||||
case "etag":
|
||||
if (!newValue) break;
|
||||
// fall through to default
|
||||
default:
|
||||
hasChanged = oldValue != newValue;
|
||||
break;
|
||||
}
|
||||
// if changed, act
|
||||
if (hasChanged) {
|
||||
Live.refreshResource(url, contentType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// act upon a changed url of certain content type
|
||||
refreshResource: function (url, type) {
|
||||
switch (type.toLowerCase()) {
|
||||
// css files can be reloaded dynamically by replacing the link element
|
||||
case "text/css":
|
||||
var link = currentLinkElements[url],
|
||||
html = document.body.parentNode,
|
||||
head = link.parentNode,
|
||||
next = link.nextSibling,
|
||||
newLink = document.createElement("link");
|
||||
|
||||
html.className = html.className.replace(/\s*livejs\-loading/gi, '') + ' livejs-loading';
|
||||
newLink.setAttribute("type", "text/css");
|
||||
newLink.setAttribute("rel", "stylesheet");
|
||||
newLink.setAttribute("href", url + "?now=" + new Date() * 1);
|
||||
next ? head.insertBefore(newLink, next) : head.appendChild(newLink);
|
||||
currentLinkElements[url] = newLink;
|
||||
oldLinkElements[url] = link;
|
||||
|
||||
// schedule removal of the old link
|
||||
Live.removeoldLinkElements();
|
||||
break;
|
||||
|
||||
// check if an html resource is our current url, then reload
|
||||
case "text/html":
|
||||
if (url != document.location.href)
|
||||
return;
|
||||
|
||||
// local javascript changes cause a reload as well
|
||||
case "text/javascript":
|
||||
case "application/javascript":
|
||||
case "application/x-javascript":
|
||||
document.location.reload();
|
||||
}
|
||||
},
|
||||
|
||||
// removes the old stylesheet rules only once the new one has finished loading
|
||||
removeoldLinkElements: function () {
|
||||
var pending = 0;
|
||||
for (var url in oldLinkElements) {
|
||||
// if this sheet has any cssRules, delete the old link
|
||||
try {
|
||||
var link = currentLinkElements[url],
|
||||
oldLink = oldLinkElements[url],
|
||||
html = document.body.parentNode,
|
||||
sheet = link.sheet || link.styleSheet,
|
||||
rules = sheet.rules || sheet.cssRules;
|
||||
if (rules.length >= 0) {
|
||||
oldLink.parentNode.removeChild(oldLink);
|
||||
delete oldLinkElements[url];
|
||||
setTimeout(function () {
|
||||
html.className = html.className.replace(/\s*livejs\-loading/gi, '');
|
||||
}, 100);
|
||||
}
|
||||
} catch (e) {
|
||||
pending++;
|
||||
}
|
||||
if (pending) setTimeout(Live.removeoldLinkElements, 50);
|
||||
}
|
||||
},
|
||||
|
||||
// performs a HEAD request and passes the header info to the given callback
|
||||
getHead: function (url, callback) {
|
||||
pendingRequests[url] = true;
|
||||
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XmlHttp");
|
||||
xhr.open("HEAD", url, true);
|
||||
xhr.onreadystatechange = function () {
|
||||
delete pendingRequests[url];
|
||||
if (xhr.readyState == 4 && xhr.status != 304) {
|
||||
xhr.getAllResponseHeaders();
|
||||
var info = {};
|
||||
for (var h in headers) {
|
||||
var value = xhr.getResponseHeader(h);
|
||||
// adjust the simple Etag variant to match on its significant part
|
||||
if (h.toLowerCase() == "etag" && value) value = value.replace(/^W\//, '');
|
||||
if (h.toLowerCase() == "content-type" && value) value = value.replace(/^(.*?);.*?$/i, "$1");
|
||||
info[h] = value;
|
||||
}
|
||||
callback(url, info);
|
||||
}
|
||||
}
|
||||
xhr.send();
|
||||
}
|
||||
};
|
||||
|
||||
// start listening
|
||||
if (document.location.protocol != "file:") {
|
||||
if (!window.liveJsLoaded)
|
||||
Live.heartbeat();
|
||||
|
||||
window.liveJsLoaded = true;
|
||||
}
|
||||
else if (window.console)
|
||||
console.log("Live.js doesn't support the file protocol. It needs http.");
|
||||
})();
|
Loading…
Reference in new issue