Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LOVE this! #1

Open
sottey opened this issue Sep 2, 2023 · 8 comments
Open

LOVE this! #1

sottey opened this issue Sep 2, 2023 · 8 comments

Comments

@sottey
Copy link

sottey commented Sep 2, 2023

I have been working on a Slack-like forward slash functionality that does something similar. Your idea is MUCH more intuitive.

One note that I didn't see in the instructions:

When you first enter Trilium, you must execute the code (click on the running man icon in the note) for it to respond to the key combo or swipe down. To address this, one can just add the attribute "#widget" to the palette command code note.

I have structured it like this:
image

A container note, then a code note holding the widget and a Command note which contains all my callable notes.

Again, I LOVE this, thank you!
Sean

@sottey
Copy link
Author

sottey commented Sep 2, 2023

One note, I cannot get the text input to filter the available commands. It looks like focus cannot be set on the input text field.

@sottey
Copy link
Author

sottey commented Sep 2, 2023

Apologies, I just realized that the intent of the .searchBox was to capture input, not filter the commands (feature request!). As such, I made a small change for your review that hides the text box while still capturing input (look for SCOCHANGE and SCOADD in showPalette() and addStyles()):

// https://github.com/justyns/trilium-scripts
// This script is meant to be added as a frontend js note.  It adds a simple command palette
// that can execute other script notes.
// To register a note for the command palette, add the following label: cmdPalette
// The value of `cmdPalette` is used as the name/description of the command.
// The palette can be opened by swiping down on mobile, or pressing cmd+shift+p / ctrl+shift+p on desktop.

// Note:  This is very experimental right now

let paletteKeydownHandler;

async function getAvailableCommands() {
  const cmdNotes = await api.getNotesWithLabel('cmdPalette');
  return cmdNotes.map(n => ({ id: n.noteId, name: n.getLabelValue('cmdPalette') }));
}

function updateSelectedCommand(selectedIndex, palette) {
  const commandItems = Array.from(palette.getElementsByClassName('command-item'))
    .filter(item => item.style.display !== 'none');
    
  selectedIndex = (selectedIndex + commandItems.length) % commandItems.length;
  
  commandItems.forEach((item, index) => {
    item.classList.toggle('selected', selectedIndex === index);
  });
  return selectedIndex;
}

async function executeCommand(commandObj) {
  const note = await api.getNote(commandObj.id);
  note ? await note.executeScript() : console.log('Note not found.');
}

function createPaletteElement() {
  const palette = document.createElement('div');
  palette.id = 'commandPalette';
  palette.className = 'command-palette';
  document.body.appendChild(palette);
  return palette;
}

async function showPalette(commands, palette) {
  console.log('Inside showPalette:', commands);
  palette.innerHTML = '';
  let selectedIndex = 0;

  paletteKeydownHandler = async function(e) {
    if (e.key === 'ArrowDown') {
      selectedIndex++;
    } else if (e.key === 'ArrowUp') {
      selectedIndex--;
    } else if (['Enter', 'Escape'].includes(e.key)) {
      if (e.key === 'Enter') await executeCommand(commands[selectedIndex]);
      palette.style.display = 'none';
      palette.removeEventListener('keydown', paletteKeydownHandler);
      return;
    }
    e.preventDefault();
    selectedIndex = updateSelectedCommand(selectedIndex, palette);
    
  }
    
  palette.addEventListener('keydown', paletteKeydownHandler);

  const searchBox = document.createElement('input');
  searchBox.type = 'text';
  searchBox.placeholder = 'Search commands...';
  searchBox.className = 'search-box';
  searchBox.oninput = () => updateSelectedCommand(selectedIndex, palette);

  //SCOADD:
  const searchDiv = document.createElement('div');
  searchDiv.className = 'search-div';
  searchDiv.appendChild(searchBox);
  
  // SCOCHANGE:
  // palette.appendChild(searchBox);
  palette.appendChild(searchDiv);

  const commandContainer = document.createElement('div');
  commandContainer.className = 'command-container';
  commands.forEach((command, index) => {
    const item = document.createElement('div');
    item.className = 'command-item';
    item.textContent = command.name;
    item.onclick = async () => {
      await executeCommand(command);
      palette.style.display = 'none';
      palette.removeEventListener('keydown', paletteKeydownHandler);
    };
    item.classList.toggle('selected', selectedIndex === index);
    commandContainer.appendChild(item);
  });
  palette.appendChild(commandContainer);
  palette.style.display = 'block';
  setTimeout(() => searchBox.focus(), 0);
}

function addStyles() {
  const style = document.createElement('style');
  style.innerHTML = `
.command-palette {
  display: none;
  position: fixed;
  top: 20%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 75%;
  max-width: 100%;
  background-color: #1E1E1E;
  color: white;
  border: 1px solid #3C3C3C;
  border-radius: 4px;
  padding: 10px;
  z-index: 9999;
}

@media screen and (max-width: 768px) {
  .command-palette {
    width: 100%; /* full width on mobile screens */
    left: 0;
    transform: translate(0, -50%);
  }
}

.command-container {
  max-height: 200px;
  overflow-y: scroll;
}
.command-item {
  padding: 10px;
  cursor: pointer;
  border-bottom: 1px solid #3C3C3C;
}
.command-item.selected {
  background-color: #3C3C3C;
}

/* SCOADD: */
.search-div {
    width: 0;
    overflow: hidden;
}
`;
  document.head.appendChild(style);
}

function init() {
  addStyles();
  const palette = createPaletteElement();
  console.log('Palette element:', palette);

  document.addEventListener("keydown", async function(e) {
    if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.code === "KeyP") {
      const commands = await api.runOnBackend(getAvailableCommands, []);
      await showPalette(commands, palette);
    }
  });
    
    let startY, startX;
    let disablePullToRefresh = false;

    document.addEventListener('touchstart', function(e) {
      startY = e.touches[0].clientY;
      if (startY < 100) {  // 100 pixels from the top
        // Only set startX if the swipe starts near the top
        startX = e.touches[0].clientX;
        disablePullToRefresh = true;
      } else {
        startX = null;  // Reset startX to prevent unwanted swipes
        disablePullToRefresh = false;
      }
    }, false);
    
    document.addEventListener('touchmove', function(e) {
      if (disablePullToRefresh) {   
        e.preventDefault(); // may prevent pull-to-refresh
      }
    }, { passive: false });

    document.addEventListener('touchend', async function(e) {
      if (startX === null) return;  // Ignore if swipe didn't start near the top

      let endX = e.changedTouches[0].clientX;
      let endY = e.changedTouches[0].clientY;

      const dx = startX - endX;
      const dy = startY - endY;

      if (Math.abs(dy) > Math.abs(dx) && Math.abs(dy) > 100) {
        if (dy < 0) {
          console.log('Swipe down detected');
          if (window.navigator && window.navigator.vibrate) {
            navigator.vibrate(100);  // vibrate for 100 ms
          }
          const commands = await api.runOnBackend(getAvailableCommands, []);
          await showPalette(commands, palette);
        }
      }
    }, false);

}

// Initialize the script
init();

justyns added a commit that referenced this issue Sep 2, 2023
@justyns
Copy link
Owner

justyns commented Sep 2, 2023

Thanks for the feedback @sottey ! I'd be interested in if you get the slash command thing working as well, I had thought about doing something like that first but it seemed more complicated even though ckeditor seems to have some built-in support for it.

  • I added a comment about adding the #run=frontendStartup #run=mobileStartup labels to auto execute the script. I haven't tried the widget label yet though, does that behave differently?
  • The input box was actually supposed to filter commands but I broke it 😅 . I just pushed a fix for it! There was a e.preventDefault(); in the wrong spot so it wasn't allowing anything but enter and the arrow keys.
  • Thanks for posting how you're organizing it, I'll probably borrow that idea too

@sottey
Copy link
Author

sottey commented Sep 3, 2023

Slash functionality: Still working on it, funny enough, the slash part was the easiest pat, it was getting a context menu to pop up in the right place that was problematic. Tying it into your awesome code makes it that much easier!

#widget: TBH, I am not sure how #widget differs from #run=frontEndStartup. I will play around with that and see if there are distinctions

input box: Super cool! That feature will be incredibly useful. Grabbing the update now.

UPDATE: The text filtering works great, the only issue is that the foreground text is the same color as the background.

image

Thanks again,
Sean

@justyns
Copy link
Owner

justyns commented Sep 3, 2023

UPDATE: The text filtering works great, the only issue is that the foreground text is the same color as the background.

Good catch! I didn't think about the theming. It looks like you're using a light theme, I had picked the colors based on the dark theme I was using. I'm looking into re-using some of the code for the Jump-to menu. If I can get that to work, I think it'll be better since it'd use the colors from the theme you have selected

@justyns
Copy link
Owner

justyns commented Sep 9, 2023

Just to double check, @sottey does the latest version fix the issues you had with the text colors? It should be using the theme colors now

@sottey
Copy link
Author

sottey commented Sep 9, 2023

It totally does!

Also, regarding the #widget vs. #run=frontEndStartup: it looks like unexpected things happen when you use widget for things that it isn't intended for. I am still a little unclear on it, but there is a discussion here that is a start.

zadam/trilium#4248

@zerebos
Copy link

zerebos commented Sep 11, 2023

Also, regarding the #widget vs. #run=frontEndStartup: it looks like unexpected things happen when you use widget for things that it isn't intended for. I am still a little unclear on it, but there is a discussion here that is a start.

A #widget should set module.exports with an instance of a class extending BasicWidget in some way. The other exported Widget types already extend BasicWidget. It is mainly used for UI, as the base class would suggest, and also shown in the wiki page.

If you don't need the functionality of that widget class or don't want the UI support it has, use #run=frontendStartup instead. Both that and widgets are evaluated at startup, but scripts using #run don't have exports checked for adding the widget to the UI tree.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants