Make ActionButtons editable

I just tried out the “forcedROMode” described in the docs in the ActionButtons section.

While implementing a “RO-Toggle” command, my demand came up: I’d like to edit the Action-Buttons “on-the-fly” to change a button/icon to represent the current status.

Would be nice, if the “actionbutton.define” function would get an “actionbutton.update” equivalent :slight_smile:
(or maybe an “actionbutton.delete” to remove a button to create it new)

Dont’t know, if changing buttons at runtime is possible, anyway…

1 Like

How about something like this:

actionButton.define {
  icon = (editor.getUiOption("forcedROMode") and "eye-off") or "eye",
  run = function()
    local mode = editor.getUiOption("forcedROMode")
    editor.setUiOption("forcedROMode", not mode)
    editor.rebuildEditorState()
    editor.reloadConfigAndCommands()
  end
}
2 Likes

Sounds great. Good Idea, i will try that as soon as possible :slight_smile:

1 Like

It works, thanks! :slight_smile:

The only thing that the top bar mini-editor does not reflect the mode. It would be nice if it also can be turned to read only mode somehow. Any ideas?

Tried it and it works like a charm! Thanks again!
In fact, that basically is exactly what I tried to achieve.

The puzzle piece I was missing obviously is the “actionbutton.define” function.
Until now I used the "config.set { actionbuttons = … } definition.

And thanks for the idea to simply reload the config/UI. Did not see the forest for the trees here… :wink:

Only thing I have to figure out now is how to define the position of the button, as via “actionbuttons.define”, the button is placed at the end (which is only a cosmetic issue for me…)

1 Like

Glad that worked :smiley:
Looking at the implementation, the config.set { actionbuttons = ... } method should still work, as actionbutton.define is just a wrapper for that. I think what’s changed is that instead of the command key you’d use the run key with a function value. That way you can still control the order in wich the buttons are placed. Didn’t try that yet though.

Don’t think there’s a build-in way, but I found a way to do it with dom-manipulation:

js.window.document.querySelector(".sb-mini-editor .cm-content").contentEditable = false

What I ended up with:

local ro = editor.getUiOption("forcedROMode")
actionButton.define {
  mobile = true,
  icon = (ro and "edit2") or "eye",
  run = function()
    local cursor = editor.getCursor()
    editor.setUiOption("forcedROMode", not ro)
    editor.rebuildEditorState()
    editor.reloadConfigAndCommands()
    js.window.document.querySelector(".sb-mini-editor .cm-content").contentEditable = ro
    editor.moveCursor(cursor, true)
  end
}

This is for the CONFIG if somebody needs it…

```space-lua
config.set {
  actionButtons = {
    -- some buttons
    {
      -- mobile: true,
      icon = editor.getUiOption('forcedROMode') and 'edit' or 'eye',
      run = function()
        local ro  = editor.getUiOption('forcedROMode')
        local cur = editor.getCursor()
        editor.setUiOption('forcedROMode', not ro)
        editor.rebuildEditorState()
        editor.reloadConfigAndCommands()
        js.window.document.querySelector('.sb-mini-editor .cm-content').contentEditable = ro
        editor.moveCursor(cur, true)
      end,
      description = 'Toggle read-only mode'
   },
   -- other buttons
}
```

The mobile: true causes it will apply on mobile device only and the button will be invisible on non-mobile device.

Thanks a lot!! This is great!!
This way it is even possible to define the positions of the icons.

Some support for ordering is needed for actionButton.define{}. I’ve no time to inspect now, but perhaps there is some already. It would be weird a little to not allow ordering of the buttons in the first plan. :slight_smile:

UPDATE Of course there IS the priority already! :smiley:

Example:

actionButton.define {
  priority = 1000,
  -- the rest
}
  • priority (optional): determines priority of button (the higher, the earlier in the list)

Also see Library/Std/Action Button.md.

1 Like

This is a pro and a con of silverbullet.md at the same time.
It has so many hidden gems!
Sometimes you only have to find them :slight_smile:

(And sometimes you have to have the idea to search for them in the first place :wink: )

I found one more thing that should not be “editable” in read-only mode. Tasks can still be clicked and edited. :frowning: (I hit this one accidentaly while on mobile where I use this primarily.)

Also, it would be nice if we can hide the edit icon for ${} queries. :slight_smile:

Anything else? It will be splendid if we hack all of this together to achieve truly safe read-only mode to be used while rumbling on public transportation vehicles, won’t it? :smiley:

Well, from my point of view I like it exacty the way it is now.
For example:
I write my shopping list to a note at home. Then I switch to readonly.
Later in the shop I can still check my purchased items, but it prevents editing of the list itself.

Before I discovered readonly, I had several times, that I deleted some text by mistake instead of checking.

So, we might need two different read-only modes :wink:

I destroyed many things historicaly while using SB in weird and rushy situations that I would rather be safe on mobile. Just looking for a way to do it. The effort is also worth as example of what can be done using the more advanced APIs. And, it’s just a Lua function in the end. Put whatever suit you best in it. :slightly_smiling_face:

So I extended this with all of the above and some extra features like persistence, and made it easily configurable. You can now also (optionally):

  • Disable the checkboxes!
  • Remember the read-only mode across sessions and set it on reload, so that for example on mobile it will stay on read-only mode

This is what I’ve got now and it works great!

-- Must be set before actionButtons
config.set {
  readOnly = {
    mobileOnlyActionButton = false,
    disableCheckboxes = true,
    shortcut = 'Ctrl-alt-t',
    persistent = true,
    enabledIcon = 'edit',
    disabledIcon = 'eye'
  }
}

config.set {
  actionButtons = {
    -- Your other action buttons here...
    {
      mobile = config.get('readOnly').mobileOnlyActionButton,
      icon = editor.getUiOption("forcedROMode") and config.get('readOnly').enabledIcon or config.get('readOnly').disabledIcon,
      description = editor.getUiOption("forcedROMode") and "Enable edit mode" or "Enable read-only mode",
      run = function()
        editor.invokeCommand("Toggle Read-Only Mode")
      end
    }
  }
}

--
-- Read-only mode implementation

function toggleReadOnlyMode()
  local cursor = editor.getCursor()
  local mode = editor.getUiOption('forcedROMode')

  editor.setUiOption('forcedROMode', not mode)
  editor.rebuildEditorState()
  editor.reloadConfigAndCommands()
  js.window.document.querySelector('.sb-mini-editor .cm-content').contentEditable = mode

  if config.get('readOnly').persistent then
    clientStore.set('readOnly', not mode)
  end

  if mode == false and config.get('readOnly').disableCheckboxes then
    js.window.document.head.insertAdjacentHTML('beforeend',
      '<style id="readonly-checkbox-css">' ..
      '.sb-checkbox input[type="checkbox"] { pointer-events: none; cursor: default; opacity: 0.5; }' ..
      '</style>'
    )
  else
    checkbox_css = js.window.document.getElementById('readonly-checkbox-css')
    if checkbox_css ~= nil then
      checkbox_css.remove()
    end
  end

  editor.moveCursor(cursor, true)
end

command.define {
  name = 'Toggle Read-Only Mode',
  key = config.get('readOnly').shortcut,
  run = function()
    toggleReadOnlyMode()
  end
}

event.listen {
  name = 'system:ready',
  run = function(e)
    if config.get('readOnly').persistent
     
      then
      local readOnlyConfig = clientStore.get('readOnly')
      local readOnlyCurrent = editor.getUiOption('forcedROMode')
  
      if readOnlyConfig and not readOnlyCurrent then
        toggleReadOnlyMode()
      end
    end
  end
}