Toolbars and menus deprecated

How a plugin adds an entry to Drive's toolbar or to one of its context menus, and where that entry will end up.

The order is declared once

Drive offers the same actions in the toolbar and in the file list's context menu, in the same order and the same groups. Both surfaces take that order from one arrangement:

group entries, in order
open View · Edit · Open in OpenCloud
make one New folder (context menu only; makes a subfolder of what was clicked)
take a copy Download (file) · Download (folder) · Save as PDF
share Share · Send by email
change Rename · Edit description · Move · Copy · Lock · Unlock
organise Add to favorites / Remove from favorites · Show in Drive · Folder color · Add to portal
remove Delete · Restore

Open it, take a copy, give it to somebody, change it, mark it, remove it. Change holds what alters the item for everybody who can see it; organise holds what alters only where you find it.

An entry your plugin adds should join the group it belongs to rather than land at either end.

The toolbar

Point: io.ox/files/toolbar/links.

ext.point('io.ox/files/toolbar/links').extend({
  id: 'my-plugin-action',
  index: 450,
  prio: 'hi',
  mobile: 'lo',
  section: 'open',
  title: gt('Do the thing'),
  icon: 'bi/stars.svg',
  ref: 'my/plugin/actions/do-the-thing'
})
  • index places the entry. Core's entries are spaced by 100 from 100 upwards, in the order above, so pick a number between the two you want to sit between. They are not stable across releases - see What changed in 8.54.
  • prio decides bar or dropdown, never order. hi is a button in the bar, lo is an entry under More actions. Both are drawn in the same declared order.
  • A bar button needs an icon. The bar is icon-only with the label as its tooltip; only the dropdown draws text.
  • section draws dividers in the dropdown only. In the bar the groups are invisible and order is the only signal, but set it anyway: an entry that becomes lo later should land in the right group without anybody having to notice.
  • drawDisabled: true keeps a button in place, greyed, where its action does not apply. Use it for an action that is usually available: a button that comes and goes resizes the row under the pointer.

Share leads the bar although its group does not. A toolbar leads with what is prominent across the app, a context menu with the likeliest thing for the object under the pointer; Share is where people reach for it without looking. It is the only exception, and it is pinned by a test.

The file list's context menu

Point: io.ox/files/listview/contextmenu. An entry is { id, index, section, title, ref }, where section is the group number - 10 open, 20 take a copy, 30 share, 40 change, 50 organise, 60 remove.

A context menu holds what acts on the thing that was clicked. Anything acting on the view - upload, new folder, layout, sort - belongs in the toolbar instead. And nothing should be reachable only from a context menu: right-click is an accelerator, not a route.

The folder tree's context menu

Point: io.ox/core/foldertree/contextmenu/<module> - infostore for Drive - with io.ox/core/foldertree/contextmenu/default drawn after it.

A context that composes its own point lists the whole menu there, in its own index space starting at 100. default is the surface for an entry that belongs in every folder menu whatever the app, and for a context that has not composed one yet: those still get default alone, exactly as before.

Anything the context names is not drawn again from default. De-duplication matches on the extension id, so a composition that means to place one of core's entries must use core's id for it. Renaming it gets you both copies - core's from default, and your own.

// draws inside Drive's menu, in its share group
ext.point('io.ox/core/foldertree/contextmenu/infostore').extend({
  id: 'my-plugin-share',
  index: 400,
  draw (baton) { /* ... */ }
})

Two consequences worth knowing:

  • Order between the two points is fixed. Everything the context composed comes first; default follows. An entry on default cannot be placed inside one of the context's groups - if it needs to be, it belongs on the context's point.
  • baton.disable(point, id) reaches one point. An entry that suppresses another - the way the OpenCloud plugin replaces core's Share for its own storages - has to sit on the same point as the entry it suppresses, and before it.

An entry registered with an inline draw cannot be composed by anyone: it can only trail in from default. Export the function if a context might want to place it, the way drawFavoriteToggle is exported from io.ox/core/folder/favorites.

Keeping the two in step

spec/io.ox/files/action-order_test.mjs fails if an action that both surfaces offer appears in a different order or a different group in one of them. It compares by action ref, so the two tables may name the same action differently.

What changed in 8.54

Anything that pinned an index has to be re-derived - a stale index does not fail, it just puts the entry somewhere absurd.

  • io.ox/files/toolbar/links was reordered. Indices bear no relation to the previous ones.
  • io.ox/files/listview/contextmenu sections were renumbered and their meanings changed. The old 50 was Rename/Move/Copy; it is now organise.
  • ToolbarView appends the overflow toggle at the end of the bar, rather than after the last lo entry. Toolbars that declare their lo entries last are unaffected.
  • Send by email is a bar button in Drive rather than a dropdown entry.
  • Drive's folder-tree entries left contextmenu/default. folder-color, move-section-up, move-section-down and hide-section are on contextmenu/infostore now. A plugin that placed itself against one of those indices has to move with them.
  • New folder replaces Add new folder as the entry's label, everywhere it appears.