Mobile Working Accessories

I was quite late to the tablet game as I had been burnt by my 2 previous attempts to force a mobile device to do more than it was designed to ( the devices being the Palm III and the Nokia N-Gage) and being more than happy to jump from android phone to full size laptop for my different computing needs, but as I am doing more and more with my Nexus 7 (including writing this blog) I thought I would note down something that I have found to be at odds with the what everyone else seems to be doing.

Data entry on tablets has improved hugely, the smart soft keyboards are brill, I personally think Swift Key on the android is a work of genius and know of at least one occasion where it has lead to someone moving from apple to android, these meet the requirements of most people, BUT I still think there is a place for a proper keyboard and by proper I don’t mean one of those naff portable keyboards that fold up, I have tried 3 different ones and they all compare poorly against the soft keyboards. the only thing that is actually better than the modern soft keyboard is your actual laptop keyboard, and that strangely is the answer.

I have ended up with the Lenovo thinkpad usb keyboard, and quite simply its perfect, its full size with all the proper keys, this is no wonder as it is basically one of the award winning think pad laptop keyboards in a ultra thin case, nipple mouse and all, it is exactly A4 in size so fits in any A4 folder meaning that not only does it fit nicely with any of your papers but cases are easy to get and cheap.

before you scoff, and I get the inevitable “well why not just use a laptop” bare in mind I use a Lenovo W Series so

  • I have a battery life of 2 hours max even with all the extra battery packs rather than the 6 hours plus of a tablet
  • The weight difference is massive
  • I can keep it charged infidelity with something like the Goal Zero Guide 10 (which is my current fav. charging solution) where as with a lot of the serious laptops you cant charge off anything but mains e.g. my W510 uses a 135W charger which plane and car chargers simply sulk over.
  • Its a cheap, easy to replace and I don’t have to get a special tablet such as the Asus transformer.

At IBM connect there were a couple of times when I pulled out a full size keyboard that I generated a moment of mirth that moved to a ‘Ooooo’ when I simply plugged it in and started typing on my lap with no fuss or compromise

An extra advantage for me is that as I also use a think pad laptop the keyboard is exactly the same so I feel completely at home, If you are a Mac boy I would perhaps recommend the apple blue tooth keyboard and get the same advantage

 

The second thing is cases and stands for the nexus 7 and other small tablets, I tried a fold over case and reckon that unless of the highest quality and the tablet is designed for them (Such as with an IPad) they are a pain in the arse, a 7 inch tablet will fit in a jeans back or jacket pocket just fine without a case, but when I tried a case I started leaving it behind as it was just that bit too bulky, it was easier that for the life of the product I simply take care of the darn thing as I do my phone. but there are times when you do need a stand (I’m using one as I type) for this I found the tiny,cheap but perfect Igo mobile phone stand to be just perfect (despite what the guy in the shop insisted)

 

Migrating to Markdown Pt3 From Domino To Markdown

Domino and in particular BlogSphere V3 by the wonderful Declan Lynch has been my blogging platform for years now and has served me well and faithfully, but all good things come to and end as part of the constant fiddling with new stuff LDC do, I have moved my blogging platform to markdown ( The wretch Ben Poole got me started on it ) on the Statamic platform, but what about years of blog entries that are happily snuggled down in my nsf file,

Java to the rescue. I have done a little agent that takes all blogs and exports them to markdown format regardless if they are html of rich text, glues the existing comments on the end of the blog posts, exports quick images and emoticons (while changing their references in the blog posts) and makes a redirects file so all your old external links work

Just copy the below code into a Java agent and set the ‘baseExportDir’ to where ever you want the site to export too. when you run the agent you will end up with a bunch of mark down files representing the blog entries, a redirects.txt file containing the 301 redirects who’s contents you can past into your root directory .htaccess file so all your old link work, a “page” directory with the emoticons in it, and a “blog” directory with all the quick images in it.

As always, yell if there is something missing or wrong

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.text.Normalizer;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.Vector;
import java.util.regex.Pattern;
import lotus.domino.AgentBase;
import lotus.domino.AgentContext;
import lotus.domino.Database;
import lotus.domino.DateTime;
import lotus.domino.Document;
import lotus.domino.DocumentCollection;
import lotus.domino.EmbeddedObject;
import lotus.domino.NotesException;
import lotus.domino.RichTextItem;
import lotus.domino.Session;
import lotus.domino.View;
public class JavaAgent extends AgentBase {
    public void NotesMain() {
        try {
            Session session = getSession();
            AgentContext agentContext = session.getAgentContext();
            Database db = agentContext.getCurrentDatabase();
            String baseExportDir = "C:\markdownexport\";
            // ****** start document export ******
            View content = db.getView("vw_Content_Blogs");
            File theDir = new File(baseExportDir);
            if (!theDir.exists())
                theDir.mkdir();
            // create a file to store all the 301 redirections for existing blog
            // entires
            File redirectfile = new File(baseExportDir + "redirects.txt");
            Writer redirect = new BufferedWriter(new FileWriter(redirectfile));
            Document doc = content.getFirstDocument();
            while (doc != null) {
                Writer output = null;
                if (doc.getItemValueString("FORM").equals("content_BlogEntry")) {
                    String filename = "";
                    Vector dM = doc.getItemValue("EntryDate");
                    DateTime dt = (DateTime) dM.elementAt(0);
                    System.out.println(dt.getLocalTime());
                    Date date = dt.toJavaDate();
                    Calendar cal = Calendar.getInstance();
                    cal.setTime(date);
                    // create file name in the correct format for statamic
                    filename = Integer.toString(cal.get(Calendar.YEAR)) + "-" + String.format("%02d", cal.get(Calendar.MONTH) + 1) + "-" + String.format("%02d", Integer.valueOf(cal.get(Calendar.DAY_OF_MONTH)));
                    filename = filename + "-" + doc.getItemValueString("EntryTitle").trim().replaceAll(" ", "-").replaceAll("\\", "-").replaceAll("/", "-").replaceAll(":", "-").replaceAll("\?", "-").replaceAll(""", "-");
                    File file = new File(baseExportDir + filename + ".md");
                    redirectfile.getParentFile().mkdirs();
                    // add redirect to redirect file
                    redirect.write("Redirect 301 /d6plinks/" + doc.getItemValueString("PermaLink") + " /blog/" + filename);
                    redirect.write("rn");
                    output = new BufferedWriter(new FileWriter(file));
                    output.write("---");
                    output.write("rn");
                    output.write("title: '" + doc.getItemValueString("EntryTitle").trim() + "'");
                    output.write("rn");
                    if (doc.getItemValueString("EntryStatus").trim().equals("Published")) {
                        output.write("status: live");
                        output.write("rn");
                    } else {
                        output.write("status: draft");
                        output.write("rn");
                    }
                    // end of meta
                    output.write("---");
                    output.write("rn");
                    output.write("rn");
                    String body = "";
                    // get the body text and clean it up for UTF-8 standard
                    if (doc.getItemValueString("EntryHTML").trim().length() > 1) {
                        body = doc.getItemValueString("EntryHTML").trim();
                    } else {
                        body = doc.getItemValueString("EntryRICH").trim();
                    }
                    body = Normalizer.normalize(body, Normalizer.Form.NFD).replaceAll("\p{InCombiningDiacriticalMarks}+", ""); // .replaceAll("[^\p{ASCII}]",
                    // "");
                    // smart single quotes and apostrophe
                    body = removeMSRubbish(body);
                    output.write(body.replaceAll("http.*?\$File", "/assets/img/blog"));
                    output.write("rn");
                    output.write("rn");
                    // get all the old comments and add them to the bottom of
                    // the blog
                    DocumentCollection responses = doc.getResponses();
                    if (responses.getCount() > 0) {
                        output.write("Old Comments");
                        output.write("rn");
                        output.write("------------");
                        output.write("rn");
                        output.write("rn");
                        Document rdoc = responses.getFirstDocument();
                        while (rdoc != null) {
                            // setting as h5 in markup
                            output.write("##### " + rdoc.getItemValueString("nameAuthor") + "(" + rdoc.getCreated().toString() + ")");
                            output.write("rn");
                            String comment = Normalizer.normalize(rdoc.getItemValueString("body").replaceAll("http.*?\$File", "/assets/img/page"), Normalizer.Form.NFD).replaceAll("\p{InCombiningDiacriticalMarks}+", "").replaceAll("[^\p{ASCII}]", "");
                            comment = removeMSRubbish(comment);
                            output.write(comment);
                            output.write("rn");
                            output.write("rn");
                            rdoc = responses.getNextDocument(rdoc);
                        }
                    }
                    output.close();
                }
                doc = content.getNextDocument(doc);
            }
            redirect.close();
            // ****** end document export ******
            // ****** start export quick images, the html references
            // '/assets/img/blog' but im just exporting them to 'blog'******
            String imagesExport = "blog\";
            View view = db.getView("lkp_QuickImages");
            doc = view.getFirstDocument();
            File theImagesDir = new File(baseExportDir + imagesExport);
            if (!theImagesDir.exists())
                theImagesDir.mkdir();
            boolean saveFlag = false;
            while (doc != null) {
                RichTextItem body = (RichTextItem) doc.getFirstItem("ImageFile");
                Vector v = body.getEmbeddedObjects();
                Enumeration e = v.elements();
                while (e.hasMoreElements()) {
                    EmbeddedObject eo = (EmbeddedObject) e.nextElement();
                    if (eo.getType() == EmbeddedObject.EMBED_ATTACHMENT) {
                        eo.extractFile(baseExportDir + imagesExport + eo.getSource());
                    }
                }
                doc = view.getNextDocument(doc);
            }
            // ****** end export quick images ******
            // ****** start export emoticon images, the html references
            // '/assets/img/page' but im just exporting them to 'page'******
            imagesExport = "page\";
            view = db.getView("lkp_Emoticons_Web");
            doc = view.getFirstDocument();
            theImagesDir = new File(baseExportDir + imagesExport);
            if (!theImagesDir.exists())
                theImagesDir.mkdir();
            while (doc != null) {
                System.out.println(doc.getItemValueString("EmoticonName"));
                Vector v = session.evaluate("@AttachmentNames", doc);
                System.out.println("emoticon:" + v.firstElement().toString());
                EmbeddedObject eo = doc.getAttachment(v.firstElement().toString());
                eo.extractFile(baseExportDir + imagesExport + v.firstElement().toString());
                doc = view.getNextDocument(doc);
            }
            // ****** end export images ******
        } catch (NotesException e) {
            System.out.println(e.id + " " + e.text);
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public String removeMSRubbish(String body) {
        body = body.replaceAll("[u2018|u2019|u201A]", "'");
        // smart double quotes
        body = body.replaceAll("[u201C|u201D|u201E]", """);
        // ellipsis
        body = body.replaceAll("u2026", "...");
        // dashes
        body = body.replaceAll("[u2013|u2014]", "-");
        // circumflex
        body = body.replaceAll("u02C6", "^");
        // open angle bracket
        body = body.replaceAll("u2039", "<");
        // close angle bracket
        body = body.replaceAll("u203A", ">");
        // spaces
        body = body.replaceAll("[u02DC|u00A0]", " ");
        return body;
    }
}

Microsoft Naff Characters

I should have done this ages ago, the daft none standard Microsoft characters have bitten me in the bum for years, and I always have to strip them out, here is a quick and dirty function for it

 public String removeMSRubbish(String body) {
        body = body.replaceAll("[u2018|u2019|u201A]", "'");
        // smart double quotes
        body = body.replaceAll("[u201C|u201D|u201E]", """);
        // ellipsis
        body = body.replaceAll("u2026", "...");
        // dashes
        body = body.replaceAll("[u2013|u2014]", "-");
        // circumflex
        body = body.replaceAll("u02C6", "^");
        // open angle bracket
        body = body.replaceAll("u2039", "<");
        // close angle bracket
        body = body.replaceAll("u203A", ">");
        // spaces
        body = body.replaceAll("[u02DC|u00A0]", " ");
        return body; 
    }

Connect 2013 Roundup

Ah, another large conference in Florida draws to a close and I can feel the first beginnings of the Lotusphere flu seeping into my bones ready for flight back.

So, what was this one like? In two words, “bloody busy”. The first couple of days were a complete whirl where I was never quite sure what was happening from one hour to the next, after that I at least had a plan but it was still mayhem.

This year I started the week with a to-do list, a list of clients to find and chat to, more potential clients to find and see if they were interested, 2 sessions to present (1 Best practises and 1 Show and Tell), 2 sponsored events, and all the dozens of other community events that needed to be visited. All in all, it left me completely buggered.

Compared to my roommates and fellow LDCers Ben Poole and Julian Woodward I rarely lasted past 24:00 each night
(yes there were 3 of us in a room as we were conferencing on the cheap, saving the money for client drinks).
Julian had about 18 hours sleep for the 7 days total, tending to come to the room for about 1 hour in the early mornings lay on his bed like a Borg, then rise recharged and machine his way through the next day.

My sessions went both well and terrible, the content and the delivery both went OK. But I had terrible time slots. The limitless languages one was just before the main Wednesday party and against Paul Mooney’s & Bill Buchan’s worst practices which pretty much killed it dead, here is the slidedeck:

The other one was on the Thursday morning at 8.00am ‘Show and Tell’ dead slot, with Julian Robichaux and while we had far more people than we expected, Julian’s content deserved a full house

Ah well, someone has to fill those slots so I can hardly complain,

I also had a few questions and bugs to raise about IBM connections and therefore ventured to the labs to get them resolved. The connections guy I met was very helpful, but the real credit goes to Maureen Leland who was passing and said hello, then asked if I could provide any useful feature requests for connections features (I asked for a navigable ATOM API Map). It made me feel like a million dollars that a senior IBM dev would not only remember me but ask for my opinion on ANY matter. It’s the existence of IBMers like Maureen and Susan Bulloch that ensures that there is still a vibrant groupware/collaboration/social community helping IBM to make a $1,000,000,000 a year from a product that might have well otherwise disappeared into the annals of time.

Going mob-handed with the rest of LDC was brill, what with Matt and I both presenting, Julian being the president of Penumbra and Ben being the lead dev of the well-received socialbizug.org. It was a good showing. However the best part was the endless laughing, some mornings my ribs hurt from it 🙂

This year’s LDC giveaway went perfectly, but Ben is going to do a blog on it so I can skip that bit.

On the final night Julian Woodward treated me to the most perfect drink in the world, my life is complete:

 

Loads of of other stuff happened including the awesome geek challenge, but what passes for my mind is a still a jetlagged flu-ridden whirlwind so I’m sure there will be more bits to remember and update the site with, but all in all I’m going to agree with IBM that this was not the end but the beginning of the conference.