It's fast and easy, and you can get in contact with others straight away...

Enter a Display Name
Enter your full name or a display name. Businesses, couples and families are also accepted. e.g.
Model Trains Inc.,
Sam and Janis Allen
Enter a username
Your username must be at least 6 characters in length, contain both letters and numbers and end in a number, but can not contain spaces or other characters.
Suggested usernames:
Password strength:
Your password can be up 250 characters in length and can contain letters, numbers, punctuation marks and spaces.
Hint: a secure password is long but easy to remember and not complex, e.g.
Jules Verne is an excellent author
Retype password
Enter the same password again to check for errors.

By signing up you agree to our Terms of Use & Privacy Policy

 
Drag and zoom to move the image.
Note: Only JPG files are currently accepted.
samjanis1@mirrorisland.com
Name
Email
Website
Address
Address 2
Town/Suburb
State
Post Code
Country
Interests
About
Hi there! We're the co-founders of Mirror Island. We also run courses through asgardiansky.com.au
View As
Sort by
Cancel esc
Click for advanced permissions.
View
Edit
Reply
Approve
Moderate
Tags
Visibility
·
Replies
General
Content Suitability
Show Replies As
Sort Replies By ·
  Paste Link Undo Redo ctrl enter

    Give Raspberry Pi a fixed hardware address when it keeps changing


    When plugging a Raspberry Pi Zero W, or any RPi, you have probably found out it assigns a new network ID every time it connects to your computer or laptop by USB. Then, it is fed the 10.0.0.1/24 static IP to get the network connection work.

    One suggestion was to configure g_ether through /etc/modprobe.d/g_ether.conf - but that didn't work for us.

    Alternatively, it turns out you can configure g_ether through /boot/cmdline.txt using the following steps:

    1. In a terminal, log in to your RPi with ssh pi@10.0.0.2

    2. Get your network IDs using dmesg | grep MAC

    The values after HOST MAC and MAC will be used. (Your output might differ from this):

    pi@pwnagotchi:~ $ dmesg | grep MAC
    [    1.691085] systemd[1]: systemd 241 running in system mode. (+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN2 +IDN -PCRE2 default-hierarchy=hybrid)
    [    5.535418] usb0: HOST MAC 0e:23:91:34:09:10
    [    5.540356] usb0: MAC 42:08:8a:8d:24:78
    

    3. Edit cmdline.txt with sudo nano /boot/cmdline.txt to set up g_ether:

    Where it says

    modules-load=dwc2,g_ether
    

    add the HOST MAC value for host_addr, and the MAC value for dev_addr, so it now look like:

    modules-load=dwc2,g_ether g_ether.host_addr=0e:23:91:34:09:10 g_ether.dev_addr=42:08:8a:8d:24:78
    

    4. Save with Ctrl+O and exit with Ctrl+X.

    5. Reboot with sudo reboot and your device should now reconnect using the same MAC addresses as before.

      When AWS Looks For Credentials In The Root Folder


      We encountered what appeared to be a strange bug when getting the credentials needed to connect to an S3 service.

      The official documentation on Amazon mentions a plain text file located in ~/.aws/credentials will be read to obtain the access/secret key pair for identification to an S3 server.

      However, when we tried this setup, we found this in Apache's error_log instead:

      [03-Jan-2022 20:49:35 Australia/Sydney] PHP Fatal error:  Uncaught AwsExceptionCredentialsException: Cannot read credentials from /.aws/credentials in /home/mirrorisland/public_html/aws/Aws/Credentials/CredentialProvider.php:875
      Stack trace:
      ...
      
      (The stack trace was a list of functions from the AWS sdk for PHP)

      At first glance, it seems that AWS is looking for .aws/ from the root folder. To try this on our home server we copied .aws/ to the root folder on a development laptop--and--it worked!

      But it shouldn't have. No sensible framework would try to read from the root of any drive in a shared environment, especially Amazon's.

      After a bit of digging around it turns out the AWS sdk does try to look in the user's home folder, by using the HOME environment variable and prepends the HOME variable to /.aws/credentials. Unfortunately for us, it wasn't set on our server. This isn't the fault of Amazon's sdk, but our server not having an environment variable configured.

      So, with a null HOME env, what was supposed to be /home/mirrorisland/.aws/credentials ended up being /.aws/credentials.

      At this point it was obvious what we had to do. Since /home/mirrorisland was missing from the above path, adding the following line to .htaccess made everything work.

      SetEnv HOME /home/mirrorisland
      

      Problem solved.
        Hi Anaxareian,

        I'm Sam Allen and one of the primary authors of Mirror Island, the other being my wife, Janis.

        Thank you for raising those points above. We use email since it's pretty much the most open standard we can use to allow cross-domain communication without starting a completely new one.

        And yes, we don't like how email addresses are exposed publicly either, but we're adjusting for scenarios when it might be necessary.

        We were working on more privacy issues, although the COVID-19 epidemic has slowed down development for us, with other things taking up our time instead. According to gitlab [https://gitlab.com/samjanis] the last time we did anything was midway through March. We do have a few changes to upload, but they're not fully tested and might break the system (like the popup editor which is currently broken :[ )

        Things are quieting down here at least so we can get back to development next week.

        Thank you for dropping by here!

        --Sam and Janis

          Behind the Scenes Updates


          The latest update has brought a more visually consistent user interface, as well as the ability to store emoji and other extended characters by updating the database to use utf8mb4 for storage. Full source is available on GitLab.

          Existing databases can be updated with the following SQL commands:
          (just remember to select the database first, then replace {DATABASE_NAME} with the actual name of the database)

          alter database {DATABASE_NAME} character set = utf8mb4 collate = utf8mb4_unicode_ci;
          alter table accounts convert to character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change fullname fullname text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change username username varchar(191) character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change address address text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change address2 address2 text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change town town text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change state state text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change postcode postcode text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change country country text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change email email text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change website website text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change interests interests longtext character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change about about longtext character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change license license text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change termsofuse termsofuse longtext character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table accounts change messagetypingstatustype messagetypingstatustype text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table approvals convert to character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table approvals change tablename tablename text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table bookmarks convert to character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table contacts convert to character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table files convert to character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table files change fileindex fileindex text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table files change filename filename text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table files change filemime filemime text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table flags convert to character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table flags change tablename tablename text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table flags change flagreason flagreason text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table members convert to character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table membertree convert to character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table passwords convert to character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table passwords change password password text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table pins convert to character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table posts convert to character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table posts change emaildisplayname emaildisplayname text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table posts change emailusername emailusername text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table posts change emailserver emailserver text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table posts change emailinbox emailinbox text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table posts change description description longtext character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table posts change title title text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table posts change searchcache searchcache text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table recipients convert to character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table recipients change tablename tablename text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table recipients change emaildisplayname emaildisplayname text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table recipients change emailusername emailusername text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table recipients change emailserver emailserver text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table searchindex convert to character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table searchindex change tablename tablename text character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table searchkeywords convert to character set utf8mb4 collate utf8mb4_unicode_ci;
          alter table searchkeywords change keyword keyword text character set utf8mb4 collate utf8mb4_unicode_ci;
          

            Bug Fixes over the Past Weeks


            The last month has been spent looking for small usability bugs. While there has been nothing major added feature wise, small issues that could interrupt a smooth user experience were patched up. Among a few of the bug fixes, were:

            - emails with attachments can now be received
            - multiple emails from the same sender are now grouped in a single entry on the sidepanel
            - some images (particularly profile photos) weren't showing up when they should
            - posts that can be replied to by email now have a link showing people how
            - links to pages with opengraph metadata now crawl better, such as with youtube,

            and a few other cosmetic changes also made their way in.

            Thanks for reading!

            --Sam and Janis

              Life Is Easier Sorted


              Another quick update this time. Pages with replies (or subpages) can sort them by date or title, as well as be able to reverse the order.

              So when opening a page you can now see this is the sidepanel:

              As usual, the updated source code is available in GitLab (https://gitlab.com/samjanis/mirrorisland)

              Have fun!

                New Feature: Title Trees


                Another new feature added this week is post tree in the side panel. When a tree of posts with titles (a page's title is set when the first line begins with a # symbol), that tree of posts is drawn in the side panel on the left.

                Try it out!

                  Got Categories? Add A Banner To A Post!


                  When editing posts, you will notice a new button beside undo/redo which allows you to set the banner image for a posts. The banner image is displayed on the top of the post, and if the post is a reply or shown in a stream, it links to the post's page as well.

                  This feature can be useful for generating groups of pages or categories for online stores. For us, this is another small step towards building the "tear-down-and-rebuild" feature we have mentioned at the beginning of this project, where the pages can change their behaviour depending on the content being shown.

                  Have fun!

                    A Minor Update That Took A Major Rewrite


                    Replies to posts can now be pinned! If you have ownership, edit, or moderate permissions to a post, there is an option in the reply's popup menu where you can pin the reply so it can appear at the top of the reply chain. Additionally, this involved a major rewrite to the back-end focusing on a more simplified approach to handling permissions.

                    A few minor usability bugs were also addressed, including multiple copies of posts appearing inside eachother after editing, and "call to action" (CTA) links appearing below each posts to show tips on how users can reply.

                    As always, this update was posted on GitLab where you can download a copy of this website's source code as well, at:
                    https://gitlab.com/samjanis/mirrorisland/

                      Saving from Thoth


                      With this release of the text editor and note taker, Thoth, it is now possible to save notes straight from the editor to Mirror Island! It is a limited test run being able to only to use Thoth and Mirror Island, although the end goal is to be able to use any client to connect to Mirror Island, and for Thoth to edit files on other networks.

                      For now this is a huge step on getting network connectivity between two different projects.

                        Encrypting and Decrypting Data with OpenSSL


                        Storing sensitive data is easier today thanks to a few lines of OpenSSL. In finding a secure way to store all sorts of passwords on a device for Thoth, we decided encrypting them with a master password was the way to go. Of course these methods aren't used just to store passwords, but for any situation where plain text or binary data would need to be securely stored.

                        This post will show you how to use 128 bit AES in CBC mode, using the encryption function EVP_aes_128_cbc() which in short means:
                        - EVP - Use OpenSSL's high level envelope functions.
                        - aes - Shuffle bit around using the Advanced Encryption Standard in 128 bit chunks.
                        - cbc - Join the 128 bit chunks together with Cipher Block Chaining

                        There are other encryption functions, listed in OpenSSL's EVP cipher modes or in more detail in OpenSSL's EVP manpage, but these other methods are an exercise left up to the reader. Also, OpenSSL can be either run as a command-line tool, or as a library for another app. We will be using the library functions from here on.


                        Encryption Basics


                        Setting up the encrypting environment is simple. We start by creating a cipher context which handles our encryption commands. The next step is to provide a key and an iv or initialization vector. The key is a byte sequence specific to a user-provided secret (usually a password), while the iv is a unique random array of bytes generated for the encryption process. Both the key and iv need to be at least the same size of the cipher block, which is 128 bits or 16 bytes of data in this example. Also, the same key and iv will need to be saved for later to decrypt the encrypted data.

                        EVP_CIPHER_CTX *ctx;
                        ctx = EVP_CIPHER_CTX_new();
                        EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
                        

                        Once the key and iv have been given, we can now encrypt a stream of data. In very long streams this can be broken up in chunks, but since we're only encrypting short strings we can do nearly all the encryption in one go. The additional variables for this function include:
                        - outbuf - an array of unsigned chars to store the encrypted data in.
                        - outlen - an unsigned integer to record how many bytes have been encrypted, incase it is less than the size of outbuf.
                        - plaintext - the original text we want to encrypt. Its length is also provided as the last variable.

                        EVP_EncryptUpdate(ctx, outbuf, &outlen, (guchar *)plaintext, strlen(plaintext));
                        

                        Remember how AES encrypts in 128 bit chunks? Sometimes the encrypted data doesn't exactly line up. The last function, "EncryptFinal" function finishes the encrypted stream cleanly and adds padding where there aren't enough bits to keep encrypting. The value in tmplen lets us know how much more data is added to the end.

                        EVP_EncryptFinal_ex(ctx, outbuf + outlen, &tmplen);
                        outlen += tmplen;
                        EVP_CIPHER_CTX_free(ctx);
                        

                        And that's basically it! In outbuf we now have an encrypted byte sequence that is outlen bytes long.


                        Wrapping Up


                        The decryption process is basically the same, except we call the "Decrypt" function counterparts, and plaintext supplied to "EncryptUpdate" is the encrypted binary data instead.

                        Of course this example only touches lightly on the full capabilities OpenSSL and other encryption libraries have in their toolkit, but this can at least provide a valuable stepping stone to anybody interested in how to encrypt and decrypt data. For more information the links at the top of this post can describe in detail the functions used throughout this demo.

                        The complete code in included at the end of this post. To see it in action, copy the following lines of code to a file named crypdemo.c and compile it from the terminal with:
                        gcc crypdemo.c -o crypdemo -lssl -lcrypto `pkg-config --cflags --libs glib-2.0 libcrypto`
                        

                        You will also need to have the glib and openssl libraries and development headers installed for this to compile and run.


                        crypdemo.c


                        /**
                         * Crypt Demo: encrypt and decrypt a text string with 128bit AES.
                         *
                         * Usage: ./crypdemo [string to encrypt] [password] [initialization vector]
                         *
                         * "string to encrypt" is either a length of plain text to encrypt,
                         *    usually enclosed in single quotes (') if using spaces,
                         *    or it is a base64 encoded string of binary data encrypted with this demo.
                         * "password" is a plain text password provided by the user (you).
                         * "initialization vector" (shortened as "iv") is an optional base64 encoded
                         *    string of binary data to use as the iv.
                         *    If you provide an iv the demo will decrypt only. If you do not provide an iv
                         *    the demo will generate one to encrypt the given string and immediately
                         *    decrypt it after.
                         *
                         * Compile with:
                         * gcc crypdemo.c -o crypdemo -lssl -lcrypto `pkg-config --cflags --libs glib-2.0 libcrypto`
                         */
                        
                        #include <glib.h>
                        #include <stdio.h>
                        #include <string.h>
                        #include <openssl/evp.h>
                        #include <openssl/rand.h>
                        
                        /**
                         * Encrypt a string of plain text and encode
                         * the encrypted byte data as base64.
                         */
                        gchar *do_encrypt (char *plaintext, char *password, char *b64iv)
                        {
                            /* Allow enough space in output buffer for additional block */
                            guchar outbuf[1024];
                            int outlen, tmplen;
                        
                            gsize iv_size;
                            guchar *iv = g_base64_decode(b64iv, &iv_size);
                        
                            /* Generate key (password) */
                            /* Can't use password string here! Need to generate a byte sequence. */
                            guchar key[256];
                            PKCS5_PBKDF2_HMAC_SHA1((const gchar *)password, g_utf8_strlen(password, -1),
                                    NULL, 0, 1000, 128, key);
                        
                            EVP_CIPHER_CTX *ctx;
                            ctx = EVP_CIPHER_CTX_new();
                            EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
                        
                            if (EVP_EncryptUpdate(ctx, outbuf, &outlen, (guchar *)plaintext,
                                    strlen(plaintext)) == 0) {
                                printf("EVP_EncryptUpdate returned 0n");
                                return 0; /* Error */
                            }
                        
                            if(!EVP_EncryptFinal_ex(ctx, outbuf + outlen, &tmplen)) {
                                printf("EVP_EncryptFinal_ex returned 0n");
                                return 0; /* Error */
                            }
                        
                            outlen += tmplen;
                            EVP_CIPHER_CTX_free(ctx);
                        
                            g_free(iv);
                        
                            /* Need to encode as base64 because encrypted data is binary. */
                            gchar *str = g_base64_encode(outbuf, outlen);
                            return str;
                        }
                        
                        /**
                         * Decrypt a base64 encoded encrypted byte sequence.
                         */
                        gchar *do_decrypt(char *base64data, char *password, char *base64iv)
                        {
                            printf("%d Decrypting with base64iv = %sn", __LINE__, base64iv);
                        
                            /* Decode IV (initalization data) */
                            gsize iv_size;
                            guchar *iv = g_base64_decode(base64iv, &iv_size);
                        
                            /* Decode base64 data to binary */
                            gsize decoded_size;
                            guchar *encdata = g_base64_decode(base64data, &decoded_size);
                        
                            /* Generate key (password) */
                            /* Can't use password string here! Need to generate a byte sequence. */
                            guchar key[256];
                            PKCS5_PBKDF2_HMAC_SHA1((const gchar *)password, g_utf8_strlen(password, -1),
                                    NULL, 0, 1000, 128, key);
                        
                            /* Decrypt now */
                            guchar outbuf[1024];
                            int outlen, tmplen;
                        
                            EVP_CIPHER_CTX *ctx;
                            ctx = EVP_CIPHER_CTX_new();
                            EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
                        
                            if (EVP_DecryptUpdate(ctx, outbuf, &outlen, encdata, decoded_size) == 0) {
                                printf("EVP_DecryptUpdate returned 0n");
                                return 0; /* Error */
                            }
                        
                            if(!EVP_DecryptFinal_ex(ctx, outbuf + outlen, &tmplen)) {
                                printf("EVP_DecryptFinal_ex returned 0n");
                                return 0; /* Error */
                            }
                        
                            outlen += tmplen;
                            EVP_CIPHER_CTX_free(ctx);
                        
                            g_free(encdata);
                            g_free(iv);
                        
                            gchar *str = g_strndup(outbuf, outlen);
                            return str;
                        }
                        
                        /**
                         *
                         */
                        int main (int argc, char *argv[])
                        {
                            if (argc < 2) {
                                   printf("Usage: %s [string] [password] [iv (optional)]n", argv[0]);
                                   printf("(Supplying an iv (initialization vector) changes the demo to decrypt only mode.n");
                                   exit(1);
                            }
                        
                            gchar *base64iv;
                            gchar *encstr = NULL;
                            if (argv[3]) {
                                /* If we're decrypting only, get the encrypted string
                                 * and iv from command-line */
                                base64iv = g_strdup(argv[3]);
                                encstr = g_strdup(argv[1]);
                            } else {
                                /* Otherwise we will supply the encrypted string
                                 * and generate a new iv here. */
                                guchar *iv = g_malloc0(16);
                                int rc = RAND_bytes(iv, 16);
                                if (rc != 1) {
                                    printf("Can't get RAND_bytes.n");
                                    exit(1);
                                }
                                base64iv = g_base64_encode(iv, 16);
                                printf("* * * Generated new iv (base64 encoded) = %s * * *n", base64iv);
                                g_free(iv);
                            }
                        
                            gchar *encpw = g_strdup(argv[2]);
                            /* Only encrypt if no IV given. */
                            if (!argv[3]) {
                                encstr = do_encrypt(argv[1], encpw, base64iv);
                                printf("Try decrypting with: %s '%s' %s '%s'n", argv[0], encstr, argv[2], base64iv);
                            } 
                            g_free(encpw);
                        
                            gchar *decpw = g_strdup(argv[2]);
                            gchar *e = g_strdup(encstr);
                            gchar *b64iv = g_strdup(base64iv);
                        
                            /* Always decrypt */
                            gchar *decstr = do_decrypt(e, decpw, b64iv);
                            printf("Decrypted text = %sn", decstr);
                            g_free(decstr);
                            g_free(decpw);
                        
                            g_free(e);
                            g_free(b64iv);        
                        
                            exit(0);
                        }
                        
                          Another small update this time.

                          Today we added some neat tricks in getting Mirror Island to work with Thoth. The plain text of any post can now be viewed with the "/src" page option. As well as unformatted text, you can also access the HTML version if it contains markdown formatting with the "/htm" page option.

                          Try it out now with this post using these links:
                          https://www.mirrorisland.com/1356/src
                          https://www.mirrorisland.com/1356/htm



                          Additional markdown formatting was also included in the update. Headings underlined with dashes (-) and equal signs(=) are handled, as well as horizontal lines with 3 or more dashes and asterisks (*) on a single line. Strong and emphasised text can be used with underscores(_) as well now.
                            Lately we have been compiling Thoth for Windows. Thanks to msys2 (http://msys2.org/) compiling GTK+ apps on Windows have gotten a lot more easier.

                            At this stage we have been able to compile a version that can run in the build environment, although the goal is to have a complete standalone application. Once we have a working standalone application we will publish it on here, including the source code and build instructions which hopefully, would also apply to compiling any GTK+ application on Windows.

                              Reading Open Graph Meta Properties


                              Last night we pushed a small update to improve link sharing on Mirror Island.

                              When people share links, finding the right information can be made easy if Open Graph tags are available. Some guesswork can be made by using HTML's <title> tag for the link text, and allowing people to choose one of many images found on the page (if we're lucky).

                              Previously, links added to a blank document used to look like this:
                              Thankfully the Open Graph protocol (http://ogp.me/) provides a number of useful <meta> tags allowing easy and a more accurate way of sharing links between websites and networks. In the <head> section of an HTML page we might find these meta tags:
                              <meta property="og:title" content="World largest steam locomotive is back! Big Boy 4014 hits the main line">
                              <meta property="og:url" content="https://www.youtube.com/watch?v=RR7Q27cIEvo">
                              <meta property="og:image" content="https://i.ytimg.com/vi/RR7Q27cIEvo/maxresdefault.jpg">
                              <meta property="og:description" content="They said it couldn't be done. It was too heavy, too long, it burned too much fuel, and would cost too much to restore. But they were wrong. Union Pacific, B...">

                              There are a number of other meta tags we could use. In our case we need to only use og:title, og:image, og:url and og:description.

                              Using these Open Graph tags, links generated in new posts now look like this:
                              Much better.

                              At this point each Open Graph meta tag is self-explanatory. So now we know what tags to read, the next part is how to read them. Usually, some usable information can be extracted by using regexp to read tags. However, regexp isn't the best solution when dealing with markup tags.

                              The method we use is to read the DOM (Document Object Model) with PHP's DOMDocument class. It can also handle most errors that would occur if using regexp, such as tags inside comments or javascript code. First, we load the web page as an HTML string. Next, the string is loaded in to a blank domDocument. From here we can navigate and read data from the DOM tree.

                              The following lines of code can read the Open Graph url, title, image, and description. This is a cut-down version of what we used, in order to focus on DOM features in this article. This code snippet is free to use for whatever purpose you see fit.


                              /* Sanitise the input */
                              $url = substr(trim(stripslashes($_POST['url'])), 0, 1024);
                              
                              /* Load the HTML as a DOM document
                               */
                              $html = file_get_contents($url);
                              $dom = new domDocument;
                              $dom->preserveWhiteSpace = false;
                              $dom->loadHTML($html);
                              
                              /* Fetch data from any Open Graph tags
                               */
                              $og_url = '';
                              $og_title = '';
                              $og_image = '';
                              $og_description = '';
                              $metatags = $dom->getElementsByTagName('meta');
                              foreach ($metatags as $meta) {
                                  $metaproperty = $meta->getAttribute('property');
                                  $metacontent = $meta->getAttribute('content');
                              
                                  if ($metaproperty == "og:url") {
                                      $og_url = $metacontent;
                                  } else if ($metaproperty == "og:title") {
                                      $og_title = $metacontent;
                                  } else if ($metaproperty == "og:image") {
                                      $og_image = $metacontent;
                                  } else if ($metaproperty == "og:description") {
                                      $og_description = $metacontent;
                                  }
                              }
                              
                              /* Use the Open Graph title if available, otherwise
                                 fall back to the HTML document's title tag */
                              $title = '';
                              if ($og_title) {
                                  $title = $og_title;
                              } else {
                                  $headtitle = $dom->getElementsByTagName('title');
                                  foreach ($headtitle as $t) {
                                      if ($t->nodeValue != '')
                                          $title = $t->nodeValue;
                                  }
                              }
                              
                              /* Overwrite the provided URL with the Open Graph
                                 provide one if available. */
                              if ($og_url) {
                                  $url = $og_url;
                              }
                              
                              /* From here on, $og_image and $og_description are used
                                 as-is */
                              
                                A small change this time. Markdown for inline code can be wrapped in <code> tags by surrounding them with backticks ( ` ). As usual, a few minor bug fixes with small screen support in CSS also were included.

                                  How We Got Line Break To Work Consistently with contentEditable


                                  (Well, In Firefox At Least)

                                  A challenge of getting the markdown parser to work more reliably was getting predictable HTML from the contentEditable node. Depending on what the user entered, contents of a contentEditable (at least in Firefox for these tests) generated <div> elements around `<br>`s, and a collection of seemingly random combinations of HTML tags.

                                  The other main quirk we encountered is not being able to append new lines unless <br> was the last child node of a contentEditable div.

                                  For this we wrote an event handler to intervene when the Enter key is pressed. It forces the contentEditable to insert <br> tags where needed, instead of Firefox's default behaviour which tries to generate HTML the best it can. This way we can make a contentEditable behave more like a <textarea> and still have rich-text editing features for images, videos, and links.

                                  We have attached the code used which is minimalist and has only been tested in Firefox itself. Other browsers will be tested in later coding sessions. It works as expected although we are constantly searching for other bugs that appear.

                                  Throughout these code snippets, index_editdescription is the node of the <div> being edited, usually accessed through document.getElementById(...).

                                  Firstly, we need to capture the key presses to intervene when Enter is pressed:

                                  index_editdescription.addEventListener('keydown', index_js_editorKeyDownEvent, false);
                                  

                                  Next, the following function inserts the required <br> tags; one at the cursor, and another if the last child element isn't a line break. Additionally, the function also prevents new lines being inserted in a blank div although this is oversimplified and doesn't check for white-space characters.

                                  function index_js_editorKeyDownEvent(ev)
                                  {
                                      if (ev.keyCode == 13) {
                                          ev.preventDefault();
                                          if (index_editdescription.innerHTML == '') {
                                              return false;
                                          }
                                          if (window.getSelection) {
                                              var sel = window.getSelection();
                                              var range = sel.getRangeAt(0);
                                              var brNode = document.createElement('br');
                                              range.deleteContents();
                                              range.insertNode(brNode);
                                              range = range.cloneRange();
                                              range.selectNodeContents(brNode.nextSibling);
                                              range.collapse(true);
                                              sel.removeAllRanges();
                                              sel.addRange(range);
                                          } // TODO: Impliment document.getSelection and document.selection.createRange for cross-browser compatibility.
                                  
                                          var lastChildTag = index_editdescription.lastChild.nodeName.toLowerCase();
                                          if (lastChildTag != 'br') {
                                              index_editdescription.appendChild(document.createElement('br'));
                                          }
                                  
                                          index_editdescription.focus();
                                      }
                                  }
                                  

                                    Bug Fix Release #1


                                    On getting closer to the 0.1.1 release, we have included a few bug fixes and usability improvements. Changes in this release include:

                                    - FEATURE Posts can now be search by their page ID.
                                    - USABILITY Moved "share menu" item. The popup for sharing links covered other menu items below it.
                                    - USABILITY The search popup resizes correctly on small screens.
                                    - BUGFIX Fixed bug not not showing search results if replying to own post.
                                    - BUGFIX Improved editor with irregular line breaks.

                                    We also updated the current terms describing minimum age requirements of primary account holders, and the data usage of advertisements (when implemented).

                                      An Update On Recent Updates


                                      For the past few weeks (and at this point in time) we have mainly been working on bug fixes rather than adding new features to Mirror Island. Our attention has been put towards building the network-connected text editor and note taker, Thoth.


                                      One of the primary aims for Mirror Island was for it to connect to many different apps, depending on what services would be needed.

                                      Thoth is the first component that will be able to edit and send notes to others either through this website or any email service. Attachments to notes will also be featured, so you can always use more than words to get your ideas across to others.

                                      This project is currently in its alpha status, although it is capable of taking notes and editing text files stored on a computer.

                                      Precompiled binaries including source, as well as news of updates at is available at https://mirrorisland.com/thoth.

                                      Alternatively, you can get the source code via git and read the build instructions at
                                      https://gitlab.com/samjanis/thoth

                                      --Sam and Janis
                                      Page 1

                                      Loading
                                      Loading...

                                      Loading messages...
                                      Visibility
                                      ( )
                                      Content Suitability
                                      Replies
                                      Click for advanced permissions.
                                      View
                                      Edit
                                      Reply
                                      Approve
                                      Moderate
                                      Tags

                                      Attach an image or file

                                      Cancel esc
                                      Cancel esc