I'm trying to add auto-completions for mermaid diagrams to my editor:
const mermaids = Object.entries({
"mermaid graph": `graph LR\n x --> y`,
}).map(([name, autocompletion]) => ({
caption: name,
meta: name,
value: "``mermaid\n" + autocompletion + "\n```"
}));
aceeditor.setOptions({
enableBasicAutocompletion: [{
getCompletions: (editor, session, pos, prefix, callback) => {
callback(null, [
...mermaids
])
}
}],
enableSnippets: false,
enableLiveAutocompletion: true
});
In the resulting editor, if the user types "graph" or "mermaid" and hits enter to auto-complete, it works as expected. (With the exception of less-than desirable cursor position after the completion.) If the user types "```" and hits enter, the autocompletion occurs after the originally typed "```". E.g.,
``````mermaid
graph LR
x --> y
\``` <-- just escaped here for SO's sake
Is there an efficient way to correct this? If not, what event can I use to determine when an auto-completion has actually occurred and search for duplicate markers?
Is there a better way to do this in general?
I think the broader issue here is that your completion item more closely resembles a snippet. Using snippets would also give you better control over where the cursor goes after insertion.
Answering your second question, Autocomplete.insertMatch is the function responsible for inserting the chosen completion item. You could hook it, or perhaps use the mysteriously undocumented .completer field on the completion items? It's been there for 9 years now, perhaps it is not an accident.
const FakeCompleter = {
insertMatch: (editor, data) => {
// stolen from default insertMatch, needed to erase text that triggered our completion:
if (editor.completer.completions.filterText) {
var ranges = editor.selection.getAllRanges()
for (var i = 0, range; range = ranges[i]; i++) {
range.start.column -= editor.completer.completions.filterText.length
editor.session.remove(range)
}
}
// if there are ` symbols immediately before cursor, omit those from completion:
let text = (data.value || data)
const lead = editor.selection.lead
const prefix = editor.session.getLine(lead.row).substring(0, lead.column)
const mt = /.*?(`{1,3})$/.exec(prefix)
if (mt && text.startsWith(mt[1])) text = text.substring(mt[1].length)
// and finally call the regular insertion:
editor.execCommand("insertstring", text)
}
}
const mermaids = Object.entries({
"mermaid graph": `graph LR\n x --> y`,
}).map(([name, autocompletion]) => ({
caption: name,
meta: name,
value: "``mermaid\n" + autocompletion + "\n```",
completer: FakeCompleter
}));
Related
I am getting my head around Vee-Validate next (v4) and how I might incorporate it in a Vue 3 project without loosing Vue's reactivity (i.e. not relying on the values simply being passed to the Form submit event).
By way of example, if I were making a hypothetical component which has autocomplete functionality, and sent a get request to the server once 3 letters had been typed, but for the input itself to be valid it required 8 letters, how would I get the value associated with the input?
using plain Vue, with pseudo-code something like:
defineComponent({
setup () {
const myVal = ref('')
const options = ref([])
watchEffect(() => if (myVal.value.length > 3) {
axios.get(...).then(serverVals => options.value = serverVals))
})
return { myVal, options }
how would I achieve this with vee-validate 4.x?
defineComponent({
setup () {
const schema = yup.object({ myVal: yup.string().required().min(8) })
// ???? what now to watch myVal
please note this is not about autocomplete - a range slider where I wanted a server call when the value was greater than 10 but a validation message if greater than 90 would also suffice as an example.
You could employ useField here to get a reactive value that's automatically watched.
const { value: myVal, errorMessage } = useField('myVal', undefined, {
initialValue: ''
});
const options = ref([])
watchEffect(() => if (myVal.value.length > 3) {
axios.get(...).then(serverVals => options.value = serverVals))
})
return { myVal, options }
Documentation has an example of using useField:
https://vee-validate.logaretm.com/v4/guide/composition-api#usefield()
Note that you don't have to use useForm, if you are using <Form> component and passing schema to it then that should work just fine.
I have a group for which elements after reduction look like this pseudocode :
{
key:"somevalue",
value: {
sum: the_total,
names:{
a: a_number,
b: b_number,
c:c_number
}
}
}
In my dc-js geoChoropleth graph the valueAccessor is (d) => d.value.sum
In my title, I would like to use the names component of my reduction. But when I use .title((d) => {...}), I can onjly access the key and the value resulting from the valueAccessor function instead of the original record.
Is that meant to be ?
This is a peculiarity of the geoChoropleth chart.
Most charts bind the group data directly to chart elements, but since the geoChoropleth chart has two sources of data, the map and the group, it binds the map data and hides the group data.
Here is the direct culprit:
_renderTitles (regionG, layerIndex, data) {
if (this.renderTitle()) {
regionG.selectAll('title').text(d => {
const key = this._getKey(layerIndex, d);
const value = data[key];
return this.title()({key: key, value: value});
});
}
}
It is creating key/value objects itself, and the value, as you deduced, comes from the valueAccessor:
_generateLayeredData () {
const data = {};
const groupAll = this.data();
for (let i = 0; i < groupAll.length; ++i) {
data[this.keyAccessor()(groupAll[i])] = this.valueAccessor()(groupAll[i]);
}
return data;
}
Sorry this is not a complete answer, but I would suggest adding a pretransition handler that replaces the titles, or alternately, using the key passed to the title accessor to lookup the data you need.
As I noted in the issue linked above, I think this is a pretty serious design bug.
Using the Debut theme in Shopify, I have a product with variants that are not compatible. At present they show as unavailable but I want to hide them totally so that only valid options show with each other. So as example if I have Red, Blue, Green shoes and have sizes 9,10,11. But can only get Red shoes in Size 10. I don't want to see options for 9 or 11 ever.
Online someone pointed to theme.js and the code below, but I'm not sure what to change.
Thanks
$(this.singleOptionSelector, this.$container).on(
'change',
this._onSelectChange.bind(this)
);
}
I've just spend most of the day on this, and have come up with the following, which seems to work nicely on the site I'm developing.
The answer I came up with involves editing the assets/theme.js file. At present, the code below disables the select options which are not relevant by checking them against the available variant combinations, but you could easily adapt the below to hide them and then show them instead with CSS.
assets/theme.js
The _hideUnavailableOptions method below needs to be added to the Variants.prototype object.
You then need to call the method from two different places, see below.
_hideUnavailableOptions: function() {
const option1 = document.getElementById("SingleOptionSelector-0");
const option2 = document.getElementById("SingleOptionSelector-1");
if (option2) {
const secondOptions = option2.options;
const variants = this.product.variants;
let possibles = [];
variants.forEach((variant) => {
if (variant.options.includes(option1.value)) {
possibles.push(variant.options)
}
})
for (let option of secondOptions) {
const value = option.value;
let flag = false;
possibles.forEach((possible) => {
if (possible.includes(value)) {
flag = true;
}
})
if (flag === false) {
option.removeAttribute('selected');
option.setAttribute('disabled', 'disabled');
} else {
option.removeAttribute('disabled');
}
} option2.querySelector(':not([disabled="disabled"])').setAttribute('selected', 'selected');
}
},
Call the method as follows:
function Variants(options) {
//stuff before this, then...
this.enableHistoryState = options.enableHistoryState;
this._hideUnavailableOptions(); //N.B. this MUST be before the next line
this.currentVariant = this._getVariantFromOptions();
}
...and again, call the method from Variants.prototype._onSelectChange() - make sure it's the first line in there...
_onSelectChange: function() {
let hideUnavailable = this._hideUnavailableOptions(); //N.B. this MUST be before the next line
var variant = this._getVariantFromOptions();
//lots more stuff follows...
}
Is there a way in JXA to get multiple properties from multiple objects with a single call?
For example, I want to get name and enabled property from menu items which can be done for each individual property as follows:
Application("System Events").processes.byName('Finder').menuBars[0].menuBarItems.name()
Application("System Events").processes.byName('Finder').menuBars[0].menuBarItems.enabled()
but is it possible to get them with a single function call? Something like:
Application("System Events").processes.byName('Finder').menuBars[0].menuBarItems.select('name', 'enabled')
I know, that I can iterate through the menuBarItems and collect properties from .properties() method, but this approach is too slow, that's why I'm looking for other options.
UPDATE
I'm looking for better performance, not for nicer syntax, i.e. I want properties to be retrieved in a single call to System Events.
I'd probably do it like this:
sys = Application('com.apple.systemevents');
FinderProc = sys.processes['Finder'];
FinderMenuBarItems = FinderProc.menuBars[0].menuBarItems();
Array.from(FinderMenuBarItems,x=>[x.name(),x.enabled()]);
By first converting the object to an array, this allows one to map each element and retrieve the desired properties for all in one go. The code is split over several lines for ease of reading.
EDIT: added on 2019-07-27
Following on from your comment regarding Objective-C implementation, I had a bit of time today to write a JSObjc script. It does the same thing as the vanilla JXA version above, and, yes, it clearly makes multiple function calls, which is necessary. But it's performing these functions at a lower level than System Events (which isn't involved at all here), so hopefully you'll find it more performant.
ObjC.import('ApplicationServices');
ObjC.import('CoreFoundation');
ObjC.import('Foundation');
ObjC.import('AppKit');
var err = {
'-25211':'APIDisabled',
'-25206':'ActionUnsupported',
'-25205':'AttributeUnsupported',
'-25204':'CannotComplete',
'-25200':'Failure',
'-25201':'IllegalArgument',
'-25202':'InvalidUIElement',
'-25203':'InvalidUIElementObserver',
'-25212':'NoValue',
'-25214':'NotEnoughPrecision',
'-25208':'NotImplemented',
'-25209':'NotificationAlreadyRegistered',
'-25210':'NotificationNotRegistered',
'-25207':'NotificationUnsupported',
'-25213':'ParameterizedAttributeUnsupported',
'0':'Success'
};
var unwrap = ObjC.deepUnwrap.bind(ObjC);
var bind = ObjC.bindFunction.bind(ObjC);
bind('CFMakeCollectable', [ 'id', [ 'void *' ] ]);
Ref.prototype.nsObject = function() {
return unwrap($.CFMakeCollectable(this[0]));
}
function getAttrValue(AXUIElement, AXAttrName) {
var e;
var _AXAttrValue = Ref();
e = $.AXUIElementCopyAttributeValue(AXUIElement,
AXAttrName,
_AXAttrValue);
if (err[e]!='Success') return err[e];
return _AXAttrValue.nsObject();
}
function getAttrValues(AXUIElement, AXAttrNames){
var e;
var _AXAttrValues = Ref();
e = $.AXUIElementCopyMultipleAttributeValues(AXUIElement,
AXAttrNames,
0,
_AXAttrValues);
if (err[e]!='Success') return err[e];
return _AXAttrValues.nsObject();
}
function getAttrNames(AXUIElement) {
var e;
var _AXAttrNames = Ref();
e = $.AXUIElementCopyAttributeNames(AXUIElement, _AXAttrNames);
if (err[e]!='Success') return err[e];
return _AXAttrNames.nsObject();
}
(() => {
const pid_1 = $.NSWorkspace.sharedWorkspace
.frontmostApplication
.processIdentifier;
const appElement = $.AXUIElementCreateApplication(pid_1);
const menuBar = getAttrValue(appElement,"AXMenuBar");
const menuBarItems = getAttrValue(menuBar, "AXChildren");
return menuBarItems.map(x => {
return getAttrValues(x, ["AXTitle", "AXEnabled"]);
});
})();
I'm displaying a table with multiple rows and columns. I'm using a JQUERY plugin called uiTableFilter which uses a text field input and filters (shows/hides) the table rows based on the input you provide. All you do is specify a column you want to filter on, and it will display only rows that have the text field input in that column. Simple and works fine.
I want to add a SECOND text input field that will help me narrow the results down even further. So, for instance if I had a PETS table and one column was petType and one was petColor -- I could type in CAT into the first text field, to show ALL cats, and then in the 2nd text field, I could type black, and the resulting table would display only rows where BLACK CATS were found. Basically, a subset.
Here is the JQUERY I'm using:
$("#typeFilter").live('keyup', function() {
if ($(this).val().length > 2 || $(this).val().length == 0)
{
var newTable = $('#pets');
$.uiTableFilter( theTable, this.value, "petType" );
}
}) // end typefilter
$("#colorFilter").live('keyup', function() {
if ($(this).val().length > 2 || $(this).val().length == 0)
{
var newTable = $('#pets');
$.uiTableFilter( newTable, this.value, "petColor" );
}
}) // end colorfilter
Problem is, I can use one filter, and it will display the correct subset of table rows, but when I provide input for the other filter, it doesn't seem to recognize the visible table rows that are remaining from the previous column, but instead it appears that it does an entirely new filtering of the original table. If 10 rows are returned after applying one filter, the 2nd filter should only apply to THOSE 10 rows. I've tried LIVE and BIND, but not working.
Can anyone shed some light on where I'm going wrong? Thanks!
The uiTableFilter plugin doesn't support what you're trying to do. A quick look at the source reveals this:
elems.each(function(){
var elem = jQuery(this);
jQuery.uiTableFilter.has_words(getText(elem), words, false)
? matches(elem)
: noMatch(elem);
});
and that expands to (essentially) this:
elems.each(function(){
var elem = jQuery(this);
jQuery.uiTableFilter.has_words(getText(elem), words, false)
? elem.show()
: elem.hide();
});
So all it does is spin through all the rows, .show() those that match, and .hide() those that don't; uiTableSorter doesn't pay attention to the current shown/hidden state of the rows and there's no way to tell it to filter on multiple columns.
If you really need your desired functionality then you can modify the plugin's behavior (the code is pretty small and simple) or just write your own. Here's a stripped down and simplified version that supports multiple filters and is a more conventional jQuery plugin than uiTableFilter:
(function($) {
$.fn.multiFilter = function(filters) {
var $table = $(this);
return $table.find('tbody > tr').each(function() {
var tr = $(this);
// Make it an array to avoid special cases later.
if(!$.isArray(filters))
filters = [ filters ];
howMany = 0;
for(i = 0, f = filters[0]; i < filters.length; f = filters[++i]) {
var index = 0;
$table.find('thead > tr > th').each(function(i) {
if($(this).text() == f.column) {
index = i;
return false;
}
});
var text = tr.find('td:eq(' + index + ')').text();
if(text.toLowerCase().indexOf(f.word.toLowerCase()) != -1)
++howMany;
}
if(howMany == filters.length)
tr.show();
else
tr.hide();
});
};
})(jQuery);
I'll leave error handling and performance as an exercise for the reader, this is just an illustrative example and I wouldn't want to get in the way of your learning. You could wire it up something like this:
$('#type').keyup(function() {
$('#leeLooDallas').multiFilter({ column: 'petType', word: this.value });
});
$('#color').keyup(function() {
$('#leeLooDallas').multiFilter([
{ column: 'petType', word: $('#type').val() },
{ column: 'petColor', word: this.value }
]);
});
And here's a live example (which assumes that you're going to enter something in "type" before "color"): http://jsfiddle.net/ambiguous/hdFDt/1/