Skip to content

Commit

Permalink
Initial metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
mmynsted committed Apr 22, 2015
1 parent fa08347 commit 1e3303a
Show file tree
Hide file tree
Showing 5 changed files with 357 additions and 0 deletions.
17 changes: 17 additions & 0 deletions examples/src/main/resources/css/social_meta_data.css
@@ -0,0 +1,17 @@
dl {
margin-right: 2em;
width: 500px;
}
dt {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 0.7em;
}
dd {
background:none no-repeat left top;
color: #444;
font-size: 0.9em;
line-height: 1.5em;
margin: 0 0 2em;
padding-left: 4.5em;
}
80 changes: 80 additions & 0 deletions examples/src/main/resources/social_meta_data.html
@@ -0,0 +1,80 @@
<h1>SocialMetadata module</h1>
<p>The <code>SocialMetadata</code> module provides information in meta tags to improve social media, and
search engine crawls. It is separated into sections to target specific consumers.
</p>
<dl>
<dt>Base data</dt>
<dd>Base data are basic meta tags that are typically used by search engines and social media sites that do not
require specially composed tags. The <em>description</em> and <em>keywords</em> are particularly important.
<dl>
<dt>Description</dt>
<dd>Description of the page content.</dd>
<dt>Keywords</dt>
<dd>Keywords that may be used to find the page.</dd>
<dt>Author</dt>
<dd>Author of the page content. Particularly useful when the page represents an article written by a
recognizable author.</dd>
<dt>Copyright</dt>
<dd>When it is important to specify the copyright.</dd>
<dt>Application-name</dt>
<dd>The name of the application where the page resides.</dd>
</dl>
There is also an <code>extras</code> parameter to permit one to add extra parameters.
</dd>
<dt>Twitter</dt>
<dd>Twitter data are used to generate a twitter
<a href="https://dev.twitter.com/cards/types/summary" target="_blank">summary card</a>.
This permits twitter to present a preview for a shared link. The meta data can be tested using the
<a href="https://cards-dev.twitter.com/validator" target="_blank">card validator</a>.
<dl>
<dt>Title</dt>
<dd>The title of the content for the page. Likely should match the <code>title</code> tag designated in the
<code>head</code> of the page, though this should be 70 characters or fewer; the preview will truncate the
title at 70 characters.</dd>
<dt>Description</dt>
<dd>A 140 character description of the page content.</dd>
<dt>Image</dt>
<dd>An image URL to include in the preview. Ideally, a thumbnail image of size 120px by 120px,
or a large image of size 280px by 150px.</dd>
</dl>
There is also an <code>extras</code> parameter to permit one to add extra parameters. Check
<a href="https://dev.twitter.com/cards/markup" target="_blank">here</a> before adding extra parameters.
</dd>
<dt>Open Graph</dt>
<dd>The <em>Open Graph</em> data are typically consumed by Facebook but can be read by other sites some even using
it as a fall-back when their own protocol is unavailable. Details can be found at
<a href="http://opengraphprotocol.org/">opengraphprotocol.org</a> and in Facebook documentation.
<dl>
<dt>Title</dt>
<dd>The title of the content for the page.</dd>
<dt>The content <code>type</code> of the page using the specific keyword.</dt>
<dd>See <a href="http://opengraphprotocol.org/#types" target="_blank">here.</a> Each type can have its own
tags associated with it. Some types include article, book, profile, and others.</dd>
<dt>Image</dt>
<dd>An image URL representing the graphed object. Ideally between 200px by 200px and 1200px by 630px</dd>
<dt>URL</dt>
<dd>The canonical URL of the graphed object. This is the distinct, authoritative, URL for the graphed object.</dd>
<dt>Description</dt>
<dd>A description of the object.</dd>
</dl>
</dd>
<dt>Schema.org</dt>
<dd>Schema.org is a robust and complicated meta data system. It is used by Google+ and others.</dd>
<dd>In addition to typical markup, Schema.org needs to have the content inside a <em>scoped</em> tag.
The expectation for this module is that this will be used on the <code>HTML</code> tag and the page level
schema.org markup will be added to the <code>HEAD</code> with Meta tags via the <code>SchemaDotOrg</code>
method. The simplest <em>type</em> to use for the <code>itemtype</code> is <code>Thing</code>.
</dd>
<dd><dl>
<dt>Name</dt>
<dd>Name of the item.</dd>
<dt>Description</dt>
<dd>Description of the item.</dd>
<dt>Image</dt>
<dd>An image associated with the item.</dd>
</dl>
This is also an <code>extras</code> parameter to permit one to add extra parameters. Check
<a href="http://schema.org" target="_blank">schema.org</a> before adding extra parameters.
</dd>
</dl>
<p>View source, then the head section of the document.</p>
@@ -0,0 +1,119 @@
package org.hyperscala.examples.ui

import org.hyperscala.bootstrap.component._
import org.hyperscala.html.tag
import org.hyperscala.html.tag.HTML
import org.hyperscala.javascript.dsl.window
import org.hyperscala.selector.Selector
import org.hyperscala.ui.module.SocialMetadata
import org.hyperscala.web.Webpage
import org.powerscala.{IO, Color}
import org.hyperscala.BuildInfo
import org.hyperscala.bootstrap.Bootstrap
import org.hyperscala.css.SelectorStyleSheet
import org.hyperscala.html._
import attributes.Target
import org.hyperscala.css.attributes._
import language.reflectiveCalls
import org.hyperscala.ui.module.SocialMetadata._

/**
* Created by mmynsted on 4/17/15.
* code@growingliberty.com
*
* Example for Social Media Meta data {{org.hyperscala.ui.module.SocialMetadata}}
*
*/
class SocialMetadataExample extends Webpage {
this.require(SocialMetadata)
this.require(Bootstrap)

//Common Social Meta Data values
val smTitle = "Hyperscala - SocialMetadata module"
val smDescription =
"""
|Hyperscala module, SocialMetadata provides meta data in meta tags to improve social media
|site linking and semantic search engine crawls.
""".stripMargin
val smKeyWords = "Hyperscala, module, social, media, data, twitter, facebook, open, graph"
val smApplicationName = "Hyperscala"
val smImage = "http://hyperscala.org/images/hyperscala.png"

//Head
title := smTitle
head.contents += new tag.Link(rel = "stylesheet", href = "/css/style.css")
head.contents += new tag.Link(href = "/css/social_meta_data.css")

//main body content
val main = new tag.Div {
contents += new StaticHTML(IO.copy(getClass.getClassLoader.getResource("social_meta_data.html")))
}

//Meta data
head.contents += new tag.Comment("Social Meta Data: START")

//base data
head.contents += new tag.Comment("base data")
SocialMetadata.BaseData(description = smDescription, keywords = Some(smKeyWords),
applicationName = Some(smApplicationName)).tags foreach(head.contents += _)

//twitter
head.contents += new tag.Comment("twitter")
SocialMetadata.TwitterData(title = smTitle,
description = smDescription, image = Some(smImage)). tags foreach(head.contents += _)

//Open Graph / Facebook
head.contents += new tag.Comment("open graph/facebook")
SocialMetadata.OpenGraphData(title = smTitle, contentType = "article",
image = smImage, canonicalUrl = "http://hyperscala.org/example/ui/social_meta_data.html",
description = smDescription, siteName = Some(smApplicationName)).tags foreach(head.contents += _)

//Schema.org
override lazy val html = new tag.HTML { this.makeScoped("Thing")}
head.contents += new tag.Comment("schema.org")
SocialMetadata.SchemaDotOrg(name = smTitle, description = smDescription,
image = smImage).tags foreach(head.contents += _)

head.contents += new tag.Comment("Social Meta Data: END")


/* Cosmetic (Just make the content a little more readable)
*/
body.role := "document"

new SelectorStyleSheet(Selector.element[tag.Body])(body) {
paddingTop := 100.px
paddingBottom := 30.px
}

def sourceURL: String = null

val container = new Container {
clazz += "wrapper"

if (sourceURL != null) {
val filename = sourceURL.substring(sourceURL.lastIndexOf('/') + 1)
contents += new Button(s"View $filename on GitHub", buttonStyle = ButtonStyle.Primary) {
style.float := Float.Right
clickEvent := window.open(sourceURL, Target.Blank)
}
}
contents += main
}
body.contents += container
body.contents += new tag.Footer {
contents += new tag.I {
style.display := Display.Block
style.width := 1170.px
style.marginLeft := Length.Auto
style.marginRight := Length.Auto
style.color := Color.White
style.fontWeight := FontWeight.Bold
style.paddingBottom := 30.px
style.fontSize := FontSize.Small
style.textAlign := Alignment.Right
contents += s"&copy;2015 Hyperscala.org, version: ${BuildInfo.version}, built: ${f"${BuildInfo.buildTime}%tc"}"
}
}

}
Expand Up @@ -90,6 +90,7 @@ object HyperscalaSite extends Website with JettyApplication {
val modalComponent = example(new ModalComponentExample, "UI", "Modal Component", Scope.Page)
val scriptLoader = example(new ScriptLoaderExample, "UI", "Script Loader", Scope.Page)
val socialSharing = example(new SocialSharingExample, "UI", "Social Sharing", Scope.Page)
val socialMetadata = pageExample(new SocialMetadataExample, "UI", "Social Meta Data", Scope.Page)

// Wrapper
val nivoSlider = example(new NivoSliderExample, "Wrapper", "Nivo Slider", Scope.Page)
Expand Down
140 changes: 140 additions & 0 deletions ui/src/main/scala/org/hyperscala/ui/module/SocialMetadata.scala
@@ -0,0 +1,140 @@
package org.hyperscala.ui.module

import org.hyperscala.Tag
import org.hyperscala.html.tag.Meta
import org.hyperscala.module.Module
import org.hyperscala.web.{Website, Webpage}
import org.powerscala.Version

/**
* Created by mmynsted on 4/17/15.
* code@growingliberty.com
*
* Provide simple meta data to improve social media linking
*/
object SocialMetadata extends Module {
val name = "socialmetadata"
val version = Version(1,0,0)

override def init(website: Website): Unit = {}
override def load(webpage: Webpage): Unit = {}

trait SMData {
def tags: Vector[Meta]
def buildMetaData(j: (String, Option[String]) => Option[Meta])(data: (String, Option[String])*) = {
data.foldLeft(Vector.empty[Meta])((b, a) => j(a._1, a._2) match {
case Some(datum) => b :+ datum
case _ => b
})
}
def maybeDatum(k: String, maybeV: Option[String]) = maybeV map (v => new Meta {
content := v
name := k
})
def toOptionalValues(s: Seq[(String, String)]): Seq[(String, Option[String])] = s map{case (k,v) => (k, Option(v))}
}

/**
* Basic Data applies to various social media site, search engines, etc.
*
* @param description of the page content
* @param keywords to help identify the page and its content
* @param author of the page when applicable
* @param copyright when applicable
* @param applicationName is the application-name for the site
* @param extras is an optional [[Seq]] of extra parameters
*/
case class BaseData(description: String, keywords: Option[String] = None, author: Option[String] = None,
copyright: Option[String] = None, applicationName: Option[String] = None,
extras: Seq[(String, String)] = Seq.empty[(String, String)]) extends SMData {
def tags = {
val items = Seq(("description", Some(description)),
("keywords", keywords), ("author", author), ("copyright", copyright),
("application-name", applicationName)) ++ toOptionalValues(extras)
buildMetaData(maybeDatum)(items: _*)
}
}

/**
* Twitter specific meta data.
*
* @param title of page
* @param description of page content
* @param image that illustrates the meaning of the page
* @param extras is an optional [[Seq]] of extra parameters
*/
case class TwitterData(title: String, description: String, image: Option[String] = None,
extras: Seq[(String, String)] = Seq.empty[(String, String)]) extends SMData {
override def maybeDatum(k: String, maybeV: Option[String]) = super.maybeDatum(s"twitter:$k", maybeV)
def tags = {
val items = Seq(("card", Some("summary")), ("title", Some(title)),
("description", Some(description)), ("image", image)) ++ toOptionalValues(extras)
buildMetaData(maybeDatum)(items: _*)
}
}

/**
* Open Graph meta data. This is used mostly for Facebook though other sites/applications do read this.
*
* @param title of the page
* @param contentType of the page, using the specific keyword. Look [[http://opengraphprotocol.org/#types here]]. "type"
* @param image that illustrates the meaning of the page, specific to the type selected.
* @param canonicalUrl is the single authoritative URL (in String format) for the given item of the given contentType.
* @param description of the item and page
* @param siteName where this page resides. "site_name"
* @param extras is an optional [[Seq]] of extra parameters
*/
case class OpenGraphData(title: String, contentType: String, image: String, canonicalUrl: String,
description: String, siteName: Option[String] = None,
extras: Seq[(String, String)] = Seq.empty[(String, String)]) extends SMData {
override def maybeDatum(k: String, maybeV: Option[String]) = maybeV map (v => {
val meta = new Meta { content := v }
meta.attribute[String]("property", true) foreach(_ := s"og:$k")
meta
})
def tags = {
val items = Seq(("title", Some(title)),("type", Some(contentType)),
("image", Some(image)), ("url", Some(canonicalUrl)), ("description", Some(description)),
("site_name", siteName)) ++ toOptionalValues(extras)
buildMetaData(maybeDatum)(items: _*)
}
}

/**
* Schema.org - A robust and complicated meta data system. Used by Google+ and others.
* Look at [[http://schema.org schema.org]]
*
* @param name of the item
* @param description of the item
* @param image can be URL or an ImageObject. (URL would be more compatible with other meta data)
* @param extras optional [[Seq]] of extra parameters
*/
case class SchemaDotOrg(name: String, description: String, image: String,
extras: Seq[(String, String)] = Seq.empty[(String, String)]) extends SMData {
override def maybeDatum(k: String, maybeV: Option[String]) = maybeV map (v => {
val meta = new Meta { content := v }
meta.attribute[String]("itemprop", true) foreach(_ := k)
meta
})
def tags = {
val items = Seq(("name", Some(name)), ("description", Some(description)),
("image", Some(image))) ++ toOptionalValues(extras)
buildMetaData(maybeDatum)(items: _*)
}
}

/**
* Schema.org needs to have the content inside a ''scoped'' tag. To be compatible with the other meta data the
* expectation is that this will be used on the `HTML` tag and the page level schema.org markup will be added to
* the `HEAD` with Meta tags via [[SchemaDotOrg]] method.
*
* @param t is the tag, likely `HTML` to be decorated with the scope identifer and the itemtype.
*/
implicit class ScopedTag(t: Tag) {
def makeScoped(itemType: String): Unit = {
t.attribute[Boolean]("itemscope", true) foreach(_ := true)
t.attribute[String]("itemtype", true) foreach(_:= s"http://schema.org/$itemType")
}
}

}

0 comments on commit 1e3303a

Please sign in to comment.