Skip to content
This repository has been archived by the owner on Feb 26, 2022. It is now read-only.

JEP Places API

Jordan Santell edited this page Jun 4, 2013 · 20 revisions

Places API JEP (WIP)

Overview

An API for Firefox Places (History and Bookmarks).

Current Hacking

Goals

  • Phase 1
    • Provide easy to use API for CRUD, querying History and Bookmarks
  • Phase 2

Proposed API

Bookmark

Bookmark contains static methods to create, update and manipulate bookmark items like bookmarks, folders and separators. Bookmark, Group and Separator classes all are considered BookmarkItem types.

Functions

Bookmark(properties)
  • @param {Object} properties
  • @return {Bookmark}

Takes an object of properties, and returns a new Bookmark object.

  • String title: required The name of the bookmark
  • String|URL url: required the url (either as a String or URL instance)
  • Group group: The Group instance that the bookmark should live under. (Default: the menu folder)
  • Number index: The position of the bookmark within its parent. Defaults to the last position. (Default: -1) MAY BE REMOVED
  • Set tags: A Set of tags.
let { Bookmark } = require('bookmark');

Bookmark({
	title: 'Mozilla',
	url: 'http://www.mozilla.org
});
Group(properties)
  • @param {Object} properties
  • @return {Group}

Takes an object of properties, and returns a new Group object.

  • String title: required The name of the group
  • Group group: The Group instance that the group should live under. (Default: the menu folder)
  • Number index: The position of the bookmark within its parent. Defaults to the last position. (Default: -1) MAY BE REMOVED
Separator(properties)
  • @param {Object} properties
  • @return {Separator}

Takes an object of properties, and returns a new Separator object.

  • Group group: The Group instance that the separator should live under. (Default: the menu folder)
  • Number index: The position of the separator within its parent. Defaults to the last position. (Default: -1) MAY BE REMOVED
save(…items, [options])
  • @param {BookmarkItems|Array} …items
  • @param {Object} options
  • @return {Promise}

Pushes properties of items to their platform counterparts -- returns a promise that resolves to new instances of these items upon saving.

Options object has one property, resolve, which is a function accepting mine and theirs arguments, which are the item attempting to save and the item currently saved on the platform. resolve is called if attempting to save a BookmarkItem that has been updated since the object was fetched from the platform to perform a manual diff.


let { Bookmark, save } = require('bookmarks');

var bookmarks = [
	{ title: "Moz", url: "http://mozilla.org" },
	{ title: "Twitter", url: "http://twitter.com" }
].map(Bookmark);

save(bookmarks).then(function (bookmarks) {
  // Updated `bookmarks`!
  // These instances are new, fresh instances
  // and !== the initial bookmarks passed into input
})

Resolving

let { query, save } = require('bookmarks');

query({ url: 'http://mozilla.org' }).then(updateBookmark);

function updateBookmark (bm) {
  // So let's update this bookmark, and say the user
  // or another add-on has already changed it, so
  // this instance is out of date already.
  
  bm.title = "My Moz";
  
  save(bm, {
    resolve: function (mine, theirs) {
       // Here we can either just choose to 
       // overwrite the title via
       // `theirs.title = mine.title; return theirs;`
       // Which will keep the users' changes other
       // than the title
       
       // Or we can just completely ignore everything the
       // user did and pass all of `bm`s current props:
       // `return mine;`
    }
  })
   
}

Recursive Saving

let { Bookmark, Group, save } = require('bookmarks');

var g = Group({ title: "Mine" });
var b1 = Bookmark({ url: "http://foo", title: "foo", group: g });
var b2 = Bookmark({ url: "http://bar", title: "bar", group: g});

// Since both `b1` and `b2` have `g` as their parent,
// and `g` has not yet been saved, this will first 
// save `g`, and then save `b1` and `b2`
save(b1, b2)

Duck Typing (requires type property)

save({ url: 'http://foo', title: 'foo', type: 'bookmark' })

query(…options)
  • @param {Object} options
  • @return {Promise}

Queries can be performed on bookmark items by passing in one or more query options. Each query option can take several properties, which are AND'd together to make one complete query. For additional queries within the query, passing more query options in will OR the total results.

Query options:

** SHOULD THESE BE SINGULAR OR PLURAL? **

** Multiple groups should not be allow within a single query. Other properties are AND'd together, and even properties within itself (multiple tags for example) are AND'd, and while it wouldn't make sense to AND groups, following this pattern makes more sense **

  • String|Array tags: Bookmarks with corresponding tags
  • String|Array urls: URLs that should match bookmarks
  • Group group: Group instances that should be owners of the returned children bookmarks
  • String query: Search terms to search url, title

More properties may be added via the HistoryQuerying service, such as searching titles.

let { query } = require('bookmarks');

// Assume we have group `g1`

// This query gives us all bookmarks with tag 'mozilla'
// that are children of `g1`.
query({
  tags: 'mozilla',
  group: g1
})

// If we wanted to get the children of g1 
// that have tag 'mozilla' OR a bookmark anywhere that
// has tag 'firefox', we can make two query options that
// are OR'd together
query({
  tags: 'mozilla',
  group: g1
}, {
  tags: 'firefox'
})

// Similar to the last query, we can all bookmarks that
// are children of `g1` that have BOTH the 'mozilla' and
// 'firefox' tag, as all the properties within a query
// are AND'd together
query({
  tags: ['mozilla', 'firefox'],
  group: g1
})


remove(…items)
  • @param {Bookmark|Folder|Separator|Array} items
  • @return {Promise}

Sugar for modifying the remove property to true of BookmarkItems. Simple transformation.

let { remove } = require('bookmark');

let item = someQueryReturningAnItem();

save(remove(item)).then(function () {
  // removed
})
Examples

Here are some platform methods converted to using this API

Getting Children
let p = query({ query: 'my-folder' })

p.then((res) => query({ group: res[0] }))

Removing Children
let p = query({ query: 'my-folder' })

let children = p.then((res) => query({ group: res[0] }))

children.then(function (bookmarks) {
  save(remove(children))
});

// or

children.then(compose(save, remove));

// or

query({ group: group }).then(compose(save, remove));

Get Bookmarks by URL
query({ url: 'http://mozilla.org' })
Get Bookmarks by Tag
query({ tags: 'firefox' })
Is URL bookmarked
query({ url: 'http://mozilla.org' }).then((x) => !!x.length);

Properties

These constants store the IDs of default folders and are used in other methods in Folder and Bookmark

  • Bookmark.MENU
  • Bookmark.PLACES
  • Bookmark.TAGS
  • Bookmark.TOOLBAR
  • Bookmark.UNSORTED

Bookmark Class

Properties
  • id readonly
  • index
  • title
  • url
  • group
  • tags

Group Class

Properties
  • id readonly
  • index
  • title
  • group
  • tags

Separator Class

Properties
  • id readonly
  • index
  • group

History

Functions

query(options)
  • @param {Object} options
  • @return {Promise}

Returns a promise for an array of history items that match the query's options.

Options
  • String sort: A string to specify the type of sort to use. Possible options: 'title', 'date', 'url', 'visitCount', 'keyword', 'dateAdded', 'lastModified'. Default is unsorted.
  • Boolean ascending: Whether or not the sorted results should be ascending. Default is false, which returns the results in a descending order. Has no effect if sort is undefined.
  • Number count: Upper limit of how many items are returned. Default is no limit.
  • Date|Number from: Time relative from the epoch that history results should be limited to occuring after. Can accept a Date object, or milliseconds from the epoch. Default is from the epoch (all time).
  • Date|Number to: Time relative from the epoch that history results should be limited to occuring before. Can accept a Date object, or milliseconds from the epoch. Default is the current time.
  • String query: Search terms to match history results with query in its URL or title.
  • String domain: Limit results that originated from a specific domain. Can limit to when domain is only the host, like 'mozilla.org', or specify that subdomains are allowed like '*.mozilla.org'.
let { query } = require('sdk/places/history');

query({
  sort: 'date',
  count: 15,
  from: new Date('1/1/2006'),
  to: new Date('12/31/2006')
}).then(function (items) {
  // `items` contain the first 15 results
  // from the year 2006 in order
})

Notes 6/3/2013

  • Don't expose an "update/refresh" method
  • Rename folders to groups -- more conceptual, and futureproofs if the UI of bookmarks is displayed differently
  • Make id property immutable
  • Robust query interface for bookmarks via .get({}, {}, …) have properties like tags, urls, and other bookmark fields. Will use several services to get the aggregate results. ANDs properties in a single query object, and ORs the results of several query objects (need intersection/union operators on bookmarks)
  • Always return a new data object, do not respect identity
var bm = Bookmark({…});
save(bm).then(function (bookmark) {
  // bm !== bookmark
})
  • Add a resolution function to save, in event of out-of-date bookmark:
save({}, { resolve: function (mine, theirs) {
  // If you want to clobber changes, just return mine
  // if out of date -- or can only overwrite something like
  // the tags property. Whatever is returned is saved.
  return mine;
}})
  • Remove createBookmark and similar methods (should just have Bookmark which instantiates a data structureish class)
  • Remove index on Bookmark items ????
  • Hide id???
  • Map v0 to v1 since a new bookmark will not have an updated time, so we'll need to map the initial save to the initial data structure to infer if conflict resolution is necessary
  • Remove delete/remove functions -- done via a variable like bookmark.remove = true and passed into save method
  • remove removeAllChildren and getChildren -- can be done with a query with a group property
  • Recursively save group dependencies when saving bookmarks if needed (save the parent if it doesn't exist while saving a bookmark)
  • remove isBookmarked -- can be done with a URL query
  • Support splats
  • Remove Tags module -- roll into module with Sets
  • Prevent duplicate creation (?)

Resources