Giter Site home page Giter Site logo

ubichr's Introduction

UbiChr

UbiChr is "Ubiquity for Chrome" - a revived command line interface that brings lots of useful command shortcuts to your browser.

In particular you can:

  • open a command like window (Ctrl+Space) execute commands with arguments and preview the results
  • open new tabs and pass GET or POST parameters (Wikipedia search, dictionary lookup, translate text)
  • fetch data via ajax, process it and display on extension popup preview (for example: IMDB movie lookup, currency converter)
  • alter current pages (highlight keywords, inverse or remove colors)
  • add your custom JavaScript commands via built-in editor in a syntax similar to original Ubiquity project
  • utilize powerful features of chrome extension (inject scripts, access cookies, use alarms, bulk save and zip urls, filter opened tabs for text or links, ...)

UbiChr is my humble attempt to create Ubiquity alternative for Chrome and Firefox Quantum browsers.

UbiChr in action

Installation

To install use Chrome Web Store https://chrome.google.com/webstore/search/ubichr

How to install dev version

To install latest commited version please follow 'Load the extension' section here https://developer.chrome.com/extensions/getstarted

License & Origins

MIT license

Most of the code is based on http://github.com/cosimo/ubiquity-opera/

Adding commands

You can add your custom commands using built-in editor (CodeMirror) or modify commands.js. The syntax is quite simple and self explanatory. Press Ctrl+Space, type edit and press Enter.

Key objects and functions

The most important object of the extension is CmdUtils. It's a global object providing many helper functions and storing all commands.

The most common function is CmdUtils.CreateCommand(cmd) which parses cmd argument and adds new command to UbiChr. Since this is a programming extension it is advised to both read examples below and browse cmdutils.js.

Basic command:

CmdUtils.CreateCommand({
    name: "example", 
    description: "A short description of your command.",
    author: "Your Name",
    icon: "http://www.mozilla.com/favicon.ico",
    execute: function execute(args) {
        alert("EX:You input: " + args.text, this);
    },
    preview: function preview(pblock, args) {
        pblock.innerHTML = "PV:Your input is " + args.text + ".";
    },
});

Use CmdUtils.CreateCommand(cmd) and provide object with name string and preview and execute functions. The execute function takes argument which is an object containing text property - a single string following command. The preview function also has pblock parameter pointing to popup div for various output.

The args object properties for execute and preview are as follows:

  • text: text passed as argument
  • _selection: true if text is a current selection
  • _cmd: current command structure
  • _opt_idx: selected option (optional), -1 by default
  • _opt_val: value of option element set in data-option-value attribute
  • pblock: the preview element in case it will be updated via execute

Also both preview and execute functions are bound to command definition object before call allowing access other properties or methods via this.

Full command definition

The command definition object (refered also as cmd_struct) can have these properties when calling CmdUtils.CreateCommand(cmd):

property necessary type info
name yes string / array of strings the actuall command name or names
preview string / function if string this will be placed instead of description, function has two arguments first being DOM preview element, second args object
execute function function called when command is executed, takes a single args object as argument
preview/execute yes function one of these functions is necessary for command to actually do something
description string short information on command shown in preview area
author string / object unused feature from original Ubiquity, email or {name,email} object
icon string url to icon/image or unicode or emoji
license string totally unused legacy option
homepage string totally unused legacy option
external bool indicates if command relies on external scripts
builtIn bool true for all built-in commands
help string additional text expanding description
timeout number if set preview/execute functions will be called after this delay, both functions will be saved as preview_timeout/execute_timeout
requirePopup string / array of strings url(s) of necessary scripts loaded before preview is called
require string / array of strings as above but for execute function
test object see Unit testing below

Command with some action

CmdUtils.CreateCommand({
    name: "google-search",
    preview: "Search on Google for the given words",
    execute: CmdUtils.SimpleUrlBasedCommand(
        "http://www.google.com/search?client=opera&num=1&q={text}&ie=utf-8&oe=utf-8"
    )
});

Note that execute is created using CmdUtils.SimpleUrlBasedCommand() the output function will substitute {text} and {location} template literals with actual argument and current tab url.

Getting outside data with async / await

CmdUtils.CreateCommand({
    name: "imdb",
    description: "Searches for movies on IMDb",
    icon: "http://www.imdb.com/favicon.ico",
    preview: async function preview(pblock, {text: text}) {
        pblock.innerHTML = "Searches for movies on IMDb";
        var doc = await CmdUtils.get("http://www.imdb.com/find?q="+encodeURIComponent(text)+"&s=tt&ref_=fn_al_tt_mr" );
        pblock.innerHTML = "<table>"+jQuery("table.findList", doc).html()+"</table>";
    },
    execute: CmdUtils.SimpleUrlBasedCommand("http://www.imdb.com/find?q={text}&s=tt&ref_=fn_al_tt_mr")
});

Here the preview function is defined with async keyword. This will allow to avoid callback hell when getting data with GET request (CmdUtils.get(url)). Note the destructuring assignment singling out the text parameter in preview function. Note: final implementation uses one liner with jQuery.load().

Search command with iframe preview

CmdUtils.makeSearchCommand({
  name: ["qwant"],
  description: "Searches quant.com",
  author: {name: "Your Name", email: "[email protected]"},
  icon: "https://www.qwant.com/favicon-152.png?1503916917494",
  url: "https://www.qwant.com/?q={QUERY}&t=all",
  prevAttrs: {zoom: 0.75, scroll: [100/*x*/, 0/*y*/], anchor: ["c_13", "c_22"]},
});

The CmdUtils.makeSearchCommand() (provided by Sebres) simplifies even more common web fetching. Instead of loading part of HTML and parsing it with JQuery an iframe is created in UbiChr results area. Extra parameters allow to scale and translate it.

Commands with options

Version 0.1.0.16 adds options inside preview. To define them just mark any DOM element with data-option attribute and optional data-option-value. Once preview is shown you can navigate through options using Ctrl+up or Ctrl+down keys. Executing command with Enter will pass extra properties into args object. Here's a brief example:

CmdUtils.CreateCommand({
    name: "optionexample",
    execute: function execute(args) {
      	CmdUtils.setTip("chosen option idx:"+args._opt_idx+" chosen option val:"+args._opt_val);
    },
    preview: function preview(pblock, args) {
        pblock.innerHTML  = "<div data-option data-option-value=one>option 1</div>";
        pblock.innerHTML += "<div data-option data-option-value=two>option 2</div>";
        pblock.innerHTML += "<div data-option data-option-value=thr>option 3</div>";
        pblock.innerHTML += "<div data-option data-option-value=fou>option 4</div>";
        pblock.innerHTML += "<div data-option data-option-value=fiv>option 5</div>";
    },
});

A more advanced example is IMDB movie lookup command. Unnecessary elements were removed for clarity.

CmdUtils.CreateCommand({
    name: "imdb",
    preview: async function define_preview(pblock, {text: text}) {
        pblock.innerHTML = "Searches for movies on IMDb";
        if (text.trim()!="") 
        jQuery(pblock).loadAbs("http://www.imdb.com/find?q="+encodeURIComponent(text)+"&s=tt&ref_=fn_al_tt_mr table.findList", ()=>{
            jQuery(pblock).find(".findResult").each((i,e)=>{
                jQuery(e).attr("data-option","");
                jQuery(e).attr("data-option-value", jQuery(e).find("a").first().attr("href"));
            });
        });
    },
    execute: function execute(args) {
        var opt = args._opt_val || "";
        if(opt.includes("://")) 
            CmdUtils.addTab(opt);
        else {
            execute = CmdUtils.SimpleUrlBasedCommand("http://www.imdb.com/find?s=tt&ref_=fn_al_tt_mr&q={text}");
            execute(args)
        }
    }
});

Inside preview after the results are loaded jQuery is used to iterate over all .findResult elements and mark them with data-option attribute. Also data-option-value is set with URL.

The execute function check if option was set and if it is an URL (includes ://). If so another browser tab is added. In case it was not defined standard tab with search results is opened.

Custom option selection event

Selecting options triggers custom 'data-option-selected' event passed to selected element. In example below command searches Emojipedia for glyphs based on decription. Results are filtered and shown as a preview. Finally each of them is attributed with 'data-option'. Also, a custom event handler (final line) is attached that copies single glyph/emoji to clipboard.

CmdUtils.makeSearchCommand({
    name: ["emoji"],
    description: "Search Emojipedia",
    icon: "https://emojipedia.org/static/img/favicons/favicon-32x32.png",
    execute: CmdUtils.SimpleUrlBasedCommand("https://emojipedia.org/search/?q={text}"),
    url: "https://emojipedia.org/search/?q={QUERY}",
    timeout: 250,
    preview: function preview(pblock, args) {
        if (args.text === "")
            pblock.innerHTML = "enter EMOJI description";
        else {
            pblock.innerHTML = "";
          	jQuery(pblock).loadAbs("https://emojipedia.org/search/?q=" + encodeURIComponent(args.text)+ " div.content", ()=>{
              pblock.innerHTML = "<font size=12>"+jQuery("span.emoji", pblock).map((i,e)=>e.outerHTML).toArray().join("")+"</font>";
              jQuery("span", pblock).each((i,e)=>{
                jQuery(e).attr("data-option","");
                jQuery(e).attr("data-option-value", jQuery(e).find("a").first().attr("href"));
                jQuery(e).on("data-option-selected", e=>CmdUtils.setClipboard($(e.target).html()) ); // custom event handler
              });
            });
        }
    }
});

Open tab, post a form and dodge anti CSRF token

The example below opens an URL and also fills a form than is finally submitted. This particular approach is suitable for to create a shortcut to all non-standard pages operating on forms with parameters passed with POST and some kind of CSRF protection (token, cookie, etc).

CmdUtils.CreateCommand({
    name: "post-form-shortcut",
    execute: function execute(args) {   
      var q = args.text; 
      CmdUtils.backgroundWindow.eval( `
	   var callback = (tab) => {
            chrome.tabs.executeScript( tab.id, { code: \`
              document.querySelector("input[name='inputfield']").value = "${q}";
			  document.querySelector("button[name='submitbutton']").click();
			\`} );
          	chrome.tabs.onCreated.removeListener( callback );
       };
       chrome.tabs.onCreated.addListener( callback );
       `);
       CmdUtils.addTab("https://site/formurl");
    },
});

UbiChr command creates internal chrome listener that is fired upon new tab being opened. The listerer callback executes JS into a page that fills the form and submits it and finally removes the callback. Please note, that the example above has no tab url check in place.

Commands that inject external JavaScript

Build 1.0.0.31 includes two commands that rely on external sources. These are allow-text-selection that removes anti selection by overriding user-select: none CSS style, and grayscale (or greyscale) which just disables colors. Both commands inject JS source taken from CDN and as a precaution include external: true attribute. Be aware that the CDN provided source may change anytime.

The intention here is to convert simple bookmarked javascript into typical commands. The author of the original JS scripts or the bookmarklets is Alan Hogan.

CmdUtils.CreateCommand({
    name: "allow-text-selecion",
    author: "Alan Hogan",
    icon: "Ꮖ",
    external: true,
    description: "Allows text selection by undoing user-select:none CSS rules.",
    homepage: "https://alanhogan.com/bookmarklets",
    execute: function execute(args) { CmdUtils.inject("https://cdn.jsdelivr.net/gh/alanhogan/bookmarklets/enable-text-selection.js"); },
});

CmdUtils.CreateCommand({
    name: ["grayscale","greyscale"],
    author: "Alan Hogan",
    icon: "🎨",
    external: true,
    description: "Removes colors.",
    homepage: "https://alanhogan.com/bookmarklets",
    execute: function execute(args) { CmdUtils.inject("https://cdn.jsdelivr.net/gh/alanhogan/bookmarklets/grayscale.js"); },
});

Update handlers

The background script runs CmdUtils.updateActiveTab() method on couple of chrome's events (onUpdated,onActivated,onHighlighted). Version 0.1.0.32 adds possibility to attach/remove your custom handlers to CmdUtils.updateActiveTab(). This is managed via:

  • CmdUtils.addUpdateHandler(name, handler) which adds named handler function to CmdUtils.updateHandlers array, note that handler must be a function
  • CmdUtils.removeUpdateHandler(name) which removes named handler from
  • CmdUtils.updateHandlers which is an array of {name,handler} objects In particular the new highlight/mark command uses these methods to add permanent highlighting of chosen keywords.

Sharing Commands

All commands can be shared with an experimental command-gist name statement. This will open gist.github.com and fill in the form assuming you are already logged as GitHub user. It is up to you to decide if the source should be sercret or public.

To search for commands shared by others go to https://gist.github.com/search?q=ubichr. For now commands can be added only by manual cut&paste of the source to the editor window.

In case you just want to share command via other means execute command-source name to copy source to clipboard.

Unit testing

UbiChr provides unit testing of individual commands. Most tests rely on one of two checks:

  • checking preview contents (ie: including, starting or being exact pattern)
  • checking if additional tabs were opened

Tests page, accessed via unittests command, allows to run multiple tests and observe the results. In order to understand test one should review tests.js source. Basically a unit test is defined as object with properties in table below. It is either accessed as test property of cmd_struct or element of tests array in tests.js.

property necessary type info
name yes / no string same as ubichr command name (can be skipped if test is included in command)
args string command arguments
text string expected preview block .innerText result
starsWithText string preview block .innerText is expected to start with this string
includesText string preview block .innerText is expected to include this string
html string expected preview block .innerHTML result
includesHTML string preview block .innerHTML is expected to include this string
url string / array of strings expected open tab(s) with this url(s), syntax is compatible with chrome.tabs.query url option parameter with wildcards
exec boolean true if command should be executed
timeout number / ms check is perfomed after this delay
init(window) function custom function executed when testing is started, before timeout!
test(window) function custom test function, should return true if OK
exit(window) function custom function executed after test is complete (ie. for cleanup)

attributes added automatically:

property necessary type info
pass(message) function called on test sucess
fail(message) function called on test failure
result string will contain 'pass' or 'fail'
el dom node node element with test status (see tests.js)

Lest see some very basic example:

{
    name: 'calc',       // this test is for calc command
    args: '2+2',        // arguments is 2+2
    text: '4',          // preview should be set to 4
    timeout: 1000,      // test is performed 1 second after popup is opened
}

Once testing is started for each command a separate tab is opened and proper input is entered, if necessary command is executed. CmdUtils.onPopup() handler is executed that handles all the testing. Beware that in order to prevent race condition of previous command preview asynchronous load the CmdUtils.loadLastInput is set to false. This results with UbiChr testing tabs no showing last command by default.

Keyboard shortcuts

Command popup

  • Enter - execute current command
  • Shift+Enter - execute to inactive tab
  • Ctrl+C - copy preview output to clipboard
  • F5 - reload the extension
  • ↑ / ↓ - select command suggestion
  • Tab - expand current suggestion to default command name
  • Space - remove command parameters; works only when input is fully selected (useful for calling last command with different parameter)
  • Ctrl+↑ / ↓ - select preview option
  • Ctrl+R / Alt+F8 - shows command history, enter parameter to filter results
  • Ctrl+P / Ctrl+E - previous command
  • Ctrl+N / Ctrl+X - next command

Scripts editor

  • Ctrl-/ - comment block of code
  • Ctrl-S - save scripts to file
  • Ctrl-F - search
  • Ctrl-G - go to next search location

Alternatives

Svalorzen has forked UbiChr and created UbiShell which has more shell like UI with piping and command options. Check it out here: https://github.com/Svalorzen/UbiShell

Privacy policy

As Google requests privacy policy here's one. Do not worry though, UbiChr doesn't collect any of your data.

ubichr's People

Contributors

rostok avatar sebres avatar svalorzen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

ubichr's Issues

`urban` command gives empty output

As the title says, when I try to put in any word, it gives empty output. I made my own version, with this as the contents of the preview function, which does work:

  if (text.trim() == '')
      return;
   	
    pblock.innerHTML = `<style>
  h2 {
    margin-top: 0.5em;
    margin-bottom: 0;
    padding: 0;
  }
  p { margin: 0 }
  blockquote {
    margin: 0.5em 0 1.5em;
    font-style: italic;
  }
</style>`;
    $(pblock).append(`Urban Dictionary entries for “${text}”:<br />`);
    CmdUtils.get(`https://www.urbandictionary.com/define.php?term=${text.replace(" ","+")}`, r => {
      $(r).find('div.definition > div').each((_,e) => {
        var term = $(e).find('h1, h2').text(),
            def  = $(e).find('.meaning').text(),
            ex   = $(e).find('.example').text(),
            //uv   = $(e).find('*[data-x-bind=thumbUp]').text(),
            //dv   = $(e).find('*[data-x-bind=thumbDown]').text(),
            date = $(e).find('.contributor').text().split(' ').slice(-3).join(' ');
        $(pblock).append(`<h2>${term}</h2>`);
        $(pblock).append(`<p>(${date}) ${def}</p>`);
        $(pblock).append(`<blockquote>${ex}</blockquote>`);
      });
    });```

Command name has a typo

The command allow-text-selecion is surely supposed to be spelled allow-text-selection, but there is a typo in the code (Line 1911).

Command Line Syntax for Ubiquity

Following our discussion via mail, I thought of making an issue so more people can contribute their ideas and we can also try to decide what the future of the project will be.

The thing is that the old paradigm used by Ubiquity (the natural language ideas) is probably not really useful anymore, and simply complicates things.

In addition, it makes very hard to devise commands that can become composable, such for example being able to grep in a webpage and pipe the result to a search/map command.

Thus, an idea would be to allow commands to explicitly list options (instead of relying on the hardcoded ones present in the old Ubiquity), and to allow commands to send/receive tagged data to each other in order to pass data to other commands.

This basically contains two problems: how to handle the parsing of a command, and what output it should generate, and how to handle the passing of data from a command to another.

For the first problem, me and @rostok had a brief discussion about what the output of a parse could look like.

For an hypothetical "directions" command, defined with options "from", "to", and "time == now" (default), the output of a parse of the line

directions test1 -from 'Sao Paulo' test2 -to 'London' -to 'Milan' test3 -random test4

Would be:

    var directObj = {
        text: “directions test1 -from 'Sao Paulo' test2 -to 'London' -to 'Milan' test3 -random test4”,
        command: "directions",
        input: "test1 test2 test3 -random test4", // random was not a defined flag so counts as input. all inputs around the text are merged
        from: “Sao Paulo”,
        to: [“London”, "Milan"],
        time: "now", // default value
        args: [“from”, “to”, “time”] // time is last since defaulted
    };

This object would then be passed to the code of the command, which would do its job. Options could also be specified with types, so that if a type does not match (number, string, list), the command parsing fails.

About the ability to pipe this data to another command, it becomes harder, because the other command would need to be able to interpret the input data. This can quickly become a N^2 problem unless we can devise a common passing scheme.

An idea for this could be to allow the user to manually redirect the fields from a command to another. So maybe if there was a command to find a restaurant, and we wanted directions to that, we could do:

yelp restaurant | directions -from here -to {location}

Where the yelp command returned a structure containing a location field. The user would need a way to inspect the output structure of a command when piping in order to know how to mesh commands together.

Ctrl+Space don't work

As the title says, I can't launch the app with the ctrl+space command, I try in both my laptop and pc (fresh install of chrome) with the extension pinned and unpinned from the bar and nothing happend, did I miss anything?

Polishing Ubichr

As per TODO, this should be the first thing to do. Once the project is polished, we can safely start reworking the architecture knowing that we're not missing anything.

I've started looking around the code in popup.js, and there's a couple of globals that can be modified and keep state of the command line. I'd prefer these to go if possible, and to try and keep each function as pure as possible. Stateful functions/objects are not a problem, but keeping state in globals is just something that I'd prefer not to be done if one wants to safely reason about the code.

It would also be nice to be able to maybe review the code together so we can decide how we want to structure it -- so I don't spend time on a PR which makes no sense. If that's not possible, I'd suggest creating issues to discuss a possible change before doing it, otherwise we're going to be modifying the code by ourselves without any coordination.

So in the end I'd say we could start creating issues about things we see that should be done for the cleanup and link them to this issue. This would allow to track how things progress, and when we should call the cleanup done.

custom commands

Add custom command (from command editor) to context menu bug the ubiquitwe context menu list.
firefox 69.0.1, windows 10, x64

Firefox does not read selected text

I'm trying out running commands with text selected, but it doesn't seem to do anything in Firefox.

Actually, I was also trying to get the underlying document, but it seems it is empty. CmdUtils.backgroundWindow also seems to have an empty page or something?

Not sure whether it is related.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.