I can use tags in regular page fields without any issue. When using tags within blocks (within a streamfield), the UI works and the tags are saved BUT the current page tags do not show up when loading the page in the admin. That's because the current value is not in the template anymore, it's in a JSON loaded via telepath.
I can confirm that the tags are saved and present in the data passed to initBlockWidget in the page source but these are ignored. Also, if I used a regular text field instead of the tag-widget, I can see the saved-values in the admin.
This is the code I have (which used to be enough before the refactor with telepath).
from wagtail.admin.widgets import AdminTagWidget
class TagBlock(TextBlock):
#cached_property
def field(self):
field_kwargs = {"widget": AdminTagWidget()}
field_kwargs.update(self.field_options)
return forms.CharField(**field_kwargs)
I think the following link is what I need to complete somehow to get it to work: https://docs.wagtail.io/en/stable/reference/streamfield/widget_api.html#form-widget-client-side-api
I've tried with this:
class AdminTagWidgetAdapter(WidgetAdapter):
class Media:
js = [
"wagtailadmin/js/vendor/tag-it.js",
"js/admin/admin-tag-widget-adapter.js",
]
register(AdminTagWidgetAdapter(), AdminTagWidget)
And under js/admin/admin-tag-widget-adapter.js:
console.log("adapter"); // this shows up in the console
class BoundWidget { // copied from wagtail source code
constructor(element, name, idForLabel, initialState) {
var selector = ':input[name="' + name + '"]';
this.input = element.find(selector).addBack(selector); // find, including element itself
this.idForLabel = idForLabel;
this.setState(initialState);
}
getValue() {
return this.input.val();
}
getState() {
return this.input.val();
}
setState(state) {
this.input.val(state);
}
getTextLabel(opts) {
const val = this.getValue();
if (typeof val !== 'string') return null;
const maxLength = opts && opts.maxLength;
if (maxLength && val.length > maxLength) {
return val.substring(0, maxLength - 1) + '…';
}
return val;
}
focus() {
this.input.focus();
}
}
// my code here:
class AdminTagWidget {
constructor(html, idPattern) {
this.html = html;
this.idPattern = idPattern;
}
boundWidgetClass = BoundWidget;
render(placeholder, name, id, initialState) {
console.log("RENDER", placeholder, name, id, initialState); // this does not show
var html = this.html.replace(/__NAME__/g, name).replace(/__ID__/g, id);
var idForLabel = this.idPattern.replace(/__ID__/g, id);
var dom = $(html);
$(placeholder).replaceWith(dom);
// eslint-disable-next-line new-cap
return new this.boundWidgetClass(dom, name, idForLabel, initialState);
}
}
console.log("here") // does show in the console
// variants I've tried:
//window.telepath.register('wagtail.admin.widgets.tags.AdminTagWidget', AdminTagWidget);
//window.telepath.register('wagtail.widgets.AdminTagWidget', AdminTagWidget);
window.telepath.register('path.where.its.used.AdminTagWidget', AdminTagWidget)
The log from my custom render method does not show. It seems that I'm not calling the right path within window.telepath.register but I don't know how what the string is supposed to be...
I'm not even sure if this is the right way forward.
Notes:
it works in regular field, the question is about tags in blocks
I'm using Wagtail version 2.13.2 but I've also tried with 2.15 without any difference.
In the console, I can log window.telepath and see my custom widget. It's just not "applied" to anything
Your WidgetAdapter class needs a js_constructor attribute:
class AdminTagWidgetAdapter(WidgetAdapter):
js_constructor = 'myapp.widgets.AdminTagWidget'
class Media:
js = [
"wagtailadmin/js/vendor/tag-it.js",
"js/admin/admin-tag-widget-adapter.js",
]
Any string value will work here - it just needs to uniquely identify the class, so it's recommended to use a dotted module-like path to avoid colliding with others. This then matches the string you pass to window.telepath.register on the Javascript side:
window.telepath.register('myapp.widgets.AdminTagWidget', AdminTagWidget)
I am creating a vscode extension that does some custom auto-completing of files paths.
I want to take what the user has typed, and if that value resolves to a folder in the workspace, I want to list all the files in that folder for auto-complete.
For example, given:
a workspace located at: /home/me/my-vs-project
with files:
/home/me/my-vs-project/assets/dog.png
/home/me/my-vs-project/assets/cat.jpeg
If I type in 'assets' or './assets' into vscode, the extension should be able to provide me an autocomplete list of:
'./assets/dog.png'
'./assets/cat.png'
Here's a snippet of the code that doesn't work (returns 0 results)..
let inputAsWorkspaceRelativeFolder = getInput(document, position); // for example, would return: '/home/me/my-vs-project/assets' for input of './assets'
let glob = inputAsWorkspaceRelativeFolder + '/*';
vscode.workspace.findFiles(glob, null, 100).then((uris: vscode.Uri[] ) => {
uris.forEach((uri: vscode.Uri) => {
console.log(uri);
});
});
For some reason, the above code is returning 0 uris though. Thoughts on how I have to format the glob to make this happen? and/or if there is a better approach?
I was able to do this using vscode.RelativePattern -- I'm sure I could've done it using generic GlobPatterns but im still not clear what the findFiles(..) consider the 'root' when matching files; RelativePattern is explicitly relative to the workspace root.
let workspaceFolder: vscode.WorkspaceFolder | undefined = vscode.workspace.getWorkspaceFolder(document.uri);
if (!workspaceFolder || document.isUntitled) {
return undefined;
}
// Workspace folder: /home/me/my-project
let workspaceFolderPath: string = workspaceFolder.uri.path;
let relativeSearchFolderPrefix = path.normalize(path.dirname(document.uri.path) + '/' + searchText);
relativeSearchFolderPrefix = path.relative(workspaceFolderPath, relativeSearchFolderPrefix);
let relativePattern: vscode.RelativePattern = new vscode.RelativePattern(
workspaceFolderPath,
relativeSearchFolderPrefix + '/**/*.{png,jpeg,jpg,gif}');
return vscode.workspace.findFiles(globPattern, null, 50).then((uris: vscode.Uri[] ) => {
let relativePaths: string[] = [];
uris.forEach((uri: vscode.Uri) => {
relativePaths.push(path.relative(current, uri.path));
});
// trivial custom function that turns an array of strings into CompletionItems
return getCompletionItems(relativePaths, vscode.CompletionItemKind.File);
});
😊👋🏻
I think you wronged the glob.
I found this intresting wiki about Glob pattern composition.
let inputAsWorkspaceRelativeFolder = 'asset'; // for example, would return: '/home/me/my-vs-project/assets' for input of './assets'
//https://github.com/ev3dev/vscode-ev3dev-browser/wiki/Glob-Patterns
let glob = '**/'+inputAsWorkspaceRelativeFolder+'/*.*';//or +'/{*.png,*.jpeg}';
Or you can use the node built-in fs
import * as fs from 'fs';
fs.readdir(inputAsWorkspaceRelativeFolder, (err, files: string[]) => {
files.forEach((file: path) => {
const uri = vscode.Uri.file(file);
console.log(uri);
});
});
More simple, if you want to get all the files in the asset folder and don't want to filter for extension.
What parameters are mandatory for an UploadCollectionItem with the URL parameter set will show the file when the filename is clicked.
I am using a factory to handle files coming from different locations.
attachmentFactory(sId, context) {
const modelObj = context.getModel().getProperty(context.getPath());
const uploadListItem = new SAPUploadCollectionItem();
// If __metadata exists, attachment entry is from odata, if not then it's a FileEntry object.
if (modelObj.__metadata) {
uploadListItem.setFileName(modelObj.FILE_NAME);
uploadListItem.setMimeType(modelObj.MIME_CODE);
uploadListItem.setUrl("https://upload.wikimedia.org/wikipedia/commons/4/49/Koala_climbing_tree.jpg");
}
else {
uploadListItem.setFileName(modelObj.name);
uploadListItem.setMimeType(modelObj.type);
uploadListItem.setUrl("https://upload.wikimedia.org/wikipedia/commons/4/49/Koala_climbing_tree.jpg");
}
return uploadListItem;
}
I get an exception in UI5 when I press the link in the function
UploadCollection.prototype._triggerLink = function(oEvent, oContext) {
var iLine = null;
var aId;
if (oContext.editModeItem) {
//In case there is a list item in edit mode, the edit mode has to be finished first.
sap.m.UploadCollection.prototype._handleOk(oEvent, oContext, oContext.editModeItem, true);
if (oContext.sErrorState === "Error") {
//If there is an error, the link of the list item must not be triggered.
return this;
}
oContext.sFocusId = oEvent.getParameter("id");
}
aId = oEvent.oSource.getId().split("-");
iLine = aId[aId.length - 2];
sap.m.URLHelper.redirect(oContext.aItems[iLine].getProperty("url"), true);
};
oContext.aItems is an array but the source.getId() value is "__item9-ta_filenameHL" so __item9 is not found in oContext.aItems
I'm not sure if this is a bug or I'm setting up my UploadCollectionItem incorrectly
I had to set the sId of the UploadCollectionItem to be the sId that was passed into the factory.
The GitHub file browser lists a file's name and info about the last commit:
Is there a way to add each file's size to these listings?
If there is no official way to do this, here is a bookmarklet which uses GitHub's API to add size info (won't work in IE):
javascript:(function(){function f(a){var g=document.querySelector('div[role="rowheader"] a[title="'+a.name+'"]').closest('div[role="row"]').lastElementChild,c=document.createElement("div");c.style.width="5em";"file"===a.type&&(c.textContent=(a.size/1024).toLocaleString("en-US",{minimumFractionDigits:1,maximumFractionDigits:1})+" KB",c.style.textAlign="right",c.style.whiteSpace="nowrap");g.insertAdjacentElement("beforebegin",c)}var b=window.location.pathname.split("/"),d=b[1],h=b[2],e=b[4];b=b.slice(5);d=["https://api.github.com/repos",d,h,"contents"].concat(b||[]).join("/")+(e?"?ref="+e:"");console.log(d);fetch(d).then(function(a){return a.json()}).then(function(a){return Array.isArray(a)?a.forEach(f):console.warn(a)})})();
Unminified source:
(function () {
"use strict";
//Parse the current GitHub repo url. Examples:
// Repo root: /Sphinxxxx/vanilla-picker
// Subfolder: /Sphinxxxx/vanilla-picker/tree/master/src/css
// Subfolder at commit: /Sphinxxxx/vanilla-picker/tree/382231756aac75a49f046ccee1b04263196f9a22/src/css
// Subfolder at tag: /Sphinxxxx/vanilla-picker/tree/v2.2.0/src/css
//
//If applicable, the name of the commit/branch/tag is always the 4th element in the url path.
//Here, we put that in the "ref" variable:
const [/* Leading slash */, owner, repo, /* "tree" */, ref, ...path] = window.location.pathname.split('/');
//Create the URL to query GitHub's API: https://developer.github.com/v3/repos/contents/#get-contents
//Example:
// https://api.github.com/repos/Sphinxxxx/vanilla-picker/contents/src/css?ref=382231756aac75a49f046ccee1b04263196f9a22
const query = ['https://api.github.com/repos', owner, repo, 'contents'].concat(path || []),
url = query.join('/') + (ref ? '?ref=' + ref : '');
console.log(url);
//https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
fetch(url).then(r => r.json())
.then(j => Array.isArray(j) ? j.forEach(handleFileInfo) : console.warn(j));
function handleFileInfo(info) {
//console.log(info);
const link = document.querySelector(`div[role="rowheader"] a[title="${info.name}"]`);
const timeCol = link.closest('div[role="row"]').lastElementChild;
const sizeCol = document.createElement('div');
sizeCol.style.width = '5em';
if(info.type === 'file') {
//http://stackoverflow.com/a/17663871/1869660
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString#Parameters
sizeCol.textContent = (info.size/1024).toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' KB';
sizeCol.style.textAlign = 'right';
sizeCol.style.whiteSpace = 'nowrap';
}
timeCol.insertAdjacentElement('beforebegin', sizeCol);
}
})();
Create a bookmarklet from the first piece of code, or just copy and paste it to your browser's console.
For Chrome browsers there exists an extension:
https://chrome.google.com/webstore/detail/github-repository-size/apnjnioapinblneaedefcnopcjepgkci
You can force file sizes to appear with a JavaScript bookmarklet that calls the GitHub API.
GitHub in 2023 now has two kinds of file table (one is a <table>, the other is <div>-based) so I prepared this new bookmarklet that detects which style is in use before modifying it:
javascript:(function e(){let t=[...document.querySelectorAll('div[role="row"]')],n;if(t.length||(t=[...document.querySelectorAll("tbody tr")],n=document.querySelector("thead tr")),(t=t.slice(1)).length&&"github.com"===document.location.host){let[,l,i,r,o,...c]=window.location.pathname.split("/");if(i&&("tree"===r||!r)){let s=["https://api.github.com/repos",l,i,"contents"].concat(c||[]).join("/")+(o?"?ref="+o:"");console.log(s),fetch(s).then(e=>e.json()).then(e=>{let l=new Map;if(e.forEach(e=>l.set(e.name,e)),n&&"Size"!==n.children[1].innerText){let i=document.createElement("th");i.style.width="6em",i.textContent="Size",n.firstElementChild.insertAdjacentElement("afterend",i)}for(let r of t){let o=r.children[n?0:1],c=o.innerText;c.indexOf("\n")>0&&(c=c.slice(0,c.indexOf("\n")));let s=l.get(c),d=document.createElement(n?"td":"div");d.style.width="6em",d.className=o.className,d.innerText=a(s?.size),o.insertAdjacentElement("afterend",d)}})}else console.error("GITHUB PATH NOT UNDERSTOOD")}else console.error("GITHUB TABLE NOT FOUND; can't add file sizes");function a(e){if(!e)return null==e?"–":"0";if(e<0)return"-"+a(-e);let t;return e<1024?e+" B":(e<1048576?(t=" KiB",e/=1024):e<1073741824?(t=" MiB",e/=1048576):(t=" GiB",e/=1073741824),e.toFixed(1)+t)}})();
See instructions here for making a bookmarklet.
Here's the original unminified code:
(function addSizes() {
// Sometimes each row is like `<div role="row" class="Box-row...">` with no column headers
// e.g. https://github.com/qwertie/ecsharp
// Sometimes each row is like `<tr class="react-directory-row">` with header in `<thead...><tr...>`
// e.g. https://github.com/qwertie/ecsharp/tree/master/Main
// Look for the first kind of rows; if not found, look for the other kind
let rows = [...document.querySelectorAll(`div[role="row"]`)], header;
if (!rows.length) {
rows = [...document.querySelectorAll(`tbody tr`)];
header = document.querySelector(`thead tr`);
}
rows = rows.slice(1);
if (rows.length && document.location.host === 'github.com') {
// Parse path after https://github.com, e.g.
// /username/repo
// /username/repo/tree/branch-name
// /username/repo/tree/branch-name/folder-name
let [/* Leading slash */, owner, repo, __tree__, branch, ...path] = window.location.pathname.split('/');
if (repo && (__tree__ === 'tree' || !__tree__)) {
let query = ['https://api.github.com/repos', owner, repo, 'contents']
.concat(path || []).join('/') + (branch ? '?ref=' + branch : '');
console.log(query);
// The GitHub API will returns an array of objects like
// [{ "name": "file.py", "path": "folder/file.py", "size": 5226, ... }, ...]
fetch(query).then(r => r.json()).then(files => {
// Index the results by file name
let fileMap = new Map();
files.forEach(file => fileMap.set(file.name, file));
// If there is a header row, add a header cell for the file size. Note:
// If user browses to another folder, the 'Size' header column still exists
// but no file sizes. In that case, avoid adding another 'Size' column.
if (header && header.children[1].innerText !== 'Size') {
let sizeCol = document.createElement('th');
sizeCol.style.width = '6em';
sizeCol.textContent = 'Size';
header.firstElementChild.insertAdjacentElement('afterend', sizeCol);
}
// For each row of the table: get the file name, look it up in
// fileMap, get the file size, and insert a size column.
for (let row of rows) {
let nameCol = row.children[header ? 0 : 1];
let name = nameCol.innerText;
if (name.indexOf('\n') > 0) name = name.slice(0, name.indexOf('\n'));
let file = fileMap.get(name);
let sizeCol = document.createElement(header ? 'td' : 'div');
sizeCol.style.width = '6em';
sizeCol.className = nameCol.className;
sizeCol.innerText = formatFileSize(file?.size);
nameCol.insertAdjacentElement('afterend', sizeCol);
}
});
} else {
console.error('GITHUB PATH NOT UNDERSTOOD');
}
} else {
console.error("GITHUB TABLE NOT FOUND; can't add file sizes");
}
function formatFileSize(size) {
if (!size) return size == null ? '–' : '0';
if (size < 0) return '-' + formatFileSize(-size);
let suffix;
if (size < 1024) {
return size + ' B';
} else if (size < 1024*1024) {
suffix = ' KiB';
size /= 1024;
} else if (size < 1024*1024*1024) {
suffix = ' MiB';
size /= 1024*1024;
} else {
suffix = ' GiB';
size /= 1024*1024*1024;
}
return size.toFixed(1) + suffix;
};
})();
It's based loosely on #Sphinxxx's code, but the file size will be the second column rather than the third, and the units change automatically as the file size increases.
No, the GitHub file browser is not configurable that way.
Getting back that extra information would mean transferring an enormous extra amount of data (for each pages of each repos) for GitHub, so I am not sure this is a feature you would see anytime soon.
Note that 'size' is an acceptable criteria for GitHub search though (meaning that size information is there and can be used, not just for browsing files).
element language:xml size:100
Matches code with the word "element" that's marked as being XML and has exactly 100 bytes.
Updated script/bookmarklet after #Sphinxxx answer, now with comments:
Bookmarklet:
!function(){"use strict";const[,t,e,,n,...i]=window.location.pathname.split("/"),o=["https://api.github.com/repos",t,e,"contents"].concat(i||[]).join("/")+(n?"?ref="+n:"");function r(t){var e=null,n=null;if("file"===t.type){const i=`div[role="rowheader"] a[title="${t.name}"]`;e=document.querySelector(i).closest('div[role="row"]').lastElementChild,(n=document.createElement("div")).style.width="5em",n.textContent=(t.size/1024).toLocaleString("en-US",{minimumFractionDigits:1,maximumFractionDigits:1})+" KB",n.style.textAlign="right",n.style.whiteSpace="nowrap",e.insertAdjacentElement("beforebegin",n)}}fetch(o).then((t=>t.json())).then((t=>Array.isArray(t)?t.forEach(r):console.warn("Not an array of files: ",t)))}();
Usage:
Right click, copy link, paste into address bar (or into bookmark link), prepend "javascript:" without quotes, press ENTER
Variant for new github layout with folders tree on the left and file contents on the right:
!function(){"use strict";const[,t,e,,n,...i]=window.location.pathname.split("/"),o=["https://api.github.com/repos",t,e,"contents"].concat(i||[]).join("/")+(n?"?ref="+n:"");function r(t){var e=null,n=null;if("file"===t.type){const i=`div[title="${t.name}"]`;e=document.querySelector(i).closest('tr[class="react-directory-row"]').lastElementChild,(n=document.createElement("td")).style.width="5em",n.innerHTML=(t.size/1024).toLocaleString("en-US",{minimumFractionDigits:1,maximumFractionDigits:1})+" KB",e.insertAdjacentElement("beforebegin",n)}}fetch(o).then((t=>t.json())).then((t=>Array.isArray(t)?t.forEach(r):console.warn("Not an array of files: ",t)))}();
Script 1:
(function () {
"use strict";
//Parse the current GitHub repo url. Examples:
// Repo root: /Sphinxxxx/vanilla-picker
// Subfolder: /Sphinxxxx/vanilla-picker/tree/master/src/css
// Subfolder at commit: /Sphinxxxx/vanilla-picker/tree/382231756aac75a49f046ccee1b04263196f9a22/src/css
// Subfolder at tag: /Sphinxxxx/vanilla-picker/tree/v2.2.0/src/css
//
//If applicable, the name of the commit/branch/tag is always the 4th element in the url path.
//Here, we put that in the "ref" variable:
const [/* Leading slash */, owner, repo, /* "tree" */, ref, ...path] = window.location.pathname.split('/'); // split url and store pieces into constants.
//Create the URL to query GitHub's API: https://developer.github.com/v3/repos/contents/#get-contents
//Example:
// https://api.github.com/repos/Sphinxxxx/vanilla-picker/contents/src/css?ref=382231756aac75a49f046ccee1b04263196f9a22;
const query = ['https://api.github.com/repos', owner, repo, 'contents'].concat(path || []),
url = query.join('/') + (ref ? '?ref=' + ref : '');
//https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
fetch(url).then(r => r.json())
.then(j => Array.isArray(j) ? j.forEach(handleFileInfo) : console.warn("Not an array of files: ",j));
function handleFileInfo(info) {
var timeCol = null;
var sizeCol = null;
var link = "";
var QR = "";
if(info.type === 'file') { // skip folders
const QR = `div[role="rowheader"] a[title="${info.name}"]`; // select the cell containing the file name
link = document.querySelector(QR);
///// Climb along the html hierarchy until it finds a DIV named "row",
///// and get last element (last cell of row), i.e. the date column:
timeCol = link.closest('div[role="row"]').lastElementChild;
///// Create label for file size
sizeCol = document.createElement('div');
sizeCol.style.width = '5em';
//http://stackoverflow.com/a/17663871/1869660
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString#Parameters
sizeCol.textContent = (info.size/1024).toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' KB';
sizeCol.style.textAlign = 'right';
sizeCol.style.whiteSpace = 'nowrap';
///// Insert new label before last element of row:
timeCol.insertAdjacentElement('beforebegin', sizeCol);
} else {
// skip folders
}
}
})();
Script 2:
(function () {
"use strict";
//Parse the current GitHub repo url. Examples:
// Repo root: /Sphinxxxx/vanilla-picker
// Subfolder: /Sphinxxxx/vanilla-picker/tree/master/src/css
// Subfolder at commit: /Sphinxxxx/vanilla-picker/tree/382231756aac75a49f046ccee1b04263196f9a22/src/css
// Subfolder at tag: /Sphinxxxx/vanilla-picker/tree/v2.2.0/src/css
//
//If applicable, the name of the commit/branch/tag is always the 4th element in the url path.
//Here, we put that in the "ref" variable:
const [/* Leading slash */, owner, repo, /* "tree" */, ref, ...path] = window.location.pathname.split('/'); // split url and store pieces into constants.
//Create the URL to query GitHub's API: https://developer.github.com/v3/repos/contents/#get-contents
//Example:
// https://api.github.com/repos/Sphinxxxx/vanilla-picker/contents/src/css?ref=382231756aac75a49f046ccee1b04263196f9a22;
const query = ['https://api.github.com/repos', owner, repo, 'contents'].concat(path || []),
url = query.join('/') + (ref ? '?ref=' + ref : '');
//https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
fetch(url).then(r => r.json())
.then(j => Array.isArray(j) ? j.forEach(handleFileInfo) : console.warn("Not an array of files: ",j));
function handleFileInfo(info) {
var timeCol = null;
var sizeCol = null;
var link = "";
var QR = "";
if(info.type === 'file') { // skip folders
const QR = `div[title="${info.name}"]`; // select the cell containing the file name
link = document.querySelector(QR);
///// Climb along the html hierarchy until it finds a DIV named "row",
///// and get last element (last cell of row), i.e. the date column:
timeCol = link.closest('tr[class="react-directory-row"]').lastElementChild;
///// Create label for file size
sizeCol = document.createElement('td');
sizeCol.style.width = '5em';
//http://stackoverflow.com/a/17663871/1869660
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString#Parameters
sizeCol.innerHTML = (info.size/1024).toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' KB';
//sizeCol.style.textAlign = 'right';
//sizeCol.style.whiteSpace = 'nowrap';
///// Insert new label before last element of row:
timeCol.insertAdjacentElement('beforebegin', sizeCol);
} else {
// skip folders
}
}
})();