robsite

"proposed new tag: IMG" - Februar 1993

Faszinierender Einblick in die Anfänge von HTML. Zeigt auch nett, dass sich existierende Lösungen gegenüber potentiell besseren Ideen meist durchsetzen. Ein paar interessante Stellen:

Netscape-Schöpfer Marc Andreessen, der hier noch am Vorgänger Mosaic arbeitet, schlägt den <img>-Tag vor:

I'd like to propose a new, optional HTML tag:
IMG
...
An example is:
<IMG SRC="file://foobar.com/foo/bar/blargh.xbm">
...
Browsers should be afforded flexibility as to which image formats they support. Xbm and Xpm are good ones to support, for example.

Xbm ist ein interessantes Format. Damals neben GIF eines der wenigen frei benutzbaren Bildformate — JPEG wurde erst fünf Monate vorher im September 1992 fertig und PNG drei Jahre später. Xbm besteht nicht aus einem Binärformat, sondern ist ein Byte-Array in C, damit man es leichter in eigenen Code einbinden kann. Dadurch allerdings auch recht groß.

Eine Idee von Tony Johnson:

I have something very similar in Midas 2.0

<ICON name="NoEntry" href="http://note/foo/bar/NoEntry.xbm">
...
The idea of the name parameter was to allow the browser to have a set of "built in" images. If the name matches a "built in" image it would use that instead of having to go out and fetch the image.

Im Browser eingebaute Standardbilder. Damals wohl zu aufwändig, heute gibt's Emoji.󾟭

Noch ein Vorschlag:

While we are on the subject of new tags, I have another, somewhat similar tag, which I would like to support in Midas 2.0. In principle it is:

<INCLUDE HREF="...">

The intention here would be that the second document is to be included into the first document at the place where the tag occured.
In principle the referenced document could be anything, but the main purpose was to allow images (in this case arbitrary sized) to be embedded into documents.

Zwei Jahre später taucht der <embed>-Tag im Netscape Navigator 2.0 auf. Einige bemängeln, dass eine einheitliche Lösung besser wäre statt für jeden Medientyp ein eigenes Tag zu haben. Doch das Chaos wächst, bald gibt es einen ganzen Zoo an <embed>-ähnlichen Tags. Die damals kurz sinnvollen Java-Applets werden erst mit dem <app>-, dann mit dem <applet>-Tag eingebunden und es gab sogar einen Vorläufer des HTML <video>-Tags: Das DYNSRC-Attribut, mit dem Videoclips oder gar wundervolle 3D-VRML-Welten eingebunden werden können.

1997 wurde versucht, sie alle mit dem <object>-Tag zu vereinheitlichen. <app>, <applet> und dynsrc sind verschwunden, doch der <embed>-Tag lebt immernoch. Selbst Youtube hat ihn bis vor kurzem der Kompatibilität mit alten Browsern wegen noch eingesetzt. Eingebettet in einen <object>-Tag.

Tim Berners-Lee, Erfinder des WWW schreibt:

If you allow an image, then suppose we also allow some content which includes anchors with x,y coordinates within the image. Then the document can intercept mouse clicks and allow hypergraphics

Clientseitige Image-Maps, vier Jahre später in HTML 3.2 eingeführt. Einen Monat vorher hatte laut Wikipedia bereits Kevin Hughes serverseitige Image-Maps erfunden.

Jay C. Weber:

Whatever happened to the enthusiasm for using the MIME typing mechanism? I made a concrete proposal a few months ago, where HREFs can point to other parts in a MIME multipart (and thereby to an "external-body"), and I've seen a similar idea recently regarding embedding media clips in a "simplemail" format.

In HTML eingebettete Medien statt externe, einzelne Dateien, genau wie Emails. Hat sich dankenswerterweise nicht durchgesetzt. Der Server müsste die Seite erst zusammenbauen, statt stur statische Dateien auszuliefern. Auch Caching einzelner Resourcen wäre so unmöglich.

Jedoch, fünf Jahre später werden Data-URIs vorgeschlagen, mit denen genau das möglich ist und die heute z.B. für Musik mit JavaScript Synths eingesetzt werden.

Wait a minute -- let's temporarily forget about MIME, if it clouds the issue. My objection was to the discussion of "how are we going to support embedded images" rather than "how are we going to support embedded objections in various media".

Otherwise, next week someone is going to suggest 'lets put in a new tag
<AUD SRC="file://foobar.com/foo/bar/blargh.snd">'
for audio.

*hust*

Marc Andreessen schlägt noch eine eigene PostScript-ähnliche Grafiksprache vor, implementiert dann aber doch den schnöden <img>-Tag und schafft somit Fakten. Am Ende schaut auch noch Python-Erfinder Guido van Rossum vorbei.

Zum weiterlesen: A History of HTML über die Anfänge des WWW.

· geschichte, html, www ·

Google Maps Animated Marker Move

Marker kann man in Google Maps zwar mit setPosition() verschieben, dies wird aber nicht animiert. Da es doch recht schön ist, wenn Marker nicht einfach rumspringen, schrieb ich die folgende Funktion. Einfach einbinden und an einem Marker aufrufen. Optional jQuery und das jQuery Easing Plugin nehmen, um mehr als nur linear zu animieren.

Demo: (auf Karte klicken)



Demo auf eigener Seite


Die Funktion:

// Animated Marker Movement. Robert Gerlach 2012-2013 https://github.com/combatwombat/marker-animate
// MIT license
//
// params:
// newPosition        - the new Position as google.maps.LatLng()
// options            - optional options object (optional)
// options.duration   - animation duration in ms (default 1000)
// options.easing     - easing function from jQuery and/or the jQuery easing plugin (default 'linear')
// options.complete   - callback function. Gets called, after the animation has finished
google.maps.Marker.prototype.animateTo = function(newPosition, options) {
  defaultOptions = {
    duration: 1000,
    easing: 'linear',
    complete: null
  }
  options = options || {};

  // complete missing options
  for (key in defaultOptions) {
    options[key] = options[key] || defaultOptions[key];
  }

  // throw exception if easing function doesn't exist
  if (options.easing != 'linear') {            
    if (typeof jQuery == 'undefined' || !jQuery.easing[options.easing]) {
      throw '"' + options.easing + '" easing function doesn\'t exist. Include jQuery and/or the jQuery easing plugin and use the right function name.';
      return;
    }
  }
  
  window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  window.cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;

  // save current position. prefixed to avoid name collisions. separate for lat/lng to avoid calling lat()/lng() in every frame
  this.AT_startPosition_lat = this.getPosition().lat();
  this.AT_startPosition_lng = this.getPosition().lng();
  var newPosition_lat = newPosition.lat();
  var newPosition_lng = newPosition.lng();

  // crossing the 180° meridian and going the long way around the earth?
  if (Math.abs(newPosition_lng - this.AT_startPosition_lng) > 180) {
    if (newPosition_lng > this.AT_startPosition_lng) {      
      newPosition_lng -= 360;      
    } else {
      newPosition_lng += 360;
    }
  }

  var animateStep = function(marker, startTime) {            
    var ellapsedTime = (new Date()).getTime() - startTime;
    var durationRatio = ellapsedTime / options.duration; // 0 - 1
    var easingDurationRatio = durationRatio;

    // use jQuery easing if it's not linear
    if (options.easing !== 'linear') {
      easingDurationRatio = jQuery.easing[options.easing](durationRatio, ellapsedTime, 0, 1, options.duration);
    }
    
    if (durationRatio < 1) {
      var deltaPosition = new google.maps.LatLng( marker.AT_startPosition_lat + (newPosition_lat - marker.AT_startPosition_lat)*easingDurationRatio,
                                                  marker.AT_startPosition_lng + (newPosition_lng - marker.AT_startPosition_lng)*easingDurationRatio);
      marker.setPosition(deltaPosition);

      // use requestAnimationFrame if it exists on this browser. If not, use setTimeout with ~60 fps
      if (window.requestAnimationFrame) {
        marker.AT_animationHandler = window.requestAnimationFrame(function() {animateStep(marker, startTime)});                
      } else {
        marker.AT_animationHandler = setTimeout(function() {animateStep(marker, startTime)}, 17); 
      }

    } else {
      
      marker.setPosition(newPosition);

      if (typeof options.complete === 'function') {
        options.complete();
      }

    }            
  }

  // stop possibly running animation
  if (window.cancelAnimationFrame) {
    window.cancelAnimationFrame(this.AT_animationHandler);
  } else {
    clearTimeout(this.AT_animationHandler); 
  }
  
  animateStep(this, (new Date()).getTime());
}

Aufruf: ```javascript var marker = new google.maps.Marker({position: new google.maps.LatLng(0,0), map: myMap, title: 'Hello World!'}); var newPosition = new google.maps.LatLng(13,42);

// move marker in 1000ms and with linear animation. marker.animateTo(newPosition);

// or with callback and options for easing and duration in milliseconds. Needs jQuery Easing Plugin. marker.animateTo(newPosition, { easing: "easeOutBounce", duration: 1000, complete: function() { alert("animation complete"); } });


<p>Das ganze auf <a href="https://github.com/combatwombat/marker-animate">github</a>.</p>
· google maps, javascript ·

Moondog

Moondog > Moondog, born Louis Thomas Hardin (May 26, 1916 – September 8, 1999), was a blind American composer, musician, poet and inventor of several musical instruments. Moving to New York as a young man, Moondog made a deliberate decision to make his home on the streets there [...] > > Most days he could be found in his chosen part of town wearing clothes he had created based on his own interpretation of the Norse god Odin. Thanks to his unconventional outfits and lifestyle, he was known for much of his life as "The Viking of 6th Avenue".

·

Wette: Anzahl der Videotheken in Deutschland

Ludwig und ich gerieten neulich in ein Argument über Videokonsum in Deutschland, genauer über Videotheken vs. Internet.

Er sagt: Die Anzahl der Videotheken in Deutschland mag in den nächsten 5 Jahren zurückgehen, Video-on-Demand wird aber nicht ausschlaggebend sein. Die Filmrechteinhaber werden nicht schnell genug von der Musikindustrie lernen, legale Videoangebote werden immernoch unzureichend sein (zu teuer, selten Originalsprache, DRM, schlechte Qualität). Nicht genug Leute haben Breitband und einfache Möglichkeiten, Filme online zu leihen/kaufen. Nur furchtlose/dumme Nerds raubkopieren noch. Videotheken sind klasse zum Entdecken neuer Filme.

Ich sage: Legale Video-on-Demand-Angebote sind zwar noch schlecht, Piraterie ist zu kompliziert und gefährlich für die meisten Leute und nicht jeder hat die nötige Breitbandverbindung. Aber trotzdem: Physische Datenträger sind am Aussterben; die Blu-Ray wird das letzte physische Medium für Heimvideos sein. In den nächsten 5 Jahren haben mehr Leute Breitband und die Chance, dass sich die Filmindustrie mit ihren Rechten auskäst, steigt jedes Jahr. Zudem sind Videotheken umständlich. Aus dem Haus gehen um einen Film auszuleihen? Und zurückzubringen? Viel zu aufwändig, Faulheit obsiegt.

Es gibt keine Aussicht auf Einigung zwischen uns, die Fronten sind verhärtet. Zeit, die Einsätze zu erhöhen und eine Wette zu starten:

Bis 2017 wird sich die Anzahl der Videotheken in Deutschland halbieren.
Von 2460 in 2011 auf ≤ 1230 in 2017.

Tritt dieser Fall ein, zahlt Ludwig 100€ an Robert.
Tritt dieser Fall nicht ein, zahlt Robert 100€ an Ludwig.

Quelle der Zahlen sind die Geschäftsberichte des IVD (Interessenverband des Video- und Medienfachhandels in Deutschland e.V.). Der folgende Graph wird fortan jährlich aktualisiert:

Quellen: IVD Geschäftsberichte für 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2014, 2015, 2017

Es wird spannend.

Edit: Sieg! Die Wettschuld ist beglichen, die Wette beendet. Dem Verlauf nach müsste es 2021 die letzte handvoll Videotheken geben. Für die 100€ legen wir dann einen schmucken Trauerkranz vor die letzte.

·

ChunkMail

DailyLit ist klasse. Sendet einem täglich einen kleinen Teil eines langen Buches per Mail oder RSS. E-Mails liest man eh; warum so also nicht auch das Buch, das seit 3 Wochen auf dem Nachttisch verstaubt? Nachteil von DailyLit: Keine eigenen Texte hochladbar.

Drum schrieb ich mir gestern ein simples kleines PHP-Script, das einem auf Anfrage den nächsten Teil eines langen Textes per Mail schickt. Mehr nicht. Keine User-Accounts, multiplen Dateien, Admin-Interfaces etc.
Wer es gebrauchen kann:

<?
/*
ChunkMail
(c) Robert Gerlach 2012 - robsite.de

Sends and displays chunks of a long text per mail. Inspired by DailyLit.com

Usage:

1. Upload chunkmail.php to a PHP capable web server

2. Put a long text file in the same folder as chunkmail.php

3. Edit $config below with the name of your long text file.

4. Make the chunkmail folder writeable by PHP (so chunkmail can create meta_$longTextFile, which stores the already mailed pages)

5. Call chunkmail.php:        
    chunkmail.php           - Display smallest unread page. Mail it, if it hasn't been mailed yet.
    chunkmail.php?page=123  - Display page 123. Mail it, if it hasn't been mailed yet.
    chunkmail.php?reset     - Reset list of mailed pages. Display page 1, mail it.

    To send you the newest page daily at 6:00, use cron:
    > crontab -e
    0 6 * * * wget http://example.com/where/chunkmail/is/chunkmail.php > /dev/null

    
    
Optional: Secure chunkmail folder via .htaccess on Apache:

1. Create a .htpasswd file somewhere your webserver can't access it. You can generate the password with tools like
    - http://www.4webhelp.net/us/password.php
    - http://www.htaccesstools.com/htpasswd-generator/

2. Create .htaccess in the chunkmail folder
    AuthUserFile /absolute/path/to/.htpasswd
    AuthType Basic
    AuthName "ChunkMail"
    Require valid-user    

3. Modify wget in crontab to use HTTP-Authentication:
    wget http://YOUR_USERNAME:YOUR_PASSWORD@example.com/where/chunkmail/is/chunkmail.php > /dev/null


License: WSCECPUCYUS - Wear a Slice of Cheese for Every CPU Core You Use the Software on
*/

$config = array(
    'longTextFile'      => "longtextislong.txt",        // Your long text file
    'chunkSize'         => 3*1024,                      // Chunk size in bytes
    'chunkPadding'      => 512,                         // Chunk padding in bytes. Displays a bit of the previous chunk, so you know where you left off.
    'receiver'          => 'you@somewhere.com'          // Receiver email
);

$serverURL = $_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'];
if ($_SERVER['HTTPS']) {
    $serverURL = 'https://'.$serverURL;
} else {
    $serverURL = 'http://'.$serverURL;
}

// File with sent page numbers, comma separated
$metaFile = 'meta_'.$config['longTextFile']; 

// Get text length and pages length
$longText = file_get_contents($config['longTextFile']);
$longTextLength = strlen($longText);
$pagesLength = floor($longTextLength / $config['chunkSize']);
if ($pagesLength < 1) {
    $pagesLength = 1;
}

// Reset sent pages. Start from the beginning
$reset = false;
if (isset($_GET['reset'])) {
    file_put_contents($metaFile, "");
    $reset = true;
}

$currentPage = 0;
$sentPages = array(); // all the already sent pages

if (file_exists($metaFile)) {    
    $fileContents = file_get_contents($metaFile);
    
    if (strlen($fileContents) > 0) {
        $sentPagesFromFile = explode(",", $fileContents);
    } 
    
    if (is_array($sentPagesFromFile)) {        
        $sentPages = $sentPagesFromFile;
    }
}

// Get current page (starting with 0). Either the ?page, or the smallest unread page
if (isset($_GET['page'])) {
    $currentPage = $_GET['page']-1;    

} else {
    // Smallest unread page or last page
    $currentPage = $pagesLength - 1;
    for ($i = 0; $i < $pagesLength; $i++) {
        if (!in_array($i."", $sentPages)) {
            $currentPage = $i."";
            break;
        }
    }
}

if ($currentPage >= $pagesLength) {
    $currentPage = $pagesLength - 1;
} else if ($currentPage < 0) {
    $currentPage = 0;
}

// Get text chunk for this page plus a little padding to ease you into the text
$ratio = $currentPage / $pagesLength;
$chunkStart = $ratio * $longTextLength;
$chunkLength = $longTextLength / $pagesLength;
$chunk = substr($longText, $chunkStart, $chunkLength); // don't bother with cutting off words. That's what the padding is for.

$paddingChunkStart = $chunkStart - $config['chunkPadding'];
if ($paddingChunkStart < 0) {
    $paddingChunkStart = 0;
}
$paddingChunkLength = $chunkStart - $paddingChunkStart;
if ($paddingChunkLength > 0) {
    $paddingChunk = substr($longText, $paddingChunkStart, $paddingChunkLength);
} else {
    $paddingChunk = '';
}

$nextPage = $currentPage + 1;
if ($nextPage >= $pagesLength) {
    $nextPage = $pagesLength - 1;
}

$previousPage = $currentPage - 1;
if ($previousPage < 0) {
    $previousPage = 0;
}

// Send current chunk per mail, but only once
$wasSent = false;
if (!in_array($currentPage, $sentPages)) {
    
    $to = $config['receiver'];
    $subject = $config['longTextFile'] . " ".($currentPage+1) . "/" . $pagesLength . " — ChunkMail";
    
    $message = $subject . "\n\n";
    $message .= $paddingChunk."\n\n--------\n\n".$chunk;
    $message .= "\n\n\n";
    $message .= "Send next unread page: " . $serverURL."\n\n";
    $message .= "First Page: " . $serverURL."?page=1\n";
    $message .= "Previous Page: " . $serverURL."?page=".($previousPage + 1)."\n";
    $message .= "Next Page: " . $serverURL."?page=".($nextPage + 1)."\n";
    $message .= "Last Page: " . $serverURL."?page=".($pagesLength)."\n\n";
    $message .= "Reset mailed pages: " . $serverURL."?reset";
    
    $headers =  'From: chunkmail@'.$_SERVER['SERVER_NAME'] . "\r\n" . 
                'Reply-To: chunkmail@'.$_SERVER['SERVER_NAME'] . "\r\n";                    
    
    mail($to, $subject, $message, $headers);    
    
    $sentPages[] = $currentPage;
    $wasSent = true;
}

sort($sentPages); // unnecessary but nice

file_put_contents($metaFile, implode(",", $sentPages));

?><!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title><?= $config['longTextFile']." ". ($currentPage+1) . "/" . $pagesLength?> — ChunkMail</title>

        <style type="text/css" media="screen">
            div {
                margin-bottom: 20px;
            }
            .reset,
            .wasSent {
                font-weight: bold;
            }
            .paddingChunk {
                color: #999;
            }
            .links a {
                display: inline-block;
                padding: 20px 30px;
                text-decoration: none;
            }
            .links a:first-child,
            .links .reset {
                padding-left: 0;
            }
        </style>    
    </head>
    <body>
        
        <? if ($reset) { ?>
          <div class="reset">
            Resetting pages. Now starting with page 1.
          </div>  
        <? } ?>
        
        <? if ($wasSent) { ?>
          <div class="wasSent">
            This page was sent to <?=htmlentities($config['receiver'])?>.
          </div>  
        <? } ?>
        
        <h1>
            <?= $config['longTextFile']." ". ($currentPage+1) . "/" . $pagesLength?>            
        </h1>
        
        <div class="text">
            <span class="paddingChunk"><?=nl2br(htmlentities($paddingChunk))?></span><?=nl2br(htmlentities($chunk))?>
        </div>
        
        <div class="links">
            <a href="<?=$serverURL;?>?page=1" title="First Page">|<</a>
            <a href="<?=$serverURL;?>?page=<?=$previousPage + 1?>" title="Previous Page"><</a>  
            <a href="<?=$serverURL;?>?page=<?=$nextPage + 1?>" title="Next Page">></a>
            <a href="<?=$serverURL;?>?page=<?=$pagesLength?>" title="Last Page">>|</a> 
            <br>
            <a class="reset" href="<?=$serverURL;?>?reset" title="Reset sent pages">Reset</a> 
        </div>
    </body>
</html>

Später sah ich dann, dass Dripread genau das gleiche macht bzw. DailyLit-mit-eigenen-Texten ist :D

·
Mastodon