TiddlyWiki - losse javascript WIKI, keuze valt op Codiad.

Ingediend door Dirk Hornstra op 18-aug-2019 23:02

In podcast nummer 18 van Scott Hanselman worden Life Hacks besproken. Hier komt even kort TiddlyWiki ter sprake, in een eerdere aflevering heeft hij deze ook genoemd. Zelf heb ik een WIKI draaien op basis van Mediawiki (link), maar die is redelijk groot (138 MB!) en heb ik qua code ook moeten aanpassen om het werkend te krijgen. De code van TiddlyWiki is vrij te gebruiken en staat op Github (link), je hebt trouwens ook nog gewoon een website waar meer informatie te vinden is: https://tiddlywiki.com/. Scott had het erover dat je deze ook op je bureaublad kon uitvoeren.

Nu kwam ik in een situatie waarin ik een wiki nodig had. Regelmatig krijg ik mailtjes van DesignModo, heb ik nog wel linkjes van sites waar je lettertypes, wordpress-templates en andere zaken voor persoonlijk en/of commercieel gebruik kunt downloaden. Die zet je ergens op een USB-stick of externe disk en dan heb je het na een jaar een keer nodig... waar kan ik dit ook alweer vinden?

Ik wil dus een site waar ik gewoon een soort artikelen met tekst, eventueel screenshots en de download heb. Daar zou ik een wordpress voor kunnen inrichten, maar dat lijkt me een beetje teveel van het goede. Hetzelfde geldt dus voor de Mediawiki. Tijd om de TiddlyWiki te testen!

Je kunt op de site een lege HTML downloaden (empty.html). Die doet het, maar dan sla je de .html-file lokaal op. Dat gaat niet werken met een serverside oplossing. De standaard draait op NodeJS, dat draait dan weer niet op mijn site. Er is een PHP-versie, die verwijst naar https://code.google.com/archive/p/bidix/downloads, dat project is in 2008 voor het laatst bijgewerkt. Niet echt recent dus. Wel even gedraaid, maar dan zie je dat er allemaal verwijzingen naar http://tiddlyhome.bidix.info in zitten, wat ik natuurlijk niet wil. Ik zie dit meteen omdat ik op HTTPS draai en dit onder andere afbeeldingen zijn die via HTTP ingeladen worden.

Terug naar de empty.html. Deze hernoemd naar index.html en op mijn omgeving gezet. Deze is nu afgeschermd met gebruikersnaam en wachtwoord (basic authentication). Je kunt de site dus aanroepen met gebruikersnaam:wachtwoord@url van de site om zo in te kunnen loggen. Ik zie dat dit ook gebruikt wordt door TiddlyWiki, in het configuratiescherm (Control Panel) heb je onder de tab Saving een tab "TiddlySpot Saver". Door bij de WIKI naam de gebruikersnaam in te vullen, bij Password het wachtwoord en bij de server URL het huidige domein in te vullen, met daarachter /saver/index.php wil ik wel eens gaan kijken of ik het zo werkend kan krijgen. In die index.php zet ik alleen een "we gaan opslaan" melding. Als ik dan in index.html de gegevens ga opslaan, zie ik via de netwerk-tab in de browser dat er een post van de hele pagina gedaan wordt. Ik krijg vervolgens een "Error while saving:" met daaronder de tekst die ik terug geef.

In index.html zoeken op "Error while saving", dat geeft me de variabele Error/WhileSaving terug. Dat zit dan binnen een callback-functie die als "err"  true (waar) is die melding geeft. Dat zit dan weer binnen de functie renderTiddler. Als ik daarop zoek lijkt het alsof er een soort JSON-bestand verwacht wordt. Ik kijk nog even in de store.php van het PHP-project en als ik dat zo doorkijk lijkt het juist alsof je gewoon niets terug hoeft te geven. Alleen status 200 (de normale status, dus daar hoef je niets extra's voor te doen). En ja, ik krijg inderdaad zo'n pop-up bovenin dat de wiki opgeslagen is!

Omdat ik nog even door die store.php zit te bladeren, vraag ik me af of het aanroepen van dat bestand op zich voldoende is. Deze zet ik dus in de root van de applicatie en in plaats van /saver/index.php vul ik daar in /store.php. Ik pas zaken aan en sla ze op. En verrek, het werkt. Maar.... alles wordt dus in één HTML-bestand geplaatst, die is 2.2 MB. Dus elke keer opslaan zorgt dat dit bestand over de lijn gaat. En het opslaan duurt bij mij ongeveer 25 / 30 seconden. En omdat dit initieel ingericht is om op je eigen computer wat teksten aan te passen zit er geen upload-mogelijkheid in. Via de plugins vind ik dan nog wel de plugin voor "external attachment support", maar bij het toevoegen van een item zie ik daar niets van terug.

Waar ik het nu voor wil gebruiken is dit niet de juiste oplossing. Ik ga kijken of er een alternatief is. Regelmatig kom ik interessante projecten op Github tegen en sla de URL dan op, maar vervolgens doe ik er niets mee. Laat ik in dit geval eens kijken of er wat tussen staat wat me hiermee kan helpen.

Zo kom ik uit bij Codiad (link), een online IDE, die wel wat weg heeft van Microsoft Visual Code. Alleen zie ik wel dat er gezegd wordt dat er inmiddels alternatieven zijn en het project daarom niet meer actief wordt onderhouden. In totaal rond de 8.5 MB, ik ga een poging wagen. ZIP-bestand gedownload, uitgepakt en op FTP gezet (in een soort admin-submap). Als ik daarna naar de URL ga krijg ik het scherm om gebruikersnaam, wachtwoord en projectnaam op te geven. Ook de locatie. Daar vul ik "design" in.

Installatie gaat goed, daarna inloggen. Ik krijg in het linkerscherm mijn project "Dirks Design Project" te zien, maar het ladertje blijft ronddraaien. Onder water zie ik dat components/controller.php een server-error (500) geeft. Na even wat dieper door te zoeken kom ik uit bij lib/diff_match_patch.php. Hier staan een 2-tal functies in die al bestaan. Hier een controle omheen op toegevoegd:


if (function_exists('mb_ord') == false) {
     function mb_ord($v) {
          $k = mb_convert_encoding($v, 'UCS-2LE', 'UTF-8');
          $k1 = ord(substr($k, 0, 1));
          $k2 = ord(substr($k, 1, 1));
          return $k2 * 256 + $k1;
     }
}
if (function_exists('mb_chr') == false) {
     function mb_chr($num){
          return mb_convert_encoding('&#'.intval($num).';', 'UTF-8', 'HTML-ENTITIES');
     }
}

Dan ben ik al een heel eind op weg. Ik kan mappen aanmaken, ik kan bestanden uploaden en aanmaken, tekstbestanden kan ik wijzigen en mijn "screenshot.jpg" kan ik met de inline-preview bekijken. Top! Dan zit ik alleen nog even met de bronbestanden.

Ik wil deze als .ZIP-bestand uploaden, zodat ik zelf, als ik iets nodig heb, dit meteen beschikbaar heb en dat niet via Github of websites nog een keer hoef te downloaden. De "standaard bezoeker" op deze site mag wel de teksten en screenshots zien, maar het is niet de bedoeling dat hij of zij de boel gaat downloaden. Daarom wil ik dat ik op een snelle manier een zip-bestand kan hernoemen.

Even in de wiki van het project gezocht, je kunt dus zelf plugins aanmaken. Je moet een map maken met daarin init.js en plugin.json, deze noem ik codiad.obfuscate. Het was even tunen met de code, omdat ik eigenlijk de standaard "bestand hernoemen" functionaliteit wil gebruiken (en niet kopiëren). Deze maakt een dialoog-venster zichtbaar, alleen zit daar wat vertraging in. Dus met een controle op is(":visible") en maximaal 10x proberen vang ik dat af. En het uniek maken van de bestandsnaam doe ik door een eigen GUID-functie te gebruiken.

Ik maak een map "obfuscate" aan en plaats deze in de plugins-map.
In plugin.json staat de onderstaande code:


[{ "author" : "Dirk Hornstra",
"version": "1.0",
"name" : "obfuscate",
"url" : "https://techblog.dirkhornstra.nl",
"exclude" : "",
"rightbar" : [],
"bottombar" : [],
"contextmenu" : [{
     "action" : "codiad.obfuscate.doRename();",
     "icon" : "icon-pencil",
     "admin" : false,
     "applies-to" : "both",
     "title" : "Obfuscate" }]
}]

In init.js staat deze code:


(function(global, $){
     var codiad = global.codiad;
     $(function() {
          codiad.obfuscate.init();
     });
     codiad.obfuscate = {
          init: function() { },
          doRename: function() {
               codiad.filemanager.renameNode($('#context-menu').attr('data-path'));
               this.inject(0);
          },
          getUniqueGuid: function() {
               // 8-4-4-4-12
               var validItems = [];
               for (k=0;k<10;k++) { validItems.push(k); }
               for (k=65;k<71;k++) { validItems.push(String.fromCharCode(k)); }
               var resultGuid = '';
               for (k=0; k < 32; k++) {
                    var rnd = Math.floor(Math.random() * validItems.length);
                    resultGuid += validItems[rnd];
                    switch(resultGuid.length) {
                         case 8:
                         case 13:
                         case 18:
                         case 23:
                              resultGuid += '-';
                              break;
                    }
               }
               return resultGuid;
          },
          inject: function(counter) {
               counter++;
               try{
                    if (counter >= 10) { return; }
                    if ($('#modal-content form').is(":visible") == false) {
                         var instance = this;
                         var paramcounter = counter;
                         setTimeout(function(){instance.inject(paramcounter)}, 1000);
                    }
                    else {
                         $('#modal-content form input[name="object_name"]').val(this.getUniqueGuid()+'.zip');
                    }
               } catch(e){ console.log(e); }
          }
     };
})(this, jQuery);

De basis werkt hiermee, nu is het een kwestie van data uploaden en goede omschrijvingen toevoegen.

Elke keer maak ik nu een README.md en TAGS.md-bestand aan wat ik wil vullen met de inhoud van de readme-bestanden die vaak bij een download zitten en in de TAGS.md wil ik informatie plaatsen waarmee je makkelijk kunt zoeken. Rechtermuisknop, New File, naam intypen en dan vaak afsluiten. Onnodige handelingen. Eigenlijk wil ik dat die bestanden "automatisch" aangemaakt worden op het moment dat ik een map maak. En ik heb het nu niet nodig, maar hetzelfde zou kunnen gelden voor mappen die je automatisch wilt aanmaken (thumbnails bijvoorbeeld). Ik heb hiervoor de plugin codiad.autocreate gemaakt. Op zich is het simpel, want als er een bestand of map aangemaakt wordt, dan wordt een seintje gestuurd naar de "listeners" die luisteren naar het event "filemanager.onCreate".

Mijn plugin.json bevat de volgende tekst:


[{ "author" : "Dirk Hornstra",
    "version": "1.0",
    "name" : "autocreate",
    "url" : "https://techblog.dirkhornstra.nl",
    "exclude" : "",
    "rightbar" : [],
    "bottombar" : [],
    "contextmenu" : []
    }]

In init.js staat deze code:


(function(global, $){

    var codiad = global.codiad;

    var autoCreateFileItems = ['README.md', 'TAGS.md'];
    var autoCreateFolderItems = [];

    $(function() {
        codiad.autocreate.init();
    });

    codiad.autocreate = {
       
        init: function() {
            amplify.subscribe('filemanager.onCreate', function(data){
                if (data.type == 'directory') {
                    codiad.autocreate.doCreate(data.createPath);
                }
            });
        },

        doCreate: function(path) {
            for (k=0; k < autoCreateFileItems.length; k++) {
                var fileType = 'file';
                var createFilePath = path + '/' + autoCreateFileItems[k];
                $.get(codiad.filemanager.controller + '?action=create&path=' + encodeURIComponent(createFilePath) + '&type=' + fileType, function(data) {
                    var createFileResponse = codiad.jsend.parse(data);
                    if (createFileResponse != 'error') {
                        codiad.message.success(fileType.charAt(0)
                            .toUpperCase() + fileType.slice(1) + ' Created');
                        // Add new element to filemanager screen
                        codiad.filemanager.createObject(path, createFilePath, fileType);
                    }
                });
            }
            for (k=0; k < autoCreateFolderItems.length; k++) {
                var folderType = 'directory';
                var createFolderPath = path + '/' + autoCreateFolderItems[k];
                $.get(codiad.filemanager.controller + '?action=create&path=' + encodeURIComponent(createFolderPath) + '&type=' + folderType, function(data) {
                    var createFolderResponse = codiad.jsend.parse(data);
                    if (createFolderResponse != 'error') {
                        codiad.message.success(folderType.charAt(0)
                            .toUpperCase() + folderType.slice(1) + ' Created');
                        // Add new element to filemanager screen
                        codiad.filemanager.createObject(path, createFolderPath, folderType);
                    }
                });
            }            
        }
    };
})(this, jQuery);

Met deze basis heb ik een back-end-omgeving waar ik met een boom-model de verschillende type ontwerpen kan indelen en opslaan. Ik wil (en moet) nog meer doen, maar dat wordt dan een nieuwe post op dit blog.

De beide plug-ins zijn ook via mijn Github te downloaden:

https://github.com/lordofcode/codiad.autocreate

https://github.com/lordofcode/codiad.obfuscate