I'm trying to drop image from outside of draft-js editor but it's always inserted at last position of the cursor/selection in editor (or at end if cursor/selection not set).
This is my wrap around draft-js-drag-n-drop-plugin
const droppableBlockDndPlugin = {
...blockDndPlugin,
handleDrop: (
selection,
dataTransfer,
isInternal,
{getEditorState, setEditorState}
) => {
const editorState = getEditorState();
const raw = dataTransfer.data.getData('text');
const data = raw ? raw.split(IMAGE_BLOCK_TYPE_SEPARATOR) : [];
if (data.length > 1 && data[0] === IMAGE_BLOCK_TYPE_PURE) {
const url = data[1];
if (url) {
const newState = imagePlugin.addImage(editorState, url);
setEditorState(newState);
}
}
return blockDndPlugin.handleDrop(selection, dataTransfer, isInternal, {
getEditorState,
setEditorState
});
}
};
Basically I'm just doing extra logic before base handleDrop occurs where I insert image using imagePlugin.addImage. Is there way to drop image to dragged position?
Actually it was quite obvious solution - you should just use passed selection and create new state with it and then add image to that new state:
const newState = imagePlugin.addImage(EditorState.forceSelection(editorState, selection), url);
setEditorState(newState);
Related
I am using quickpick to display list of items and allowing user to do multiselect. I am using quickpick like this.
context.subscriptions.push(vscode.commands.registerCommand('command', async () => {
const list = await vscode.window.showQuickPick(filedsList, { ignoreFocusOut: true, canPickMany: true});
}));
I trying to make like if user previously selected some items in the quick pick and next time use opened this quick pick I want to preselect the previously selected values.
Is it feasible to do it in vscode extension development.
There are two options. In both cases you'll have to make QuickPickItems out of your AccurevOperations.cpkFieldsList as you cannot just pass strings as the first argument and use the pre-selection capability. It isn't difficult to do this, just loop through that AccurevOperations.cpkFieldsList array and create objects with the QuickPickItem properties you want, like:
const arr = ["1", "2", "3"];
// const quickPickItems = arr.map(item => ( { label: item, picked: true } ) ); // but this would select them all
const quickPickItems = arr.map(item => {
if (Number(item) % 2 != 0) return { label: item, picked: true };
else return { label: item }
});
So you would use your logic to set the items you want pre-selected to have the picked:true object property. Then
const options = { canPickMany: true };
const qp = vscode.window.showQuickPick(quickPickItems, options);
should show a QuickPick with your selected items.
The other option is to use the createQuickPick method instead, because with that you can use its selectedItems property. So starting with your array:
const arr = ["1", "2", "3"];
const quickPickItems = arr.map(item => ( { label: item } ) ); // and whatever other properties each item needs, perhaps nothing other than label
const qp = vscode.window.createQuickPick();
qp.canSelectMany = true;
qp.items = quickPickItems;
qp.selectedItems = [quickPickItems[0], quickPickItems[2]]; // your logic here
qp.show();
You will have to create an array of objects to assign to the selectedItems property. Perhaps by filtering the quickPickItems array (from above) for the labels you want pre-selected:
qp.selectedItems = quickPickItems.filter(item => {
return item.label === "1" || item.label === "3";
});
the object vscode::QuickPickItem has a property
picked
Optional flag indicating if this item is picked initially.
Preface:
I am aware that there is a duplicate question out there. I am posting it again because it has no answers (and it's from 4 years ago).
General description of what I want:
I want to be able to hide a DOM-element (adding Element.style.display = "none") before the DOM is loaded into the view.
What I've tried:
Other posts point to using a MutationObserver and running it on the document element.
To ensure that we are able to hide an element before the DOM is loaded, we are to run the script containing the MutationObserver as a content_script with "run_at":"document_start".
I did all of this, and I still see a flicker (the elements appear when I load a page and then quickly disappear).
What I'm trying to do:
There's a ul which contains some li with some text on the page I inject my content_script.js into. I populate my popup.html with <text, checkbox> pairs. If the checkbox is checked, the li containing said text is visible, else it is hidden. I want it to persist between refreshes, hence the use of storage.
Things work - but there's a flicker whenever I refresh the page. The elements are there, then they're gone. I don't want them to show up in the first place!
My code:
When I detect that the DOM elements I may remove have loaded, I generate an Object that indicates whether I should hide or keep visible that specific DOM element.
I then set its Element.style.display to none or block accordingly.
/**manifest.json
...
"content_scripts": [
{
"matches": [
"some_website_url"
],
"js": [
"content_script.js"
],
"run_at": "document_start"
}
]
...
*/
///content_script.js
const mutationObserver = new MutationObserver((mutations) => {
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
if (node.tagName) {
if (node.querySelector(potentially_hidden_element_selector)) {
chrome.storage.sync.get("courses", ({ courses }) => {
chrome.storage.sync.set({ "courses": generateCourseList(courses) }, () => {
const courseElements = Array.from(node.closest('ul').querySelectorAll('a[data-parent-key="mycourses"]'))
courseElements.forEach(courseElement => {
const courseName = getCourseName(courseElement)
const isVisible = courses[courseName]
updateCourseElementInSidebar(courseElement, isVisible)
})
})
})
// We found what we were looking for so stop searching
mutationObserver.disconnect()
}
}
}
}
})
mutationObserver.observe(document, { childList: true, subtree: true })
EDIT 1:
My generateCourseList method depends on the DOM elements I may try to hide - so I can't call the chrome.storage.set method before the DOM has loaded I think.
When I refresh the page, a list of courses eventually populates the DOM.
I then populate the storage's courses object based on these course elements' innerText properties. I set each of these elements' visibility to true or false based on one of two factors: if this course is already defined in the courses object, keep its visibility status, if it isn't, set it to true (visible by default).
I can't make certain DOM elements visible/hidden if I don't have reference to them though. So if I try to call generateCourseList before those specific DOM elements have loaded, I end up trying to retrieve all the course elements (document.querySelectorAll('a[data-parent-key="mycourses"]')) and get returned nothing. I end up setting courses in chrome.storage to nothing because of this chrome.storage.sync.set({ "courses": generateCourseList(courses) }....
EDIT 2:
Here is all of my code. I try to chrome.storage.sync.get as soon as I can, and I try to not depend on the result of chrome.storage.sync.set.
I try to delete the elements as soon as I can, but I'm having difficulty doing so. This is because I have difficulty knowing when the content I want to access (the course elements) have fully loaded. Previously, I was detecting when one course element was visible, and when it was, I assumed all were. This was a mistake. I was able to access the one courselement the moment it popped up, but sometimes only 4 of the 6 course elements were actually loaded. I can't hardcode this number, because it changes from person to person. I can't just tackle them one by one, because then I wouldn't know when to disconnect the MutationObserver. I used the debugger and tried to find what element is loaded soon after all 6 course elements are loaded, and that is the header#page-header.row element. I still get a flicker, though less noticeable than before.
Anything I can do to make it even less noticeable?
function start_mutation_observer() {
chrome.storage.sync.get({ 'savedCourses': {} }, ({ savedCourses }) => {
const observer = new MutationObserver((mutations) => {
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
// The page header gets updated AFTER the courseList is updated - so once it's in the page, we know the courseElements are too
if (document.querySelector('header#page-header.row')) {
observer.disconnect()
const generatedCourses = generateCourseList(savedCourses)
const courseElements = getCourseElements()
// Set visibility of course elements
courseElements.forEach(courseElement => {
const courseName = getCourseElementTextContent(courseElement);
const isShown = generatedCourses[courseName];
setCourseElementVisibility(courseElement, isShown);
});
chrome.storage.sync.set({ 'savedCourses': generatedCourses });
return
}
}
}
});
observer.observe(document, { childList: true, subtree: true });
// In case the content script has been injected when some of the DOM has already loaded
onMutation([{ addedNodes: [document.documentElement] }]);
});
}
function getCourseElements() {
const COURSE_ELEMENT_SELECTOR = 'ul > li > a[data-parent-key="mycourses"]'
return Array.from(document.querySelectorAll(COURSE_ELEMENT_SELECTOR))
}
function getCourseElementTextContent(courseElement) {
const COURSE_ELEMENT_TEXT_CONTAINER_SELECTOR = 'a[data-parent-key="mycourses"] > div > div > span.media-body'
return courseElement.querySelector(COURSE_ELEMENT_TEXT_CONTAINER_SELECTOR).textContent
}
function generateCourseList(savedCourses) {
// Turns [[a, b], [b,c]] into {a:b, b:c}
return Object.fromEntries(getCourseElements().map(courseElement => {
const courseName = getCourseElementTextContent(courseElement)
const isShown = savedCourses[courseName] ?? true
return [courseName, isShown]
}))
}
function setCourseElementVisibility(courseElement, isShown) {
if (isShown) {
courseElement.style.display = "block"
} else {
courseElement.style.display = "none"
}
}
start_mutation_observer()
EDIT 3:
I think it's as good as can be now. I only refresh the visibility of the course elements that were just loaded into the DOM. There's essentially no flicker now (there is a slight one, but its' the same amount of flickering without my extension).
Here is the code for the MutationObserver
function start_mutation_observer() {
let handledCourseElements = new Set()
chrome.storage.sync.get({ 'savedCourses': {} }, ({ savedCourses }) => {
const observer = new MutationObserver((mutations) => {
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
const courseElements = getCourseElements()
const courseElementsAdded = courseElements.length > handledCourseElements.size
// If a courseElement was added, update visibility of those that weren't already processed
if (courseElementsAdded) {
const generatedCourses = generateCourseList(savedCourses)
courseElements
.filter(courseElement => !handledCourseElements.has(courseElement))
.forEach(courseElement => {
const courseName = getCourseElementTextContent(courseElement)
const courseShouldBeVisible = generatedCourses[courseName];
setCourseElementVisibility(courseElement, courseShouldBeVisible);
handledCourseElements.add(courseElement)
})
}
// The page header gets updated AFTER the courseList is updated - so once it's in the page, we know the courseElements are too
if (document.querySelector('header#page-header.row')) {
observer.disconnect()
chrome.storage.sync.set({ 'savedCourses': generateCourseList(savedCourses) });
return
}
}
}
});
observer.observe(document, { childList: true, subtree: true });
// In case the content script has been injected when some of the DOM has already loaded
onMutation([{ addedNodes: [document.documentElement] }]);
});
}
Reading storage is slow and asynchronous, so you need to do it at the beginning:
chrome.storage.sync.get('courses', ({ courses }) => {
chrome.storage.sync.set({ 'courses': generateCourseList(courses) });
const observer = new MutationObserver(onMutation);
observer.observe(document, { childList: true, subtree: true });
onMutation([{addedNodes: [document.documentElement]}]);
function onMutation(mutations) {
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
if (node.tagName && node.querySelector(potentially_hidden_element_selector)) {
observer.disconnect();
processNode(node, courses);
}
}
}
}
});
function processNode(node, courses) {
const courseElements = Array.from(
node.closest('ul').querySelectorAll('a[data-parent-key="mycourses"]'));
courseElements.forEach(courseElement => {
const courseName = getCourseName(courseElement);
const isVisible = courses[courseName];
updateCourseElementInSidebar(courseElement, isVisible);
});
}
For a word add-ins in javascript, a simple use case is to get the word on the left of the cursor and to replace it in upper case.
For example, if | is the cursor:
Hello world| will become Hello WORLD|
Hello| world will become HELLO| world
Is it possible to perform this example with the Word.Range class? For example, to expand the range until a space like this fictive code:
Word.run(function (context) {
var selection = context.document.getSelection();
var cursor = selection.getRange('Start');
// Fictive: how to expand the range to the left until a space?
var range = cursor.expandToLeftUntil(' ');
range.load("text");
var html = range.getHtml();
await context.sync();
var textToReplace = html.value.toUpperCase();
// Replace the text
range.insertText(textToReplace, 'Replace');
await context.sync();
});
Or is there any other solution?
A possible strategy is to use the search method to get a RangeCollection of all the words in the document (or body or paragraph, etc.). Then get a reference to the current selected range (where the cursor is). Then loop through the collection and call the Range.compareLocationWith method to find the range that is "AdjacentBefore" the currently selected range.
I was trying to do a similar thing. At least, when a selection is empty get the word nearby the cursor. I hope their would be some API function, but that's not the case.
I started out with the answer/idea of Rick Kirkham (thanks!). I couldn't get to search method to work to get a list of words. Using split on a space worked fine though.
Instead of select like I do you could modify the the text.
If you don't want to get close-by but only after you should alter the function to check 'InsideStart' (in that scenario you would like to go to the previous word, so i-1).
Word.run(async (context) => {
let cursorOrSelection = context.document.getSelection();
cursorOrSelection.load();
await context.sync();
// if the cursor is empty we make a selection of the Word close-by
// this behaviour is done automatically when you insert a comment in Word
if (cursorOrSelection.isEmpty) {
console.log("Empty selection, cursor.");
// get the paragraph closest to the cursor.
const paragraph = cursorOrSelection.paragraphs.getFirst();
const allWordsInParagraph = paragraph.split([" "], true /* trimDelimiters*/, true /* trimSpaces */);
allWordsInParagraph.load();
await context.sync();
// compare the cursorRange with the ranges of individual words in the paragraph.
let compareRanges = [];
allWordsInParagraph.items.forEach( item => {
compareRanges.push({
compare: cursorOrSelection.compareLocationWith(item),
range: item
});
});
await context.sync();
// walk through all the words and compare the location relation with the cursor
// were the location relation changes, the word is near the cursor.
let previousLocationRelation = null;
let wordClosestToCursorRange = null;
for (let i = 0; i < compareRanges.length; i++) {
const locationRelation = compareRanges[i].compare.value;
console.log(locationRelation);
// if first entry is Before, we are at the beginning
if(i==0 && locationRelation === 'Before') {
wordClosestToCursorRange = compareRanges[i].range;
// jump out
break;
}
else {
if(previousLocationRelation && locationRelation != previousLocationRelation) {
// first "edge" we find.
// console.log('-- edge');
// if first edge we encounter is Before
// we need the previous one (could be after)
if(locationRelation === 'Before') {
wordClosestToCursorRange = compareRanges[i-1].range;
}
else {
// we are inside the word or end of the word
// Inside, InsideStart, InsideEnd
wordClosestToCursorRange = compareRanges[i].range;
}
// jump out we are only interested in the first edge
break;
}
}
previousLocationRelation = locationRelation;
}
wordClosestToCursorRange.select();
}
return context.sync();
})
.catch(function (error) {
console.log(error.message)
})
I need to add custom props to my created polys. To do so currently when the user select in the toolbar the polygon and create a shape, on the create event I convert it to json remove it from the map add the custom props to the json and reload the newly created layer.
this.map.on('pm:create', e => {
const id = getUID();
const leafId = e.layer._leaflet_id;
const featureGroup = L.featureGroup().addLayer(e.layer);
this.map.eachLayer(layer => {
if (layer._leaflet_id === leafId) {
this.map.removeLayer(layer);
}
});
const data = featureGroup.toGeoJSON();
data.features[0].properties = {
id,
name: `Zone ${id}`
};
this.zoneService.add({id, data: JSON.stringify(data)})
.pipe(
switchMap((res) => this.zoneService.getAll().pipe(this.addToMap(this.map)))
).subscribe();
});
This is working but I feel I am not doing something right here. Adding removing Adding, there must be a better way. Thanks for any help
Is it possible to set a TextDocument as dirty programatically in VSCode? Something like
openedDocument.setDirty()
There isn't a direct way to do it; TextDocument.isDirty is a read-only property.
However, I put together a workaround that sets isDirty by making an edit that has no effect (tested with VSCode 1.37.1):
// Set the dirty bit on 'textEditor'. This is meant to be called as a
// text editor command.
async function setDirty(textEditor: TextEditor, editBuilder: TextEditorEdit)
: Promise<void>
{
// The strategy here is to make a change that has no effect. If the
// document has text in it, we can replace some text with itself
// (simply inserting an empty string does not work). We prefer to
// edit text at the end of the file in order to minimize spurious
// recomputation by analyzers.
// Try to replace the last line.
if (textEditor.document.lineCount >= 2) {
const lineNumber = textEditor.document.lineCount-2;
const lastLineRange = new Range(
new Position(lineNumber, 0),
new Position(lineNumber+1, 0));
const lastLineText = textEditor.document.getText(lastLineRange);
editBuilder.replace(lastLineRange, lastLineText);
return;
}
// Try to replace the first character.
const range = new Range(new Position(0, 0), new Position(0, 1));
const text = textEditor.document.getText(range);
if (text.length > 0) {
editBuilder.replace(range, text);
return;
}
// With an empty file, we first add a character and then remove it.
// This has to be done as two edits, which can cause the cursor to
// visibly move and then return, but we can at least combine them
// into a single undo step.
await textEditor.edit(
(innerEditBuilder: TextEditorEdit) => {
innerEditBuilder.replace(range, " ");
},
{ undoStopBefore: true, undoStopAfter: false });
await textEditor.edit(
(innerEditBuilder: TextEditorEdit) => {
innerEditBuilder.replace(range, "");
},
{ undoStopBefore: false, undoStopAfter: true });
}
In your activate function, hook it up with something like:
context.subscriptions.push(
commands.registerTextEditorCommand("extension.setDirty", setDirty));