Blog

Some very rough notes I took while learning to create an Atlassian Confluence plugin. Perhaps, I'll clean these notes up a bit when I create my next plugin.



Contents

Install the Atlassian SDK

See: Set up the Atlassian Plugin SDK and Build a Project

Create the Plugin Skeleton

Navigate to the directory on your system where you'd like to create your plugin.  The command we are about to run will create a folder with the plugin directories inside. 

Create directory /Users/cburleson/repos/test-conf-plugin

cd into the directory

atlas-create-confluence-plugin

The first time, Maven will download a bunch of packages.

Now you need to define some things:

Define value for groupId: : com.codyburleson.test
Define value for artifactId: : myPlugin
Define value for version: 1.0.0-SNAPSHOT: : 1.0.0-SNAPSHOT
Define value for package: com.codyburleson.test: : com.codyburleson.test.MyPlugin

You will then be prompted to confirm:

Confirm properties configuration:
groupId: com.codyburleson.test
artifactId: myPlugin
version: 1.0.0-SNAPSHOT
package: com.codyburleson.test.MyPlugin
 Y: : Y

Maven will download some more stuff.

Open eclipse and switch to the /repos workspace

The basic skeleton for your Atlassian JIRA plugin is created in a new myPlugin directory: 

Feel free to take a moment to explore the different files created by the Atlassian SDK before you continue. 

Change to the myPlugin directory and enter the following command: 

atlas-run

DO NOT FORGET TO USE CTRL-D TO SHUT THE atlas-run execution down gracefully!

This will download a bunch of stuff (Maven) and then run the product with your plugin installed.

Navigate to:

http://localhost:1990/confluence

Login with admin | admin

Go find your plugin in the manage page:

http://localhost:1990/confluence/plugins/servlet/upm/manage/all

Import Into Eclipse IDE

Import > Eisting Maven Projects and find the project with the pom.xml file.

Preferences > Maven

Add a new Installation and point to: /Applications/Atlassian/atlassian-plugin-sdk-6.2.14/apache-maven-3.2.1

Set Maven user settings so that the Global settings point to: /Applications/Atlassian/atlassian-plugin-sdk-6.2.14/apache-maven-3.2.1/conf/settings.xml


Panel Macro


cd /Users/cburleson/repos/confluence-panel-macro/panelMacro
atlas-run


Open the atlassian-plugin.xml file in your favourite editor.


Locate the end of the <web-resource>...</web-resource> section in the file and enter the following:



<xhtml-macro name="panel" class="com.codyburleson.confluence.macro.Panel" key='panel-macro'>
	    <description key="panel.macro.desc"/>
	    <category name="formatting"/>
	    <parameters/>
</xhtml-macro>

Open the file /src/main/resources/myConfluenceMacro.properties and add the following line at the bottom of the file:

panel.macro.desc="Cody Burleson's Panel Macro"

Create the 

com.codyburleson.confluence.macro package.

Create the Java file in that package...

Panel.java
package com.codyburleson.confluence.macro;

import com.atlassian.confluence.content.render.xhtml.ConversionContext;
import com.atlassian.confluence.macro.Macro;
import com.atlassian.confluence.macro.MacroExecutionException;

import java.util.Map;

public class Panel implements Macro {

    public String execute(Map<String, String> map, String s, ConversionContext conversionContext) throws MacroExecutionException {
        return "<h1>Hello World</h1>";
    }

    public BodyType getBodyType() { return BodyType.NONE; }

    public OutputType getOutputType() { return OutputType.BLOCK; }
}

This is the minimum skeleton your Macro will require to implement the confluence Macro class and display a Macro object in Confluence. 

In your terminal window, change directory back to the top directory for your plugin (eg cd /repos/confluence-panel-macro/panelMacro)

Run the command:

atlas-mvn package

You should see a confirmation message 

[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.656 s
[INFO] Finished at: 2016-10-10T18:33:09+10:00
[INFO] Final Memory: 37M/433M
[INFO] ------------------------------------------------------------------------

Monitor the window where confluence was run originally and confirm that QuickReload finished loading. You should see a confirmation message:

Here I got a stack trace error.

[INFO] [talledLocalContainer] java.lang.RuntimeException: Cannot getResourceAsStream(panelMacro_en_GB.properties): This operation must occur before the plugin 'com.codyburleson.confluence.panelMacro' is uninstalled
[INFO] [talledLocalContainer] 	at com.atlassian.event.internal.SingleParameterMethodListenerInvoker.invoke(SingleParameterMethodListenerInvoker.java:54)

I created the file: 

panelMacro_en_GB.properties

Important to REFRESH THE WORKSPACE after creating new files.

Then fun atlas-mvn-package again - and all was cool...


Now you can try adding the Macro to a test page in Confluence (you'll need to make a new Confluence Space and Page before you can test it out so go ahead and do that first).


Now, you will allow the user to specify their name using a parameter to learn about how parameters can be set, and used.

Open the atlassian-plugin.xml file in your favourite editor.

Locate the <parameters/> element within the <xhtml-macro> element you created in the first part of this tutorial.  


Replace the <parameters/> element with the following:


<parameters>
    <parameter name="Title" type="string" />
</parameters>

This specifies that the parameter is called 'Name' and is of type 'string'.  You can find the full list of types under the Parameters heading in the macro module documentation

Now modify the execute method in the Java class so it looks like this:

public String execute(Map<String, String> map, String s, ConversionContext conversionContext) throws MacroExecutionException {
    if (map.get("Title") != null) {

        StringBuilder sb = new StringBuilder();
        sb.append("<div class=\"panelHeader\">");
        sb.append(map.get("Title"));
        sb.append("</div>");
        return sb.toString();
        
    } else {
        return "<h1>Hello World!<h1>";
    }
}

REFRESH THE PROJECT and then execute atlas-mvn package.

Press CTRL-D to shut down the atlas-run session gracefully.


Reference Resources

Create a Confluence 'Hello World' Macro

Linux and Unix Commands

A cheatsheet for common Linux / Unix commands.


Contents

Change File Permissions

CommandUsage
chmod +x filename.extGive execute access to a file.
sudo chmod -R 777 workspace-R means RECURSIVE
sudo chown -R basejump workspaceTake ownership of a file.
basejump (user) and workspace (directory) 

Compress or Extract Files


CommandUsage
tar -zxvf file.tar.gzTo extract one or more members from an archive:


Create an Alias for a Common Command

CommandUsage
alias p=<command>Create shortcut aliases to common commands.
For example:
alias p="open /x/yx/z" to open a particular directory in Finder

Execute Command as Root User

CommandUsage
sudo <command>Execute command as the root user.
sudo !!Execute the last command as root user.
(!!) represents the last command you just tried to run, but couldn't because of permission issues.

Execute Task in Background

CommandUsage
./startTest.sh &Start the execution in the background, which will allow you to kill your SSH terminal without killing the process itself.

Find Large Files

CommandUsage
cd /
find . -size +10000000c -print
Prints out the names of all files with size > 10mb.
cd /
sudo du -sm * 
Examine the size of the directories under /.
You can then navigate into any given subdirectory and execute dm -sm * again to see which subdirectories are the largest. 

Inspect Disk Space and Usage

CommandUsage
df -hInspect disk space and usage (in MB or GB)

Remove a File


CommandUsage
mvRename a file 
rmRemove a file 
rm -fForcefully remove a file
rm -iInteractively remove a file

If you are not certain about removing files that match a pattern you supply, it is always good to run rm interactively (rm –i) to prompt before every removal.

Rename or Remove a Directory


CommandUsage
mvRename a directory
rmdirRemove an empty directory
rm -rfForcefully remove a directory recursively


Switch User

CommandUsage
su - <username>Switch to the given user, loading their profile.
You may have to use sudo su - <username>. 
suWithout a username means to just switch to root user.
whoami"Who Am I?" Prints the current user. 


View a File

CommandUsage
catUsed for viewing files that are not very long; it does not provide any scroll-back.
tacUsed to look at a file backwards, starting with the last line.
lessUsed to view larger files because it is a paging program; it pauses at each screenful of text, provides scroll-back capabilities, and lets you search and navigate within the file. Note: Use / to search for a pattern in the forward direction and ? for a pattern in the backward direction. Press Q to quit.
tailUsed to print the last 10 lines of a file by default. You can change the number of lines by doing -n 15 or just -15 if you wanted to look at the last 15 lines instead of the default.
headThe opposite of tail; by default it prints the first 10 lines of a file.


Use Pipes

The UNIX/Linux philosophy is to have many simple and short programs (or commands) cooperate together to produce quite complex results, rather than have one complex program with many possible options and modes of operation. In order to accomplish this, extensive use of pipes is made; you can pipe the output of one command or program into another as its input.

In order to do this we use the vertical-bar, |, (pipe symbol) between commands as in:
$ command1 | command2 | command3


The above represents what we often call a pipeline and allows Linux to combine the actions of several commands into one. This is extraordinarily efficient because command2 and command3 do not have to wait for the previous pipeline commands to complete before they can begin hacking at the data in their input streams; on multiple CPU or core systems the available computing power is much better utilized and things get done quicker. In addition there is no need to save output in (temporary) files between the stages in the pipeline, which saves disk space and reduces reading and writing from disk, which is often the slowest bottleneck in getting something done.

An example SPARQL query filtering for resources within a given date range (between two given dates).

PREFIX c: <https://carbonldp.com/ns/v1/platform#>

SELECT ?document ?createdDate WHERE {
    ?document c:created ?createdDate
	FILTER (?createdDate < "2017-04-18T22:29:33.667Z"^^xsd:dateTime && ?createdDate > "2017-04-18T21:37:37.708Z"^^xsd:dateTime)
} LIMIT 100

To automatically serve static resources with Spring Boot (e.g. when using spring-boot-starter-web), you can simply place the static resources in one of several paths that Spring Boot automatically recognizes as a static file paths.


Following are paths that will are recognized as static file paths:

  • src/main/resources/META-INF/resources/index.html
  • src/main/resources/resources/index.html
  • src/main/resources/static/index.html
  • src/main/resources/public/index.html

You can then access the resources at:

http://host/index.html

So, for example, given the following resource at the following location...

  • src/main/resources/static/css/bootstrap.min.css

You could fetch the file with the following URL:

http://host/css/bootstrap.min.css



What are the most popular database management systems? DB-Engines Ranking provides a list that's updated monthly. You can look at the complete ranking or filter by types like Relational DBMS, Key-value stores, Document stores, Graph DBMS, RDF stores, Search engines, and more.

For a ranked list of popular databases, check out the DB-Engines Ranking page on the DB-Engines site, a knowledge base of relational and NoSQL database management systems.

Most Popular RDF Stores as of April 2017

Following, for example, is a list of popular RDF Stores as calculated in April 2017.

The site calculates popularity monthly using the following general parameters (see also: Method of calculating the scores of the DB-Engines Ranking):

  • Number of mentions on websites, measured as number of results in search engines queries on Google, Bing and Yandex (searching for <system name> together with the term database, e.g. "Oracle" and "database").
  • General interest in the system, as determined by the frequency of searches in Google Trends.
  • Frequency of technical discussions about the system, as determined by the number of related questions and the number of interested users on Stack Overflow and DBA Stack Exchange.
  • Number of job offers, in which the system is mentioned on leading job search engines Indeed and Simply Hired.
  • Number of profiles in professional networks, in which the system is mentioned in popular professional networks LinkedIn and Upwork.
  • Relevance in social networks, as per a count of the number of Twitter tweets, in which the system is mentioned.






Logging with Spring Boot is dead simple. Everything's pretty much setup and ready to go. In this post, I provide some quick and simple tips to get your Spring Boot logs rolling.

About Logging Dependencies

If you use the ‘Starters’, Logback will be used with appropriate routing included to ensure that dependent libraries that use Java Util Logging, Commons Logging, Log4J or SLF4J will all work correctly. Let's suppose, for example, that you're using the web starter in your Maven pom.xml file, as shown below.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

Generally you won’t need to change any logging dependencies and the Spring Boot defaults will work just fine. That is to say, you don't need to add any additional dependencies for logging to the pom. You can verify this by printing a tree representation of your project dependencies. On the command line, change to your project directory and executing the following command.

Print Maven Dependency Tree
mvn dependency:tree

Notice that the Spring Boot starter already includes dependencies for logging...

[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:1.5.2.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:1.5.2.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:1.5.2.RELEASE:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.1.11:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.1.11:compile
[INFO] |  |  |  +- org.slf4j:jcl-over-slf4j:jar:1.7.24:compile
[INFO] |  |  |  +- org.slf4j:jul-to-slf4j:jar:1.7.24:compile
[INFO] |  |  |  \- org.slf4j:log4j-over-slf4j:jar:1.7.24:compile

Configure Log Levels

The easiest way to configure logging levels is in the application.properties file. If you don't already have one, create an application.properties file in the root of the resources folder. Then, simply prefix Java packages and classes with logging.level as shown below. Notice that you can configure the root logger at a specific level first, then get more specific with other loggers.

application.properties
logging.level.root=INFO
logging.level.com.cburleson.rdfx=TRACE

Put Logging Code in Your Classes

Now, we can use SLF4J for logging. Here's how.

Add the following to the imports section of your java code:


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Add the following at the top of your class in the global section (just under the line that declares your class public class Whatever extends Whatever). Change the name of the class (MyClassName) in the getLogger method call, of course. Name it the same as the class you're dropping this code into.


static final Logger LOG = LoggerFactory.getLogger(MyClassName.class);

To test quickly, you can throw some logging statements in your code somewhere where you know they'll be fired right away when you run your app. For example:


LOG.trace("Hello World!");
LOG.debug("How are you today?");
LOG.info("I am fine.");
LOG.warn("I love programming.");
LOG.error("I am programming.");

The default log configuration will echo messages to the console as they are written. If your terminal supports ANSI, color output will be used to aid readability.

Log to a File

If you want to write log files in addition to the console, you can set a logging.file or logging.path property in your application.properties. For example...

logging.level.root=INFO
logging.level.com.cburleson.rdfx=TRACE


#output to a temp_folder/file
logging.file=${java.io.tmpdir}/myapp.log

#output to a file
#logging.file=/Users/cburleson/myapp.log


#output to a file called spring.log in the specified path
#logging.path=/var/log

Conclusion

As you can see, logging from your Spring Boot application is piece of cake. Of course, there's a lot more that you can do as your requirements dictate. For more information, take a look at Logging, in the Spring Boot Reference Guide.

References

  • Logging, in the Spring Boot Reference Guide
Confluence Panel Macro

A macro for Atlassian Confluence that mimics a Bootstrap 3 Panel.


Overview

This is a simple macro I developed that mimics the Bootstrap 3 Panel. The macro takes an optional Title, an optional Style, and a rich text macro body. It renders results as shown in the examples section below.

I did this as a learning exercise in preparation to do a more sophisticated plugin for Confluence. You might find the code useful if your doing something similar. You can find the source code in the following public repository on GitHub:

https://github.com/codyburleson/confluence-panel-macro


Tested on:

  • Atlassian Confluence 6.0.5
  • Google Chrome Version 57.0.2987.98 (64-bit) on Mac OS

If you find any issues, please open an issue on GitHub.

Examples


This panel has no title filled out and no style selected. It shows an example of using another macro within the body; the STATUS MACRO .

No Style Selected

 This one has a title, but no style selected. It defaults to the 'default' style.

Default

This panel is configured with the style 'default'.

Primary

 This panel is configured with the style 'primary'.

Success

 This panel is configured with the style 'success'.

Info

 This panel is configured with the style 'info'.

Warning

 This panel is configured with the style 'warning'.

Danger

 This panel is configured with the style 'danger'.

Attachments

The current version of the macro that I've got deployed here in this wiki is attached. If you want to try it out, you can upload the jar through Manage add-ons in the Confluence admin area.

  File Modified
Java Archive panelMacro-1.0.0-SNAPSHOT.jar Mar 25, 2017 by Cody Burleson

License

MIT License

Copyright (c) 2017 Cody Burleson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

This page is a never-ending work-in-progress where I've decided to capture handy little tips for enhancing workflow in WebStorm. Shoot me an email if you know of any other handy little things that I can add to this list.

Auto-Import (TypeScript)

Put cursor at end of red text object name and click CTRL + SPACE (auto-complete) and select the object from the pop-up context menu.

Comment Code

Select code, then CMD + /

Multiple Cursors

ALT + CLICK to set multiple cursors manually on the page. (on mac that includes the function key)
or...
Select a word and press CTRL + G 


Did you know that Emmet's built-in to WebStorm? Emmet takes the snippets idea to a whole new level: you can type CSS-like expressions that imply the HTML structure you want, press Tab, and then WebStorm spits out the desired HTML.

Try It!

Here's a simple example of a basic expression in the Emmet syntax. Try typing the following in an HTMl page in WebStorm:

ul>li*10

At the end of the expression, just hit the Tab key. WebStorm will use Emmet to parse the text and spit out the intended HTML which, in this case, will be an unordered list with 5 list items (shown below):

<ul>
   <li></li>
   <li></li>
   <li></li>
   <li></li>
   <li></li>
</ul>


For details, you can refer to the Emmet Documentation, but for convenience, I'm including the most common stuff below. There's a lot more to it though, so if you like what you see here, be sure to RTFM.

Emmet Syntax

Nesting

You can use > operator to nest elements inside each other:

div>ul>li
Result
<div>
    <ul>
        <li></li>
    </ul>
</div>

Sibling

Use + operator to place elements near each other, on the same level:

div+p+bq
Result
<div></div>
<p></p>
<blockquote></blockquote>

Multiplication

With * operator you can define how many times element should be outputted:

ul>li*5
Result
<ul>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
</ul>

Grouping

Parenthesises are used by Emmets’ power users for grouping subtrees in complex abbreviations:

div>(header>ul>li*2>a)+footer>p
Result
<div>
    <header>
        <ul>
            <li><a href=""></a></li>
            <li><a href=""></a></li>
        </ul>
    </header>
    <footer>
        <p></p>
    </footer>
</div>

If you’re working with browser’s DOM, you may think of groups as Document Fragments: each group contains abbreviation subtree and all the following elements are inserted at the same level as the first element of group.

You can nest groups inside each other and combine them with multiplication * operator.

ID and CLASS


In CSS, you use elem#id and elem.class notation to reach the elements with specified id or class attributes. In Emmet, you can use the very same syntax to add these attributes to specified element:

div#header+div.page+div#footer.class1.class2.class3
Result
<div id="header"></div>
<div class="page"></div>
<div id="footer" class="class1 class2 class3"></div>

Custom attributes

You can use [attr] notation (as in CSS) to add custom attributes to your element:

td[title="Hello world!" colspan=3]
Result
<td title="Hello world!" colspan="3"></td>

Item numbering

With multiplication * operator you can repeat elements, but with $ you can number them. Place $ operator inside element’s name, attribute’s name or attribute’s value to output current number of repeated element:

ul>li.item$*5
Result
<ul>
    <li class="item1"></li>
    <li class="item2"></li>
    <li class="item3"></li>
    <li class="item4"></li>
    <li class="item5"></li>
</ul>

You can use multiple $ in a row to pad number with zeroes:

ul>li.item$$$*5
Result
<ul>
    <li class="item001"></li>
    <li class="item002"></li>
    <li class="item003"></li>
    <li class="item004"></li>
    <li class="item005"></li>
</ul>

Text

You can use curly braces to add text to element:


a{Click me}
Result
<a href="">Click me</a>

Greek Text

You know - that filler text that designers use when they can't think of real words that are relevant to a design...

lorem5
Lorem ipsum dolor sit amet.


Holy Shit Example

Here's a crazy example to give you an idea of what you can accomplish if you get bad ass with Emmet.

Emmet Abbreviation
nav#menuSystem.navMenu.isOpen>div#hotelLogo>div.navMenuIcon.logoIcon+div#arrowPointer+ul#navMenuMain>li.navMenuItem.navMenuItem$$$*10>div.navMenuIcon{Item $}+a{Item $}
Result
<nav id="menuSystem" class="navMenu isOpen">
   <div id="hotelLogo">
      <div class="navMenuIcon logoIcon"></div>
      <div id="arrowPointer"></div>
      <ul id="navMenuMain">
         <li class="navMenuItem navMenuItem001">
            <div class="navMenuIcon">Item 1</div>
            <a>Item 1</a></li>
         <li class="navMenuItem navMenuItem002">
            <div class="navMenuIcon">Item 2</div>
            <a>Item 2</a></li>
         <li class="navMenuItem navMenuItem003">
            <div class="navMenuIcon">Item 3</div>
            <a>Item 3</a></li>
         <li class="navMenuItem navMenuItem004">
            <div class="navMenuIcon">Item 4</div>
            <a>Item 4</a></li>
         <li class="navMenuItem navMenuItem005">
            <div class="navMenuIcon">Item 5</div>
            <a>Item 5</a></li>
         <li class="navMenuItem navMenuItem006">
            <div class="navMenuIcon">Item 6</div>
            <a>Item 6</a></li>
         <li class="navMenuItem navMenuItem007">
            <div class="navMenuIcon">Item 7</div>
            <a>Item 7</a></li>
         <li class="navMenuItem navMenuItem008">
            <div class="navMenuIcon">Item 8</div>
            <a>Item 8</a></li>
         <li class="navMenuItem navMenuItem009">
            <div class="navMenuIcon">Item 9</div>
            <a>Item 9</a></li>
         <li class="navMenuItem navMenuItem010">
            <div class="navMenuIcon">Item 10</div>
            <a>Item 10</a></li>
      </ul>
   </div>
</nav>




Here's how to avoid tracking page views in Google Analytics when you're logged in as an administrative or other specific user.


This solution presupposes that your Google Analytics tracking snippet is pasted into the field labelled "At end of the HEAD" in the Custom HTML section in the Confluence Administration Area as shown below...

Before modification, your Google Analytics tracking code should look something like this.

<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-21819432-1', 'auto');
ga('send', 'pageview');


</script>

Now, to avoid tracking pages for an administrative user (or any particular user or set of users, for that matter), you can wrap the ga() function calls in an IF check. In the following code, we use the Confluence AJS object to determine whether or not the authenticated user is an administrative user. If that's true, we do not reach the ga() functions and thus, the pageview is not tracked. In all other cases, however, the pageview will be tracked.

<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

if(! AJS.params.isConfluenceAdmin) {
	ga('create', 'UA-21819432-1', 'auto');
  	ga('send', 'pageview');
}
</script>

You could use console.log statements to test this in your local browser.

If you want to avoid logging page views for specific users, you can check for specific user names with AJS.params.remoteUser.


In my digital journal, I've kept a page called "Bucket List", but I've kicked it out in favor of a new title: "Goals".

I've learned that the language you use and the story you tell yourself is fundamentally important to what you actually achieve and do. And maybe it goes even deeper than that. Maybe it's not just the language you use, but the personal meaning that you've ascribed to the concepts in that language. The Merriam-Webster online dictionary defines a bucket list as a list of things that one has not done but wants to do before dying. That's how I think of it too, but there's something about the concept for me that is far more forgiving than a list of goals. I think of the bucket list as a list of frilly things or nice-to-dos - just icing on the cake of life, which you never have to eat. Nobody's going to hold it against you if you don't do the things that are on your bucket list - not even yourself. Death is your deadline, after all.

I don't think that works for me, so instead, I've decided to change the title of my Bucket List to "Goals". The meaning of the two concepts are entirely different to me. Goals are much more real. For me, a goal implies a stronger intent to achieve it. A goal begs an action plan. A goal begs to be SMART:

  • Specific (and Significant)
  • Measurable (and Meaningful)
  • Attainable (and Action-oriented)
  • Realistic, Relevant (and Rewarding)
  • Time-Based (and Time-bound or Trackable)

To me, the things on a bucket list are just ideas. Possibilities. Opportunities. I'd rather think of them as "draft goals". They're worth writing down. But give them any careful thought and they might just as easily fall off the list as get prioritized and planned. So, now I have my list of goals - some prioritized and planned. Some SMART. Some, just draft ideas. But I don't have a bucket list anymore.

There are simply things I've done, things I will do, and things I might do - goals I've achieved, goals I will achieve, and then all the things that might or might not become goals. If they become goals, they'll get an action plan and evaluation against the principles of SMART. If they don't make it to that point, then they probably are just frilly ideas that don't deserve to stay on the list.

 

This week, I've been listening to the audiobook, Benjamin Franklin: An American Life by Walter Iscaacson. My favorite part, so far is where he recounted thirteen virtues that Benjamin Franklin recorded in his autobiography. I think Ben Franklin wrote these in 1726, at the mere age of twenty.

  1. Temperance. Eat not to dullness; drink not to elevation.
  2. Silence. Speak not but what may benefit others or yourself; avoid trifling conversation.
  3. Order. Let all your things have their places; let each part of your business have its time.
  4. Resolution. Resolve to perform what you ought; perform without fail what you resolve.
  5. Frugality. Make no expense but to do good to others or yourself; i.e., waste nothing.
  6. Industry. Lose no time; be always employ'd in something useful; cut off all unnecessary actions.
  7. Sincerity. Use no hurtful deceit; think innocently and justly, and, if you speak, speak accordingly.
  8. Justice. Wrong none by doing injuries, or omitting the benefits that are your duty.
  9. Moderation. Avoid extremes; forbear resenting injuries so much as you think they deserve.
  10. Cleanliness. Tolerate no uncleanliness in body, cloaths, or habitation.
  11. Tranquillity. Be not disturbed at trifles, or at accidents common or unavoidable.
  12. Chastity. Rarely use venery but for health or offspring, never to dullness, weakness, or the injury of your own or another's peace or reputation.
  13. Humility. Imitate Jesus and Socrates.

Angular 2 provides a Title service that you can use to set the title of a page. This is good, of course, for SEO. Here's how to use it.

import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser'; 

@Component({
	selector: 'my-app',  
	viewProviders: [Title],  
	template: `<h1>MyApp</h1>` 
}) 
export class MyAppComponent {
	constructor(title: Title) {
		title.setTitle('My App - Page Title');
	}
}

The service automatically creates the title element in the head if needed and also sets the value.  The service also has a getter method to get the title.

Pink Hearts

My sister tried to kill herself. Or, at least, I think she did.

I was eleven years old. I don't remember much about it; it was a long time ago - kind of a blur at this point. The thing that sticks out is all the little pink hearts - speed pills. She'd swallowed a shit-load, then sprayed them all over the upholstery of the car with her vomit.


Mom rolled down the windows. The stench was bad.

I watched those little pills drip off the top of the car as we raced to the hospital to have her stomach pumped.

She was thirteen years old.

She survived it.

To this day, I don't know why she did that.

I was too young to understand much, but it makes me wonder now.

What could have been so troublesome for her at that age? I don't even recall her having a boyfriend at the time. She never did drugs before then. It's not like we lived on skid row or anything - we were at my sweet grandmother's house for Christ's sake. Where in the hell does a thirteen year old girl find a bunch of speed? 

Maybe they were just caffeine pills. Maybe it was just a ploy for attention or a cry for help. Maybe not.

I'll never know.



In my last two posts about Carbon LDP, I used a very simple Create Blog Postexample to show how to save a document and then how to save a document treeusing the JavaScript SDK. In this post, I build upon those examples by showing how to save a document using established vocabularies.

A Linked Data Platform best practices is to use re-use established linked data vocabularies instead of (re-)inventing duplicates. In my last two posts, we saw that you can create persist any arbitrary JavaScript object to Carbon, such as the one shown below.

content = {
	title: contentTitle,
	body: contentBody,
	publishedDate: pubDate
};

As a matter of convenience, Carbon will automatically generate vocabulary for the properties of such an object. That is to say, it will create a fully qualified URI for the property that is based on the application context you are working in. If the application istest-app/, for example, the following URIs are generated for the properties title, body, and publishedDate found in the JavaScript object shown above.

In most cases, however, you'll want to adhere to the LDP best practice by using terms from pre-existing, well-established vocabularies. The dcterms vocabulary from the Dublin Core®Metadata Initiative, for example. Since the Dublin Core terms are intended for describing media resources, it makes sense to use dcterms:title for the title of blog post content. Also, in order to make my data more flexible with different systems, I'd also like to store the title using the predicate (or property) type, rdfs:label. To do this, only minor modifications to the code we've been using are required.

We can add vocabulary for Carbon to use with the Carbon.extendObjectSchema method. Following is one way that works.

carbon.extendObjectSchema( {
    "dcterms": "http://purl.org/dc/terms/",
    "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
    "xsd": "http://www.w3.org/2001/XMLSchema#",
    "title": {
        "@id": "dcterms:title",
        "@type": "xsd:string"
    },
    "label": {
        "@id": "rdfs:label",
        "@type": "xsd:string"
    }
} );

 

In the example above, we are defining the namespace prefixes and URIs of the vocabularies to use. Then we are telling the system to use specific terms within each vocabulary whenever a JavaScript object's property is found with the specified matching name. For example, anyObject.title would submit dcterms:title to the Carbon server. Likewise,anyObject.label would submit rdfs:label.

However, because we have specified no particular object types, these vocabulary terms will be used globally for any object found with matching properties, which we may not always want. For example, we may decide to use a #title term from one vocabulary for a given object type, and a #title term from some other vocabulary for another. Instead of defining the use of these terms globally, we can alternatively be more specific in the following way. We can define the vocabularies globally, but then specify the use of those vocabularies more specifically to a given object type as shown below.

// Define common prefixes in the general object schema
carbon.extendObjectSchema({
    "dcterms": "http://purl.org/dc/terms/",
    "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
    "xsd": "http://www.w3.org/2001/XMLSchema#"
} );

// Use the specified terms only in the case of 
// BlogPost object type
carbon.extendObjectSchema( "http://example.org/ont/web#BlogPost", {	
    "title": {
        "@id": "dcterms:title",
        "@type": "xsd:string"
    },
    "label": {
        "@id": "rdfs:label",
        "@type": "xsd:string"
    }
} );

 

We also need to modify our JavaScript object to declare it has the type of BlogPost. This is done with the types property as shown below.

content = {
	title: contentTitle,
	label: contentTitle,
	body: contentBody,
	publishedDate: pubDate,
	types: ["http://example.org/ont/web#BlogPost"]
};

Complete Example

Here's all the code from the Create a BlogPost example exercise now.

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
	<meta name="description" content="A chronological log of thoughts, personal reflections, discoveries, and random musings.">
	<meta name="author" content="Cody Burleson">
	<title>Create Blog Post</title>
	<link rel="icon" href="/images/favicon.ico">
	<link rel="author" href="https://plus.google.com/100082232329079808865"/>
	<link href="/css/bootstrap.min.css" rel="stylesheet"/>
	<link href="/css/styles.css" rel="stylesheet"/>
	<link id="link-rel-common" rel="import" href="/components/cb-common/common.html">
	<script src="/ckeditor/ckeditor.js"></script>
	<script src="/_head-script.js"></script>
	
</head>

<body>

	<article id="content">
		<div id="pageContainer" class="container">

			<div class="row">

				<div class="col-md-12">

					<h1>Create Blog Post</h1>

					<p class="lead">
						Share your thoughts with the world. 
					</p>
					
					<form>
						<div class="row">
							<div class="form-group col-xs-6">
								<label for="userName">Email (Login ID)</label> 
								<input type="text" class="form-control" id="userName" value="cody@base22.com">
							</div>
							<div class="form-group col-xs-6">
								<label for="password">Password</label>
								<!-- Change to type="password" in a real-world scenario... -->
								<input type="text" class="form-control" id="password" value="password">
							</div>
						</div>
						<div class="form-group">
							<label for="contentTitle">Title</label> 
							<input type="text" class="form-control" id="contentTitle" placeholder="Content Title">
						</div>
						<div class="form-group">
						    <label for="editor1">Body</label>
						    <textarea class="form-control" id="editor1" name="editor1" rows="10" cols="80">
						     Write your brilliant words here.
						    </textarea>
						</div>
						<div class="form-group">
							<label for="slug">Slug</label> 
							<input type="text" class="form-control" id="contentSlug" placeholder="content-uri-slug">
						</div>

			            <script>
			                // Replace the <textarea id="editor1"> with a CKEditor
			                // instance, using default configuration.
			                CKEDITOR.replace( 'editor1' );
			            </script>
			            <p></p>
			            <div class="alert alert-success" role="alert" id="alertSuccess" style="display:none">
						  <p><strong>Document saved successfully!</strong><br/></p>
						  <a href="#" class="alert-link" id="alertSuccessDocId"></a>
						</div>
			            
			            <button type="button" class="btn btn-primary" id="submitButton">Submit</button>
			        </form>

				</div>

				<!-- ***** RIGHT PANEL ***** -->

				<div id="right" class="col-md-4">


				</div><!-- /.col-md-4 -->

			</div><!-- /.row -->
		</div><!-- /.container -->
	</article>

	<script src="/javascript/all.js"></script>
	<script src="/_footer-script.js"></script>
	<!-- Source: ./src/main/typescript/create-content.ts -->
	
	
	<script src="/javascript/moment.min.js"></script>
	
	<!-- IE required polyfill -->
	<!-- Copied by gulp copy-resources from node_modules/core-js/client/shim.js -->
	<script src="/javascript/shim.min.js"></script>
	
	<script src="/javascript/Carbon.sfx.js"></script>
	
	<script>
	
	(function() {

		var APP_SLUG = 'test-app/';
		
		var carbon = new Carbon();
		carbon.setSetting( "domain", "localhost:8083" );
		carbon.setSetting( "http.ssl", false );
		
		// Define common prefixes in the general object schema
		carbon.extendObjectSchema({
		    "dcterms": "http://purl.org/dc/terms/",
		    "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
		    "xsd": "http://www.w3.org/2001/XMLSchema#"
		} );
		
		// Use the specified terms only in the case of 
		// BlogPost object type
		carbon.extendObjectSchema( "http://example.org/ont/web#BlogPost", {	
		    "title": {
		        "@id": "dcterms:title",
		        "@type": "xsd:string"
		    },
		    "label": {
		        "@id": "rdfs:label",
		        "@type": "xsd:string"
		    }
		} );
		
		// Returns the slug (document identifying portion) of the given URI.
		function getSlug(uri) {
			// Snip off the trailing slash, if the uri has one...
			if(uri.endsWith('/')) {
				uri = uri.substring(0,uri.length-1);
			}
			// Return all characters after the last remaining slash if one 
			// exists, otherwise return the given string
			if ( uri.indexOf('/') != -1 ) {
				var lastSlashNdx = uri.lastIndexOf('/');
				return uri.substring(lastSlashNdx + 1, uri.length);
			} else {
				return uri;
			}
		}

		function getOrCreateAndGetChildDocument(myAppContext,parentDocId,docId,doc) {
			console.log(">> getOrCreateAndGetDocument()");
			var slug = getSlug(docId);
			return myAppContext.documents.exists( docId ).then(
	    		function( promiseResult ){
					if( promiseResult[0] ) {
						console.log("Document exists; fetching it...");
						return myAppContext.documents.get( docId );
					} else {
						console.log("Document does not exist; creating it...");
						return myAppContext.documents.createChildAndRetrieve( parentDocId, doc, slug );
					}
	    		}
	    	);
		}
		
		
		function submitContent() {
			
			// Get all the values from the UI Form...
			// (No form validation going on here yet)
		    var contentTitle = document.getElementById("contentTitle").value;
		    var contentBody = CKEDITOR.instances.editor1.getData();
		    
		    var pubDate = moment().format();
		    var pubYear = moment().format('YYYY');
		    var pubMonth = moment().format('MM');
		    var pubDay = moment().format('DD');
		    
		    var contentSlug = document.getElementById("contentSlug").value;
		    var userName = document.getElementById("userName").value;
		    var password = document.getElementById("password").value;

		    var myAppContext,
		    	content,
		    	blogContainer,
		    	yearContainer,
		    	monthContainer,
		    	dayContainer;
		    
		    return carbon.auth.authenticate( userName, password ).then(
			    	function( token ) {
			    		//console.log( "Authenticated Agent: %o",carbon.auth.authenticatedAgent ); // Yourself!
			    		return carbon.apps.getContext( APP_SLUG );
			    	}
			    ).then(
			    	function( appContext ) {
			    		myAppContext = appContext; // retrieving your app context
			    		
			    		// First, lets create the blog container
			    		
			    		// ID of the Carbon document you want to retrieve
						//var containerId = "http://localhost:8083/apps/" + APP_SLUG + "blog2/";
			    		var containerId = "blog/";
						
			    		blogContainer = {
			    			title: "Blog"
			    		};
			    		
			    		// Create the Blog container if it doesn't exist...
						return getOrCreateAndGetChildDocument(myAppContext,"/",containerId,blogContainer);
					}
				).then(
					function( result ) {
						blogContainer = result[0];
						console.log("blogContainer: %o",blogContainer);

						yearContainer = {
			    			title: pubYear
				    	};

						return getOrCreateAndGetChildDocument(myAppContext,blogContainer.id,blogContainer.id + pubYear + "/",yearContainer);
					}
				).then(
					function( result ) {
						yearContainer = result[0];
						console.log("yearContainer: %o",yearContainer);

						monthContainer = {
				    			title: pubMonth
					    };
						
						// Create the month container if it doesn't exist
						return getOrCreateAndGetChildDocument(myAppContext,yearContainer.id,yearContainer.id + pubMonth + "/",monthContainer);
					}
				).then(
					function( result ) {
						monthContainer = result[0];
						
						console.log("monthContainer: %o",monthContainer);
						
						dayContainer = {
				    			title: pubDay
					    };

						// Create the day container if it doesn't exist
						return getOrCreateAndGetChildDocument(myAppContext,monthContainer.id,monthContainer.id + pubDay + "/",dayContainer);
					}
				).then(
					function( result ) {													
						dayContainer = result[0];
						
						console.log("dayContainer: %o",dayContainer);

						content = {
			    			title: contentTitle,
			    			label: contentTitle,
			    			body: contentBody,
			    			publishedDate: pubDate,
			    			types: ["http://example.org/ont/web#BlogPost"]
				    	};
												
						return getOrCreateAndGetChildDocument(myAppContext,dayContainer.id,dayContainer.id + contentSlug + "/",content);
					}
				).then(
					function( result ) {												
						content = result[0];
						console.log("Content: %o", content);
						
						document.getElementById(alertSuccess)
						
						var a = document.getElementById("alertSuccess"), aStyle = a.style;
						document.getElementById("alertSuccessDocId").nodeValue = content.id;
						
						var $alertLink = $("#alertSuccessDocId");
						$alertLink.text(content.id);
						$alertLink.attr("href", content.id)
						aStyle.display = "block";
						
					}
				).catch( 
						function( result ) {
							console.log("ERROR: %o", result);
						}
				);

			
		}

		var el = document.getElementById('submitButton');
		el.addEventListener("click", submitContent, false);
		
	})();
	
	</script>

</body>
</html>