first commit
This commit is contained in:
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
/dist-dev
|
||||||
|
/src/assets/sprites/*.*
|
||||||
|
!/src/assets/sprites/.gitkeep
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw*
|
||||||
|
#src/views/account/ForgetPwd.vue
|
||||||
22
background.js
Normal file
22
background.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
chrome.action.onClicked.addListener(async (tab) => {
|
||||||
|
try {
|
||||||
|
// Inject the modules and then execute the main function
|
||||||
|
await chrome.scripting.executeScript({
|
||||||
|
target: { tabId: tab.id },
|
||||||
|
files: ["fields.js"],
|
||||||
|
world: "MAIN"
|
||||||
|
});
|
||||||
|
await chrome.scripting.executeScript({
|
||||||
|
target: { tabId: tab.id },
|
||||||
|
files: ["dom.js"],
|
||||||
|
world: "MAIN"
|
||||||
|
});
|
||||||
|
await chrome.scripting.executeScript({
|
||||||
|
target: { tabId: tab.id },
|
||||||
|
files: ["main.js"],
|
||||||
|
world: "MAIN"
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error injecting scripts:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
91
dom.js
Normal file
91
dom.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
export const getGuestSpaceId = () => {
|
||||||
|
const pathMatch = window.location.pathname.match(/\/guest\/([0-9]+)\//);
|
||||||
|
return pathMatch ? pathMatch[1] : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createFieldSpan = ({
|
||||||
|
code: fieldCode,
|
||||||
|
type: fieldType,
|
||||||
|
width: fieldWidth,
|
||||||
|
}) => {
|
||||||
|
const container = document.createElement("div");
|
||||||
|
if (fieldWidth) {
|
||||||
|
container.style.width = `${Number(fieldWidth) - 8}px`;
|
||||||
|
container.style.marginLeft = "8px";
|
||||||
|
} else {
|
||||||
|
container.style.width = "100%";
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldSpan = document.createElement("span");
|
||||||
|
fieldSpan.textContent =
|
||||||
|
fieldType !== void 0 ? `${fieldCode} (${fieldType})` : fieldCode;
|
||||||
|
fieldSpan.style.display = "inline-block";
|
||||||
|
fieldSpan.style.width = "100%";
|
||||||
|
fieldSpan.style.color = "red";
|
||||||
|
fieldSpan.style.overflowWrap = "anywhere";
|
||||||
|
fieldSpan.style.whiteSpace = "pre-wrap";
|
||||||
|
|
||||||
|
if (fieldType === "GROUP") {
|
||||||
|
fieldSpan.style.marginLeft = "20px";
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(fieldSpan);
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createFieldWithTooltips = ({
|
||||||
|
code: tooltipCode,
|
||||||
|
type: tooltipType,
|
||||||
|
width: tooltipWidth,
|
||||||
|
}) => {
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.style.display = "inline-block";
|
||||||
|
|
||||||
|
const fieldSpan = createFieldSpan({
|
||||||
|
code: tooltipCode,
|
||||||
|
type: tooltipType,
|
||||||
|
width: tooltipWidth,
|
||||||
|
});
|
||||||
|
|
||||||
|
container.append(fieldSpan);
|
||||||
|
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createFieldLabels = (fieldNames, widths, fieldType) => {
|
||||||
|
const labels = fieldNames.map((name, index) => {
|
||||||
|
const width = widths[index];
|
||||||
|
return createFieldWithTooltips({
|
||||||
|
code: name,
|
||||||
|
width: index === fieldNames.length - 1 ? width - 1 : width,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const container = document.createElement("span");
|
||||||
|
|
||||||
|
if (fieldType === "REFERENCE_TABLE") {
|
||||||
|
const spacer = document.createElement("span");
|
||||||
|
spacer.style.width = "30px";
|
||||||
|
spacer.style.display = "inline-block";
|
||||||
|
container.appendChild(spacer);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < labels.length; i++) {
|
||||||
|
const label = labels[i];
|
||||||
|
container.appendChild(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getColumnWidths = (tableElement, fieldType) => {
|
||||||
|
const columnWidths = [];
|
||||||
|
const headers = tableElement.querySelectorAll("th");
|
||||||
|
|
||||||
|
for (let i = 0; i < headers.length; i++) {
|
||||||
|
const header = headers[i];
|
||||||
|
columnWidths.push(header.clientWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldType === "SUBTABLE" ? columnWidths : columnWidths.slice(1);
|
||||||
|
};
|
||||||
193
fields.js
Normal file
193
fields.js
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
export const kintonePrettyFields = {
|
||||||
|
generateFields: (properties, layouts) => {
|
||||||
|
const fields = [];
|
||||||
|
const spacers = [];
|
||||||
|
|
||||||
|
const isStatus = field => field.type === "STATUS";
|
||||||
|
const isStatusAssignee = field => field.type === "STATUS_ASSIGNEE";
|
||||||
|
const isCategory = field => field.type === "CATEGORY";
|
||||||
|
|
||||||
|
const propertyList = Object.values(properties)
|
||||||
|
|
||||||
|
const statusField = propertyList.find(isStatus);
|
||||||
|
if (statusField && statusField.enabled) {
|
||||||
|
fields.push(statusField);
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusAssigneeField = propertyList.find(isStatusAssignee);
|
||||||
|
if (statusAssigneeField && statusAssigneeField.enabled) {
|
||||||
|
fields.push(statusAssigneeField);
|
||||||
|
}
|
||||||
|
|
||||||
|
const categoryField = propertyList.find(isCategory);
|
||||||
|
if (categoryField && categoryField.enabled) {
|
||||||
|
fields.push(categoryField);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const r of layouts) {
|
||||||
|
if (r.type === "ROW") {
|
||||||
|
for (const f of r.fields) {
|
||||||
|
if (f.type === "LABEL" || f.type === "HR") continue;
|
||||||
|
if (f.type === "SPACER") {
|
||||||
|
spacers.push(f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const ee = properties[f.code];
|
||||||
|
if (ee.type !== "SUBTABLE" && ee.type !== "GROUP") {
|
||||||
|
if (ee.type === "CHECK_BOX" || ee.type === "DROP_DOWN" || ee.type === "MULTI_SELECT" || ee.type === "RADIO_BUTTON") {
|
||||||
|
const sortedOptions = Object.values(ee.options).sort((a, b) => Number(a.index) - Number(b.index)).map(o => o.label);
|
||||||
|
fields.push({
|
||||||
|
...ee,
|
||||||
|
sortedOptions
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fields.push(ee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (r.type === "SUBTABLE") {
|
||||||
|
const tableCode = r.code;
|
||||||
|
const tableField = properties[tableCode];
|
||||||
|
if (tableField.type !== "SUBTABLE") continue;
|
||||||
|
const tableFields = {};
|
||||||
|
for (const { code: fieldCode } of r.fields) {
|
||||||
|
const subf = tableField.fields[fieldCode];
|
||||||
|
if (subf.type === "CHECK_BOX" || subf.type === "DROP_DOWN" || subf.type === "MULTI_SELECT" || subf.type === "RADIO_BUTTON") {
|
||||||
|
const sortedOptions = Object.values(subf.options).sort((a, b) => Number(a.index) - Number(b.index)).map(o => o.label);
|
||||||
|
tableFields[fieldCode] = {
|
||||||
|
...subf,
|
||||||
|
table: tableCode,
|
||||||
|
sortedOptions
|
||||||
|
};
|
||||||
|
fields.push({
|
||||||
|
...subf,
|
||||||
|
table: tableCode,
|
||||||
|
sortedOptions
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tableFields[fieldCode] = {
|
||||||
|
...subf,
|
||||||
|
table: tableCode
|
||||||
|
};
|
||||||
|
fields.push({
|
||||||
|
...subf,
|
||||||
|
table: tableCode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fields.push({
|
||||||
|
...tableField,
|
||||||
|
fields: tableFields
|
||||||
|
});
|
||||||
|
} else if (r.type === "GROUP") {
|
||||||
|
const groupCode = r.code;
|
||||||
|
const groupField = properties[groupCode];
|
||||||
|
if (groupField.type !== "GROUP") continue;
|
||||||
|
const groupFields = {};
|
||||||
|
for (const row of r.layout) {
|
||||||
|
for (const f of row.fields) {
|
||||||
|
if (f.type === "LABEL" || f.type === "HR") continue;
|
||||||
|
if (f.type === "SPACER") {
|
||||||
|
spacers.push({
|
||||||
|
...f,
|
||||||
|
group: groupCode
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const gSubf = properties[f.code];
|
||||||
|
if (gSubf.type !== "CATEGORY" && gSubf.type !== "STATUS" && gSubf.type !== "STATUS_ASSIGNEE" && gSubf.type !== "SUBTABLE" && gSubf.type !== "GROUP") {
|
||||||
|
if (gSubf.type === "CHECK_BOX" || gSubf.type === "DROP_DOWN" || gSubf.type === "MULTI_SELECT" || gSubf.type === "RADIO_BUTTON") {
|
||||||
|
const sortedOptions = Object.values(gSubf.options).sort((a, b) => Number(a.index) - Number(b.index)).map(o => o.label);
|
||||||
|
groupFields[f.code] = {
|
||||||
|
...gSubf,
|
||||||
|
group: groupCode,
|
||||||
|
sortedOptions
|
||||||
|
};
|
||||||
|
fields.push({
|
||||||
|
...gSubf,
|
||||||
|
group: groupCode,
|
||||||
|
sortedOptions
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
groupFields[f.code] = {
|
||||||
|
...gSubf,
|
||||||
|
group: groupCode
|
||||||
|
};
|
||||||
|
fields.push({
|
||||||
|
...gSubf,
|
||||||
|
group: groupCode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fields.push({
|
||||||
|
...groupField,
|
||||||
|
fields: groupFields
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup copy
|
||||||
|
const flatFields = fields;
|
||||||
|
for (const f of flatFields) {
|
||||||
|
if ('lookup' in f && f.lookup !== null) {
|
||||||
|
for (const mapping of f.lookup.fieldMappings) {
|
||||||
|
const target = flatFields.find(ee => ["SINGLE_LINE_TEXT", "NUMBER", "MULTI_LINE_TEXT", "RICH_TEXT", "LINK", "CHECK_BOX", "RADIO_BUTTON", "DROP_DOWN", "MULTI_SELECT", "DATE", "TIME", "DATETIME", "USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT", "CALC", "RECORD_NUMBER"].includes(ee.type) && ee.code === mapping.field);
|
||||||
|
if (target) {
|
||||||
|
target.isLookupCopy = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordNumberField = propertyList.find(f => f.type === "RECORD_NUMBER");
|
||||||
|
if (recordNumberField && !fields.some(f => f.type === "RECORD_NUMBER")) fields.push(recordNumberField);
|
||||||
|
|
||||||
|
const creatorField = propertyList.find(f => f.type === "CREATOR");
|
||||||
|
if (creatorField && !fields.some(f => f.type === "CREATOR")) fields.push(creatorField);
|
||||||
|
|
||||||
|
const createdTimeField = propertyList.find(f => f.type === "CREATED_TIME");
|
||||||
|
if (createdTimeField && !fields.some(f => f.type === "CREATED_TIME")) fields.push(createdTimeField);
|
||||||
|
|
||||||
|
const modifierField = propertyList.find(f => f.type === "MODIFIER");
|
||||||
|
if (modifierField && !fields.some(f => f.type === "MODIFIER")) fields.push(modifierField);
|
||||||
|
|
||||||
|
const updatedTimeField = propertyList.find(f => f.type === "UPDATED_TIME");
|
||||||
|
if (updatedTimeField && !fields.some(f => f.type === "UPDATED_TIME")) fields.push(updatedTimeField);
|
||||||
|
|
||||||
|
return { fields, spacers };
|
||||||
|
},
|
||||||
|
isCategory: field => field.type === "CATEGORY",
|
||||||
|
isCheckBox: field => field.type === "CHECK_BOX",
|
||||||
|
isCreatedTime: field => field.type === "CREATED_TIME",
|
||||||
|
isCreator: field => field.type === "CREATOR",
|
||||||
|
isDate: field => field.type === "DATE",
|
||||||
|
isDatetime: field => field.type === "DATETIME",
|
||||||
|
isDropDown: field => field.type === "DROP_DOWN",
|
||||||
|
isFile: field => field.type === "FILE",
|
||||||
|
isGroup: field => field.type === "GROUP",
|
||||||
|
isGroupSelect: field => field.type === "GROUP_SELECT",
|
||||||
|
isInGroup: field => 'group' in field,
|
||||||
|
isInSubtable: field => 'table' in field,
|
||||||
|
isLink: field => field.type === "LINK",
|
||||||
|
isLookup: field => 'lookup' in field,
|
||||||
|
isLookupCopy: field => field.isLookupCopy === true,
|
||||||
|
isModifier: field => field.type === "MODIFIER",
|
||||||
|
isMultiLineText: field => field.type === "MULTI_LINE_TEXT",
|
||||||
|
isMultiSelect: field => field.type === "MULTI_SELECT",
|
||||||
|
isNotInSubtable: field => !('table' in field),
|
||||||
|
isNumber: field => field.type === "NUMBER",
|
||||||
|
isOrganizationSelect: field => field.type === "ORGANIZATION_SELECT",
|
||||||
|
isRadioButton: field => field.type === "RADIO_BUTTON",
|
||||||
|
isRecordNumber: field => field.type === "RECORD_NUMBER",
|
||||||
|
isReferenceTable: field => field.type === "REFERENCE_TABLE",
|
||||||
|
isRichText: field => field.type === "RICH_TEXT",
|
||||||
|
isSingleLineText: field => field.type === "SINGLE_LINE_TEXT",
|
||||||
|
isStatus: field => field.type === "STATUS",
|
||||||
|
isStatusAssignee: field => field.type === "STATUS_ASSIGNEE",
|
||||||
|
isSubtable: field => field.type === "SUBTABLE",
|
||||||
|
isTime: field => field.type === "TIME",
|
||||||
|
isUpdatedTime: field => field.type === "UPDATED_TIME",
|
||||||
|
isUserSelect: field => field.type === "USER_SELECT",
|
||||||
|
};
|
||||||
93
main.js
Normal file
93
main.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { kintonePrettyFields } from './fields.js';
|
||||||
|
import { getGuestSpaceId, createFieldWithTooltips, createFieldLabels, getColumnWidths } from './dom.js';
|
||||||
|
import { KintoneRestAPIClient } from '@kintone/rest-api-client';
|
||||||
|
|
||||||
|
export async function runKintoneHelper() {
|
||||||
|
const guestSpaceId = getGuestSpaceId();
|
||||||
|
const client = new KintoneRestAPIClient({
|
||||||
|
guestSpaceId: guestSpaceId,
|
||||||
|
});
|
||||||
|
const appId = kintone.app.getId();
|
||||||
|
|
||||||
|
if (appId === null) return;
|
||||||
|
|
||||||
|
const language = kintone.getLoginUser().language;
|
||||||
|
const isPreview = false;
|
||||||
|
const { properties: formFields } = await client.app.getFormFields({
|
||||||
|
app: appId,
|
||||||
|
lang: language,
|
||||||
|
preview: isPreview,
|
||||||
|
});
|
||||||
|
const { layout: layout } = await client.app.getFormLayout({
|
||||||
|
app: appId,
|
||||||
|
preview: isPreview,
|
||||||
|
});
|
||||||
|
const { fields: fieldsWithLabels, spacers: spacerElements } =
|
||||||
|
kintonePrettyFields.generateFields(formFields, layout);
|
||||||
|
for (const field of fieldsWithLabels) {
|
||||||
|
const fieldElement = kintone.app.record.getFieldElement(field.code);
|
||||||
|
if (fieldElement) {
|
||||||
|
if (kintonePrettyFields.isSubtable(field)) {
|
||||||
|
fieldElement.before(
|
||||||
|
createFieldWithTooltips({
|
||||||
|
code: field.code,
|
||||||
|
type: field.type,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const fieldNames = Object.keys(field.fields);
|
||||||
|
const columnWidths = getColumnWidths(fieldElement, field.type);
|
||||||
|
fieldElement.after(
|
||||||
|
createFieldLabels(fieldNames, columnWidths, field.type)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kintonePrettyFields.isReferenceTable(field)) {
|
||||||
|
fieldElement.before(
|
||||||
|
createFieldWithTooltips({
|
||||||
|
code: field.code,
|
||||||
|
type: field.type,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (field.referenceTable) {
|
||||||
|
const displayFields = field.referenceTable.displayFields;
|
||||||
|
const columnWidths = getColumnWidths(fieldElement, field.type);
|
||||||
|
fieldElement.appendChild(
|
||||||
|
createFieldLabels(displayFields, columnWidths, field.type)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (kintonePrettyFields.isGroup(field)) {
|
||||||
|
fieldElement.parentElement?.before(
|
||||||
|
createFieldWithTooltips({
|
||||||
|
code: field.code,
|
||||||
|
type: field.type,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fieldElement.before(
|
||||||
|
createFieldWithTooltips({
|
||||||
|
code: field.code,
|
||||||
|
type: field.type,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const spacer of spacerElements) {
|
||||||
|
const spacerElement = kintone.app.record.getSpaceElement(spacer.elementId);
|
||||||
|
if (spacerElement) {
|
||||||
|
spacerElement.appendChild(
|
||||||
|
createFieldWithTooltips({
|
||||||
|
code: spacer.elementId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
spacerElement.style.border = "1px dotted blue";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute immediately upon injection
|
||||||
|
runKintoneHelper().catch(console.error);
|
||||||
14
manifest.json
Normal file
14
manifest.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "Kintone Helper Extension",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "A Chrome extension to enhance Kintone",
|
||||||
|
"permissions": [
|
||||||
|
"activeTab",
|
||||||
|
"scripting"
|
||||||
|
],
|
||||||
|
"action": {},
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
5913
package-lock.json
generated
Normal file
5913
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
package.json
Normal file
23
package.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "kintone-helper-extenstion",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"main": "main.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"dev": "vite dev",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-legacy": "^6.1.1",
|
||||||
|
"vite": "^6.3.6",
|
||||||
|
"vite-plugin-web-extension": "^4.4.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@kintone/rest-api-client": "^5.7.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
vite.config.js
Normal file
14
vite.config.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import webExtension from 'vite-plugin-web-extension';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
webExtension({
|
||||||
|
additionalInputs: ['main.js', 'fields.js', 'dom.js'],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user