nowaikit-utils 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/ai-window.html +598 -0
- package/background/service-worker.js +398 -0
- package/cli.mjs +65 -0
- package/content/ai-sidebar.js +1198 -0
- package/content/code-templates.js +843 -0
- package/content/content.js +2527 -0
- package/content/integration-bridge.js +627 -0
- package/content/main-panel.js +592 -0
- package/content/styles.css +1609 -0
- package/icons/README.txt +1 -0
- package/icons/icon-128.png +0 -0
- package/icons/icon-16.png +0 -0
- package/icons/icon-48.png +0 -0
- package/icons/icon.svg +16 -0
- package/manifest.json +63 -0
- package/options/options.html +434 -0
- package/package.json +49 -0
- package/popup/popup.html +663 -0
- package/popup/popup.js +414 -0
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NowAIKit Utils — Code Templates Library
|
|
3
|
+
* Ctrl+Shift+T to open. Floating, resizable, syntax-highlighted.
|
|
4
|
+
* Supports custom user templates stored in chrome.storage.local.
|
|
5
|
+
*/
|
|
6
|
+
(function() {
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
// ─── Syntax Highlighting Token Sets ─────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
var JS_KEYWORDS = new Set([
|
|
12
|
+
'var','let','const','function','return','if','else','for','while','do',
|
|
13
|
+
'switch','case','break','continue','new','this','typeof','instanceof',
|
|
14
|
+
'try','catch','finally','throw','class','extends','import','export',
|
|
15
|
+
'default','async','await','yield','null','undefined','true','false','in','of',
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
var SN_APIS = new Set([
|
|
19
|
+
'GlideRecord','GlideQuery','GlideAjax','GlideSystem','GlideAggregate',
|
|
20
|
+
'GlideDateTime','GlideElement','GlideUser','AbstractAjaxProcessor',
|
|
21
|
+
'g_form','g_list','g_user','g_navigation','gs','current','previous',
|
|
22
|
+
'sn_ws','RESTMessageV2','GlideHTTPRequest','GlideSysAttachment',
|
|
23
|
+
'Class','Object',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
// ─── Built-in Templates ────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
var BUILT_IN_TEMPLATES = [
|
|
29
|
+
{ title: 'GlideRecord Query', category: 'GlideRecord', description: 'Basic GlideRecord query with encoded query',
|
|
30
|
+
code: "var gr = new GlideRecord('table_name');\ngr.addEncodedQuery('active=true');\ngr.query();\nwhile (gr.next()) {\n gs.info(gr.getValue('number'));\n}" },
|
|
31
|
+
{ title: 'GlideRecord Insert', category: 'GlideRecord', description: 'Create a new record',
|
|
32
|
+
code: "var gr = new GlideRecord('table_name');\ngr.initialize();\ngr.setValue('field', 'value');\ngr.insert();" },
|
|
33
|
+
{ title: 'GlideRecord Update', category: 'GlideRecord', description: 'Find and update a record',
|
|
34
|
+
code: "var gr = new GlideRecord('table_name');\ngr.addQuery('number', 'INC0010001');\ngr.query();\nif (gr.next()) {\n gr.setValue('state', 2);\n gr.update();\n}" },
|
|
35
|
+
{ title: 'GlideRecord Delete', category: 'GlideRecord', description: 'Find and delete records',
|
|
36
|
+
code: "var gr = new GlideRecord('table_name');\ngr.addQuery('active', false);\ngr.query();\ngr.deleteMultiple();" },
|
|
37
|
+
{ title: 'GlideRecord Get', category: 'GlideRecord', description: 'Get a single record by sys_id',
|
|
38
|
+
code: "var gr = new GlideRecord('table_name');\nif (gr.get('sys_id_here')) {\n gs.info(gr.getValue('number'));\n}" },
|
|
39
|
+
{ title: 'GlideQuery Select', category: 'GlideQuery', description: 'Modern query with GlideQuery (Orlando+)',
|
|
40
|
+
code: "new global.GlideQuery('incident')\n .where('active', true)\n .where('priority', 1)\n .select('number', 'short_description', 'state')\n .forEach(function(inc) {\n gs.info(inc.number + ': ' + inc.short_description);\n });" },
|
|
41
|
+
{ title: 'GlideQuery Aggregate', category: 'GlideQuery', description: 'Count, sum, avg with GlideQuery',
|
|
42
|
+
code: "var count = new global.GlideQuery('incident')\n .where('active', true)\n .aggregate('COUNT')\n .get()\n .getOrElse(0);\ngs.info('Active incidents: ' + count);" },
|
|
43
|
+
{ title: 'GlideQuery Insert', category: 'GlideQuery', description: 'Insert record with GlideQuery',
|
|
44
|
+
code: "var sysId = new global.GlideQuery('incident')\n .insert({\n short_description: 'New incident',\n urgency: 2,\n impact: 2\n })\n .get()\n .getValue('sys_id');" },
|
|
45
|
+
{ title: 'GlideQuery Update', category: 'GlideQuery', description: 'Update records with GlideQuery',
|
|
46
|
+
code: "new global.GlideQuery('incident')\n .where('number', 'INC0010001')\n .update({\n state: 2,\n assigned_to: gs.getUserID()\n });" },
|
|
47
|
+
{ title: 'onChange Client Script', category: 'Client Script', description: 'React to field value changes',
|
|
48
|
+
code: "function onChange(control, oldValue, newValue, isLoading) {\n if (isLoading || newValue === '') return;\n\n g_form.setValue('field_name', 'value');\n g_form.setMandatory('field_name', true);\n}" },
|
|
49
|
+
{ title: 'onLoad Client Script', category: 'Client Script', description: 'Run when form loads',
|
|
50
|
+
code: "function onLoad() {\n g_form.setVisible('field_name', false);\n g_form.setReadOnly('field_name', true);\n g_form.addInfoMessage('Record loaded');\n}" },
|
|
51
|
+
{ title: 'onSubmit Client Script', category: 'Client Script', description: 'Validate before form submission',
|
|
52
|
+
code: "function onSubmit() {\n var desc = g_form.getValue('short_description');\n if (!desc || desc.length < 10) {\n g_form.addErrorMessage('Short description must be at least 10 characters');\n return false;\n }\n return true;\n}" },
|
|
53
|
+
{ title: 'Before Business Rule', category: 'Business Rule', description: 'Modify record before DB operation',
|
|
54
|
+
code: "(function executeRule(current, previous) {\n current.setValue('assignment_group', 'sys_id_here');\n\n if (current.priority > 3) {\n current.setAbortAction(true);\n gs.addErrorMessage('Priority must be 3 or higher');\n }\n})(current, previous);" },
|
|
55
|
+
{ title: 'After Business Rule', category: 'Business Rule', description: 'Execute logic after DB operation',
|
|
56
|
+
code: "(function executeRule(current, previous) {\n var task = new GlideRecord('task');\n task.initialize();\n task.short_description = 'Follow-up for ' + current.number;\n task.parent = current.sys_id;\n task.insert();\n\n gs.eventQueue('custom.event', current, current.getValue('assigned_to'), current.getValue('number'));\n})(current, previous);" },
|
|
57
|
+
{ title: 'Async Business Rule', category: 'Business Rule', description: 'Background processing after commit',
|
|
58
|
+
code: "(function executeRule(current, previous) {\n var restMessage = new sn_ws.RESTMessageV2('OutboundService', 'POST');\n restMessage.setStringParameterNoEscape('body', JSON.stringify({\n number: current.getValue('number'),\n state: current.getValue('state')\n }));\n var response = restMessage.execute();\n gs.info('External call response: ' + response.getStatusCode());\n})(current, previous);" },
|
|
59
|
+
{ title: 'GlideAjax (Client + Server)', category: 'Patterns', description: 'Client-to-server AJAX pattern',
|
|
60
|
+
code: "// === CLIENT SCRIPT ===\nvar ga = new GlideAjax('MyScriptInclude');\nga.addParam('sysparm_name', 'myMethod');\nga.addParam('sysparm_param1', g_form.getValue('field'));\nga.getXMLAnswer(function(answer) {\n var result = JSON.parse(answer);\n g_form.setValue('target_field', result.value);\n});\n\n// === SCRIPT INCLUDE (client-callable) ===\nvar MyScriptInclude = Class.create();\nMyScriptInclude.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n myMethod: function() {\n var param = this.getParameter('sysparm_param1');\n return JSON.stringify({ value: 'processed: ' + param });\n },\n type: 'MyScriptInclude'\n});" },
|
|
61
|
+
{ title: 'REST Message (Outbound)', category: 'REST', description: 'Make outbound REST call from server',
|
|
62
|
+
code: "try {\n var restMessage = new sn_ws.RESTMessageV2('ServiceName', 'POST');\n restMessage.setRequestHeader('Content-Type', 'application/json');\n restMessage.setRequestBody(JSON.stringify({\n key: 'value'\n }));\n\n var response = restMessage.execute();\n var statusCode = response.getStatusCode();\n var body = JSON.parse(response.getBody());\n gs.info('Response: ' + statusCode);\n} catch (e) {\n gs.error('REST call failed: ' + e.message);\n}" },
|
|
63
|
+
{ title: 'Scripted REST Resource', category: 'REST', description: 'Handle inbound REST API requests',
|
|
64
|
+
code: "(function process(request, response) {\n var body = request.body.data;\n\n if (!body.number) {\n response.setStatus(400);\n response.setBody({ error: 'number is required' });\n return;\n }\n\n var gr = new GlideRecord('incident');\n if (gr.get('number', body.number)) {\n response.setStatus(200);\n response.setBody({\n sys_id: gr.getUniqueValue(),\n state: gr.getValue('state')\n });\n } else {\n response.setStatus(404);\n response.setBody({ error: 'Not found' });\n }\n})(request, response);" },
|
|
65
|
+
{ title: 'Fix Script Template', category: 'Patterns', description: 'One-time data fix script with logging',
|
|
66
|
+
code: "// Fix Script: Description of what this fixes\n// Author: Your Name | Date: YYYY-MM-DD\n\nvar count = 0;\nvar gr = new GlideRecord('table_name');\ngr.addEncodedQuery('your_query_here');\ngr.query();\ngs.info('Fix Script: Found ' + gr.getRowCount() + ' records to process');\n\nwhile (gr.next()) {\n gr.setValue('field', 'new_value');\n gr.setWorkflow(false);\n gr.autoSysFields(false);\n gr.update();\n count++;\n}\ngs.info('Fix Script: Updated ' + count + ' records');" },
|
|
67
|
+
{ title: 'Scheduled Job Template', category: 'Patterns', description: 'Scheduled script execution pattern',
|
|
68
|
+
code: "var batchSize = 1000;\nvar processed = 0;\n\nvar gr = new GlideRecord('table_name');\ngr.addEncodedQuery('active=true^sys_updated_onRELATIVELE@dayofweek@ago@7');\ngr.setLimit(batchSize);\ngr.query();\n\nwhile (gr.next()) {\n try {\n // Process each record\n processed++;\n } catch (e) {\n gs.error('Scheduled Job error on ' + gr.getUniqueValue() + ': ' + e.message);\n }\n}\n\ngs.info('Scheduled Job: Processed ' + processed + '/' + gr.getRowCount() + ' records');" },
|
|
69
|
+
{ title: 'ACL Script', category: 'Patterns', description: 'Access control list script template',
|
|
70
|
+
code: "answer = false;\n\nif (gs.hasRole('admin') || gs.hasRole('itil')) {\n answer = true;\n}\nelse if (current.assigned_to == gs.getUserID()) {\n answer = true;\n}\nelse if (gs.getUser().isMemberOf(current.assignment_group)) {\n answer = true;\n}" },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
// ─── State ─────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
var customTemplates = [];
|
|
76
|
+
var panel = null;
|
|
77
|
+
var isOpen = false;
|
|
78
|
+
var isFloating = false;
|
|
79
|
+
var editingIdx = -1; // -1 = new, >= 0 = editing custom template index
|
|
80
|
+
var showingForm = false;
|
|
81
|
+
|
|
82
|
+
// Load custom templates from storage
|
|
83
|
+
chrome.storage.local.get({ customTemplates: [] }, function(data) {
|
|
84
|
+
customTemplates = data.customTemplates || [];
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ─── Utilities ─────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
function esc(str) {
|
|
90
|
+
var d = document.createElement('div');
|
|
91
|
+
d.textContent = str;
|
|
92
|
+
return d.innerHTML;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function escAttr(str) {
|
|
96
|
+
return str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getAllTemplates() {
|
|
100
|
+
return BUILT_IN_TEMPLATES.concat(customTemplates.map(function(t) {
|
|
101
|
+
return Object.assign({}, t, { isCustom: true });
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getCategories() {
|
|
106
|
+
var cats = [];
|
|
107
|
+
getAllTemplates().forEach(function(t) {
|
|
108
|
+
if (cats.indexOf(t.category) === -1) cats.push(t.category);
|
|
109
|
+
});
|
|
110
|
+
return cats;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ─── Syntax Highlighter ────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
function highlightCode(code) {
|
|
116
|
+
var tokens = [];
|
|
117
|
+
var i = 0;
|
|
118
|
+
while (i < code.length) {
|
|
119
|
+
// Single-line comment
|
|
120
|
+
if (code[i] === '/' && code[i + 1] === '/') {
|
|
121
|
+
var end = code.indexOf('\n', i);
|
|
122
|
+
if (end === -1) end = code.length;
|
|
123
|
+
tokens.push({ t: 'c', s: code.substring(i, end) }); i = end; continue;
|
|
124
|
+
}
|
|
125
|
+
// Multi-line comment
|
|
126
|
+
if (code[i] === '/' && code[i + 1] === '*') {
|
|
127
|
+
var end = code.indexOf('*/', i + 2);
|
|
128
|
+
if (end === -1) end = code.length; else end += 2;
|
|
129
|
+
tokens.push({ t: 'c', s: code.substring(i, end) }); i = end; continue;
|
|
130
|
+
}
|
|
131
|
+
// String
|
|
132
|
+
if (code[i] === "'" || code[i] === '"') {
|
|
133
|
+
var q = code[i], end = i + 1;
|
|
134
|
+
while (end < code.length && code[end] !== q) { if (code[end] === '\\') end++; end++; }
|
|
135
|
+
tokens.push({ t: 's', s: code.substring(i, end + 1) }); i = end + 1; continue;
|
|
136
|
+
}
|
|
137
|
+
// Word
|
|
138
|
+
if (/[a-zA-Z_$]/.test(code[i])) {
|
|
139
|
+
var end = i + 1;
|
|
140
|
+
while (end < code.length && /[a-zA-Z0-9_$]/.test(code[end])) end++;
|
|
141
|
+
var word = code.substring(i, end);
|
|
142
|
+
if (SN_APIS.has(word)) tokens.push({ t: 'a', s: word });
|
|
143
|
+
else if (JS_KEYWORDS.has(word)) tokens.push({ t: 'k', s: word });
|
|
144
|
+
else tokens.push({ t: 'p', s: word });
|
|
145
|
+
i = end; continue;
|
|
146
|
+
}
|
|
147
|
+
// Number
|
|
148
|
+
if (/[0-9]/.test(code[i])) {
|
|
149
|
+
var end = i + 1;
|
|
150
|
+
while (end < code.length && /[0-9.]/.test(code[end])) end++;
|
|
151
|
+
tokens.push({ t: 'n', s: code.substring(i, end) }); i = end; continue;
|
|
152
|
+
}
|
|
153
|
+
tokens.push({ t: 'p', s: code[i] }); i++;
|
|
154
|
+
}
|
|
155
|
+
var cls = { c: 'nk-hl-comment', s: 'nk-hl-string', k: 'nk-hl-keyword', a: 'nk-hl-api', n: 'nk-hl-number' };
|
|
156
|
+
return tokens.map(function(tk) {
|
|
157
|
+
var e = esc(tk.s);
|
|
158
|
+
return cls[tk.t] ? '<span class="' + cls[tk.t] + '">' + e + '</span>' : e;
|
|
159
|
+
}).join('');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ─── Theme ─────────────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
function toggleTheme() {
|
|
165
|
+
if (typeof window.nowaikitToggleTheme === 'function') {
|
|
166
|
+
var t = window.nowaikitToggleTheme();
|
|
167
|
+
updateThemeBtn(t);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function updateThemeBtn(theme) {
|
|
172
|
+
var btn = panel && panel.querySelector('#nk-tpl-theme');
|
|
173
|
+
if (!btn) return;
|
|
174
|
+
btn.title = theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode';
|
|
175
|
+
btn.innerHTML = theme === 'dark'
|
|
176
|
+
? '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>'
|
|
177
|
+
: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ─── Panel Creation ────────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
function createPanel() {
|
|
183
|
+
if (panel) return panel;
|
|
184
|
+
injectStyles();
|
|
185
|
+
|
|
186
|
+
panel = document.createElement('div');
|
|
187
|
+
panel.id = 'nk-tpl-panel';
|
|
188
|
+
panel.innerHTML = buildPanelHTML();
|
|
189
|
+
document.body.appendChild(panel);
|
|
190
|
+
|
|
191
|
+
// Resize handle
|
|
192
|
+
var resizeHandle = document.createElement('div');
|
|
193
|
+
resizeHandle.className = 'nk-tpl-resize';
|
|
194
|
+
panel.appendChild(resizeHandle);
|
|
195
|
+
initResize(resizeHandle);
|
|
196
|
+
initDrag();
|
|
197
|
+
|
|
198
|
+
bindEvents();
|
|
199
|
+
renderTemplates(getAllTemplates());
|
|
200
|
+
|
|
201
|
+
// Set theme button state
|
|
202
|
+
var theme = (typeof window.nowaikitGetTheme === 'function') ? window.nowaikitGetTheme() : 'dark';
|
|
203
|
+
updateThemeBtn(theme);
|
|
204
|
+
|
|
205
|
+
return panel;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function buildPanelHTML() {
|
|
209
|
+
var cats = getCategories();
|
|
210
|
+
var chipsHTML = '<button class="nk-tpl-chip active" data-cat="all">All</button>';
|
|
211
|
+
cats.forEach(function(cat) {
|
|
212
|
+
chipsHTML += '<button class="nk-tpl-chip" data-cat="' + escAttr(cat) + '">' + esc(cat) + '</button>';
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return '' +
|
|
216
|
+
'<div class="nk-tpl-header">' +
|
|
217
|
+
'<div class="nk-tpl-header-left">' +
|
|
218
|
+
'<svg width="22" height="22" viewBox="0 0 512 512" style="flex-shrink:0"><defs><linearGradient id="nkTplGrad" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#00F0C0"/><stop offset="50%" stop-color="#00D4AA"/><stop offset="100%" stop-color="#0F4C81"/></linearGradient></defs><rect width="512" height="512" rx="128" fill="url(#nkTplGrad)"/><g transform="translate(256,256) scale(9.13) translate(-22,-23)"><path d="M5 39V7l15 27V7" stroke="#fff" stroke-width="5.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/><path d="M34 4l2 7 6 2-6 2-2 7-2-7-6-2 6-2z" fill="#fff" opacity="0.9"><animate attributeName="opacity" values="0.9;0.4;0.9" dur="2s" repeatCount="indefinite"/></path><circle cx="34" cy="34" r="4.5" fill="#fff" opacity="0.8"><animate attributeName="opacity" values="0.8;0.4;0.8" dur="2s" repeatCount="indefinite" begin="0.3s"/></circle></g></svg>' +
|
|
219
|
+
'<span class="nk-tpl-title"><span class="nk-tpl-logo-now">Now</span><span class="nk-tpl-logo-ai">AI</span><span class="nk-tpl-logo-kit">Kit</span><span class="nk-tpl-logo-suffix"> Templates</span></span>' +
|
|
220
|
+
'</div>' +
|
|
221
|
+
'<div class="nk-tpl-header-actions">' +
|
|
222
|
+
'<button class="nk-tpl-hbtn" id="nk-tpl-add" title="Create template"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></button>' +
|
|
223
|
+
'<button class="nk-tpl-hbtn" id="nk-tpl-theme" title="Toggle theme"></button>' +
|
|
224
|
+
'<button class="nk-tpl-hbtn" id="nk-tpl-float" title="Float window"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><rect x="7" y="7" width="10" height="10" rx="1" fill="currentColor" opacity="0.15"/></svg></button>' +
|
|
225
|
+
'<button class="nk-tpl-hbtn" id="nk-tpl-close" title="Close (Esc)"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>' +
|
|
226
|
+
'</div>' +
|
|
227
|
+
'</div>' +
|
|
228
|
+
'<div class="nk-tpl-search-bar">' +
|
|
229
|
+
'<input type="text" id="nk-tpl-search" placeholder="Search templates..." autocomplete="off" />' +
|
|
230
|
+
'</div>' +
|
|
231
|
+
'<div class="nk-tpl-chips">' + chipsHTML + '</div>' +
|
|
232
|
+
'<div class="nk-tpl-list" id="nk-tpl-list"></div>' +
|
|
233
|
+
buildFormHTML();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ─── Create/Edit Form ─────────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
function buildFormHTML() {
|
|
239
|
+
var catOpts = '<option value="Custom">Custom</option>';
|
|
240
|
+
['GlideRecord','GlideQuery','Client Script','Business Rule','Patterns','REST'].forEach(function(c) {
|
|
241
|
+
catOpts += '<option value="' + escAttr(c) + '">' + esc(c) + '</option>';
|
|
242
|
+
});
|
|
243
|
+
return '' +
|
|
244
|
+
'<div class="nk-tpl-form" id="nk-tpl-form" style="display:none">' +
|
|
245
|
+
'<div class="nk-tpl-form-title" id="nk-tpl-form-heading">Create Template</div>' +
|
|
246
|
+
'<div class="nk-tpl-fg">' +
|
|
247
|
+
'<label>Title</label>' +
|
|
248
|
+
'<input type="text" id="nk-tpl-f-title" placeholder="Template title">' +
|
|
249
|
+
'</div>' +
|
|
250
|
+
'<div class="nk-tpl-fg">' +
|
|
251
|
+
'<label>Category</label>' +
|
|
252
|
+
'<select id="nk-tpl-f-cat">' + catOpts + '</select>' +
|
|
253
|
+
'</div>' +
|
|
254
|
+
'<div class="nk-tpl-fg">' +
|
|
255
|
+
'<label>Description</label>' +
|
|
256
|
+
'<input type="text" id="nk-tpl-f-desc" placeholder="Brief description">' +
|
|
257
|
+
'</div>' +
|
|
258
|
+
'<div class="nk-tpl-fg">' +
|
|
259
|
+
'<label>Code</label>' +
|
|
260
|
+
'<textarea id="nk-tpl-f-code" placeholder="Paste your code here..." rows="8"></textarea>' +
|
|
261
|
+
'</div>' +
|
|
262
|
+
'<div class="nk-tpl-form-btns">' +
|
|
263
|
+
'<button class="nk-tpl-fbtn nk-tpl-fbtn-save" id="nk-tpl-f-save">Save</button>' +
|
|
264
|
+
'<button class="nk-tpl-fbtn nk-tpl-fbtn-cancel" id="nk-tpl-f-cancel">Cancel</button>' +
|
|
265
|
+
'</div>' +
|
|
266
|
+
'</div>';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function showForm(tpl) {
|
|
270
|
+
showingForm = true;
|
|
271
|
+
var form = panel.querySelector('#nk-tpl-form');
|
|
272
|
+
var list = panel.querySelector('#nk-tpl-list');
|
|
273
|
+
var chips = panel.querySelector('.nk-tpl-chips');
|
|
274
|
+
var search = panel.querySelector('.nk-tpl-search-bar');
|
|
275
|
+
form.style.display = 'flex';
|
|
276
|
+
list.style.display = 'none';
|
|
277
|
+
chips.style.display = 'none';
|
|
278
|
+
search.style.display = 'none';
|
|
279
|
+
|
|
280
|
+
panel.querySelector('#nk-tpl-form-heading').textContent = tpl ? 'Edit Template' : 'Create Template';
|
|
281
|
+
panel.querySelector('#nk-tpl-f-title').value = tpl ? tpl.title : '';
|
|
282
|
+
panel.querySelector('#nk-tpl-f-cat').value = tpl ? tpl.category : 'Custom';
|
|
283
|
+
panel.querySelector('#nk-tpl-f-desc').value = tpl ? tpl.description : '';
|
|
284
|
+
panel.querySelector('#nk-tpl-f-code').value = tpl ? tpl.code : '';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function hideForm() {
|
|
288
|
+
showingForm = false;
|
|
289
|
+
editingIdx = -1;
|
|
290
|
+
var form = panel.querySelector('#nk-tpl-form');
|
|
291
|
+
var list = panel.querySelector('#nk-tpl-list');
|
|
292
|
+
var chips = panel.querySelector('.nk-tpl-chips');
|
|
293
|
+
var search = panel.querySelector('.nk-tpl-search-bar');
|
|
294
|
+
form.style.display = 'none';
|
|
295
|
+
list.style.display = 'flex';
|
|
296
|
+
chips.style.display = 'flex';
|
|
297
|
+
search.style.display = 'block';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function saveTemplate() {
|
|
301
|
+
var title = (panel.querySelector('#nk-tpl-f-title').value || '').trim();
|
|
302
|
+
var cat = panel.querySelector('#nk-tpl-f-cat').value;
|
|
303
|
+
var desc = (panel.querySelector('#nk-tpl-f-desc').value || '').trim();
|
|
304
|
+
var code = (panel.querySelector('#nk-tpl-f-code').value || '').trim();
|
|
305
|
+
if (!title || !code) { showToast('Title and code are required', 'warn'); return; }
|
|
306
|
+
|
|
307
|
+
var tpl = { title: title, category: cat, description: desc, code: code };
|
|
308
|
+
if (editingIdx >= 0 && editingIdx < customTemplates.length) {
|
|
309
|
+
customTemplates[editingIdx] = tpl;
|
|
310
|
+
} else {
|
|
311
|
+
customTemplates.push(tpl);
|
|
312
|
+
}
|
|
313
|
+
chrome.storage.local.set({ customTemplates: customTemplates });
|
|
314
|
+
hideForm();
|
|
315
|
+
rebuildChips();
|
|
316
|
+
filterTemplates(panel.querySelector('#nk-tpl-search').value);
|
|
317
|
+
showToast('Template saved');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function deleteTemplate(customIdx) {
|
|
321
|
+
customTemplates.splice(customIdx, 1);
|
|
322
|
+
chrome.storage.local.set({ customTemplates: customTemplates });
|
|
323
|
+
rebuildChips();
|
|
324
|
+
filterTemplates(panel.querySelector('#nk-tpl-search').value);
|
|
325
|
+
showToast('Template deleted');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function rebuildChips() {
|
|
329
|
+
var container = panel.querySelector('.nk-tpl-chips');
|
|
330
|
+
var cats = getCategories();
|
|
331
|
+
var html = '<button class="nk-tpl-chip active" data-cat="all">All</button>';
|
|
332
|
+
cats.forEach(function(cat) {
|
|
333
|
+
html += '<button class="nk-tpl-chip" data-cat="' + escAttr(cat) + '">' + esc(cat) + '</button>';
|
|
334
|
+
});
|
|
335
|
+
container.innerHTML = html;
|
|
336
|
+
// Re-bind chip clicks
|
|
337
|
+
container.querySelectorAll('.nk-tpl-chip').forEach(function(chip) {
|
|
338
|
+
chip.addEventListener('click', function() {
|
|
339
|
+
var wasActive = chip.classList.contains('active');
|
|
340
|
+
container.querySelectorAll('.nk-tpl-chip').forEach(function(c) { c.classList.remove('active'); });
|
|
341
|
+
if (!wasActive) chip.classList.add('active');
|
|
342
|
+
else container.querySelector('[data-cat="all"]').classList.add('active');
|
|
343
|
+
filterTemplates(panel.querySelector('#nk-tpl-search').value);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ─── Template Rendering ────────────────────────────────────────────────────
|
|
349
|
+
|
|
350
|
+
function renderTemplates(list) {
|
|
351
|
+
var container = panel.querySelector('#nk-tpl-list');
|
|
352
|
+
if (!container) return;
|
|
353
|
+
if (list.length === 0) {
|
|
354
|
+
container.innerHTML = '<div class="nk-tpl-empty">No templates match your search.</div>';
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
var html = '';
|
|
358
|
+
list.forEach(function(tpl, i) {
|
|
359
|
+
var isCustom = !!tpl.isCustom;
|
|
360
|
+
var customIdx = isCustom ? customTemplates.indexOf(tpl.isCustom ? tpl : null) : -1;
|
|
361
|
+
// For custom templates, find original index
|
|
362
|
+
if (isCustom) {
|
|
363
|
+
for (var ci = 0; ci < customTemplates.length; ci++) {
|
|
364
|
+
if (customTemplates[ci].title === tpl.title && customTemplates[ci].code === tpl.code) {
|
|
365
|
+
customIdx = ci; break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
html +=
|
|
371
|
+
'<div class="nk-tpl-card nk-tpl-collapsed">' +
|
|
372
|
+
'<div class="nk-tpl-card-top">' +
|
|
373
|
+
'<div class="nk-tpl-card-info">' +
|
|
374
|
+
'<span class="nk-tpl-card-chevron">▶</span>' +
|
|
375
|
+
'<span class="nk-tpl-card-title">' + esc(tpl.title) + '</span>' +
|
|
376
|
+
'<span class="nk-tpl-badge">' + esc(tpl.category) + '</span>' +
|
|
377
|
+
(isCustom ? '<span class="nk-tpl-badge nk-tpl-badge-custom">Custom</span>' : '') +
|
|
378
|
+
'</div>' +
|
|
379
|
+
'<div class="nk-tpl-card-btns">' +
|
|
380
|
+
'<button class="nk-tpl-btn nk-tpl-btn-copy" data-code-idx="' + i + '" title="Copy to clipboard">Copy</button>' +
|
|
381
|
+
'<button class="nk-tpl-btn nk-tpl-btn-run" data-code-idx="' + i + '" title="Copy & open Background Scripts">Run</button>' +
|
|
382
|
+
(isCustom ? '<button class="nk-tpl-btn nk-tpl-btn-edit" data-custom-idx="' + customIdx + '" title="Edit">Edit</button>' : '') +
|
|
383
|
+
(isCustom ? '<button class="nk-tpl-btn nk-tpl-btn-del" data-custom-idx="' + customIdx + '" title="Delete">Del</button>' : '') +
|
|
384
|
+
'</div>' +
|
|
385
|
+
'</div>' +
|
|
386
|
+
'<div class="nk-tpl-card-body">' +
|
|
387
|
+
(tpl.description ? '<div class="nk-tpl-card-desc">' + esc(tpl.description) + '</div>' : '') +
|
|
388
|
+
'<pre class="nk-tpl-code"><code>' + highlightCode(tpl.code) + '</code></pre>' +
|
|
389
|
+
'</div>' +
|
|
390
|
+
'</div>';
|
|
391
|
+
});
|
|
392
|
+
container.innerHTML = html;
|
|
393
|
+
// Store template refs for button handlers
|
|
394
|
+
container._templates = list;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function filterTemplates(query) {
|
|
398
|
+
var activeChip = panel.querySelector('.nk-tpl-chip.active');
|
|
399
|
+
var activeCat = activeChip ? activeChip.dataset.cat : 'all';
|
|
400
|
+
var q = (query || '').toLowerCase().trim();
|
|
401
|
+
var all = getAllTemplates();
|
|
402
|
+
|
|
403
|
+
var filtered = all.filter(function(tpl) {
|
|
404
|
+
if (activeCat !== 'all' && tpl.category !== activeCat) return false;
|
|
405
|
+
if (q) {
|
|
406
|
+
var searchable = (tpl.title + ' ' + tpl.category + ' ' + tpl.description).toLowerCase();
|
|
407
|
+
return searchable.indexOf(q) !== -1;
|
|
408
|
+
}
|
|
409
|
+
return true;
|
|
410
|
+
});
|
|
411
|
+
renderTemplates(filtered);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ─── Actions ───────────────────────────────────────────────────────────────
|
|
415
|
+
|
|
416
|
+
function copyCode(code, btn) {
|
|
417
|
+
navigator.clipboard.writeText(code).then(function() {
|
|
418
|
+
if (btn) {
|
|
419
|
+
var orig = btn.textContent;
|
|
420
|
+
btn.textContent = 'Copied!';
|
|
421
|
+
btn.classList.add('nk-tpl-btn-ok');
|
|
422
|
+
setTimeout(function() { btn.textContent = orig; btn.classList.remove('nk-tpl-btn-ok'); }, 1500);
|
|
423
|
+
}
|
|
424
|
+
}).catch(function() {
|
|
425
|
+
if (btn) {
|
|
426
|
+
btn.textContent = 'Failed';
|
|
427
|
+
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function openInBgScript(code) {
|
|
433
|
+
var url = (window.nowaikitState || {}).instanceUrl;
|
|
434
|
+
// Always copy to clipboard first (reliable, like SNUtils)
|
|
435
|
+
navigator.clipboard.writeText(code).then(function() {
|
|
436
|
+
if (!url) {
|
|
437
|
+
showToast('Script copied! Open Background Scripts and paste.', 'warn');
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
// Also store for auto-inject attempt (best effort)
|
|
441
|
+
chrome.storage.local.set({ nowaikitPendingScript: code }, function() {
|
|
442
|
+
window.open(url + '/sys.scripts.do', '_blank');
|
|
443
|
+
showToast('Script copied & page opening — paste with Ctrl+V');
|
|
444
|
+
});
|
|
445
|
+
}).catch(function() {
|
|
446
|
+
if (!url) {
|
|
447
|
+
showToast('Could not copy script', 'warn');
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
chrome.storage.local.set({ nowaikitPendingScript: code }, function() {
|
|
451
|
+
window.open(url + '/sys.scripts.do', '_blank');
|
|
452
|
+
showToast('Opening Background Scripts');
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function showToast(msg, type) {
|
|
458
|
+
if (typeof window.showToast === 'function') { window.showToast(msg, type); return; }
|
|
459
|
+
var t = document.createElement('div');
|
|
460
|
+
t.className = 'nowaikit-toast' + (type === 'warn' ? ' nowaikit-toast-warn' : '');
|
|
461
|
+
t.textContent = msg;
|
|
462
|
+
document.body.appendChild(t);
|
|
463
|
+
requestAnimationFrame(function() { t.classList.add('nowaikit-toast-show'); });
|
|
464
|
+
setTimeout(function() {
|
|
465
|
+
t.classList.remove('nowaikit-toast-show');
|
|
466
|
+
setTimeout(function() { t.remove(); }, 300);
|
|
467
|
+
}, 2500);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// ─── Open / Close / Toggle ─────────────────────────────────────────────────
|
|
471
|
+
|
|
472
|
+
function openPanel() {
|
|
473
|
+
if (isOpen) return;
|
|
474
|
+
// Close other panels
|
|
475
|
+
if (typeof window.closeAISidebar === 'function') window.closeAISidebar();
|
|
476
|
+
if (typeof window.closeMainPanel === 'function') window.closeMainPanel();
|
|
477
|
+
createPanel();
|
|
478
|
+
isOpen = true;
|
|
479
|
+
panel.classList.add('nk-tpl-open');
|
|
480
|
+
var s = panel.querySelector('#nk-tpl-search');
|
|
481
|
+
if (s) setTimeout(function() { s.focus(); }, 150);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function closePanel() {
|
|
485
|
+
if (!isOpen || !panel) return;
|
|
486
|
+
isOpen = false;
|
|
487
|
+
panel.classList.remove('nk-tpl-open');
|
|
488
|
+
if (showingForm) hideForm();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function togglePanel() {
|
|
492
|
+
if (isOpen) closePanel(); else openPanel();
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ─── Floating Mode ─────────────────────────────────────────────────────────
|
|
496
|
+
|
|
497
|
+
function toggleFloat() {
|
|
498
|
+
if (!panel) return;
|
|
499
|
+
isFloating = !isFloating;
|
|
500
|
+
panel.classList.toggle('nk-tpl-floating', isFloating);
|
|
501
|
+
var btn = panel.querySelector('#nk-tpl-float');
|
|
502
|
+
if (btn) {
|
|
503
|
+
btn.title = isFloating ? 'Dock to side' : 'Float window';
|
|
504
|
+
btn.innerHTML = isFloating
|
|
505
|
+
? '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="18" rx="2"/><line x1="15" y1="3" x2="15" y2="21"/></svg>'
|
|
506
|
+
: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><rect x="7" y="7" width="10" height="10" rx="1" fill="currentColor" opacity="0.15"/></svg>';
|
|
507
|
+
}
|
|
508
|
+
if (!isFloating) {
|
|
509
|
+
panel.style.left = '';
|
|
510
|
+
panel.style.top = '';
|
|
511
|
+
panel.style.right = '';
|
|
512
|
+
panel.style.width = '';
|
|
513
|
+
panel.style.height = '';
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function initDrag() {
|
|
518
|
+
var isDragging = false, ox = 0, oy = 0;
|
|
519
|
+
document.addEventListener('mousedown', function(e) {
|
|
520
|
+
if (!isFloating || !panel) return;
|
|
521
|
+
var header = panel.querySelector('.nk-tpl-header');
|
|
522
|
+
if (!header || !header.contains(e.target)) return;
|
|
523
|
+
if (e.target.closest('.nk-tpl-hbtn')) return;
|
|
524
|
+
isDragging = true;
|
|
525
|
+
var r = panel.getBoundingClientRect();
|
|
526
|
+
ox = e.clientX - r.left; oy = e.clientY - r.top;
|
|
527
|
+
e.preventDefault();
|
|
528
|
+
});
|
|
529
|
+
document.addEventListener('mousemove', function(e) {
|
|
530
|
+
if (!isDragging) return;
|
|
531
|
+
panel.style.left = Math.max(0, e.clientX - ox) + 'px';
|
|
532
|
+
panel.style.top = Math.max(0, e.clientY - oy) + 'px';
|
|
533
|
+
panel.style.right = 'auto';
|
|
534
|
+
});
|
|
535
|
+
document.addEventListener('mouseup', function() { isDragging = false; });
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function initResize(handle) {
|
|
539
|
+
var isResizing = false, startX = 0, startW = 0;
|
|
540
|
+
handle.addEventListener('mousedown', function(e) {
|
|
541
|
+
if (isFloating) return;
|
|
542
|
+
isResizing = true; startX = e.clientX; startW = panel.offsetWidth;
|
|
543
|
+
handle.classList.add('active');
|
|
544
|
+
e.preventDefault();
|
|
545
|
+
});
|
|
546
|
+
document.addEventListener('mousemove', function(e) {
|
|
547
|
+
if (!isResizing) return;
|
|
548
|
+
var nw = Math.max(340, Math.min(startW + (startX - e.clientX), window.innerWidth * 0.8));
|
|
549
|
+
panel.style.width = nw + 'px';
|
|
550
|
+
});
|
|
551
|
+
document.addEventListener('mouseup', function() {
|
|
552
|
+
if (isResizing) { isResizing = false; handle.classList.remove('active'); }
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// ─── Event Binding ─────────────────────────────────────────────────────────
|
|
557
|
+
|
|
558
|
+
function bindEvents() {
|
|
559
|
+
panel.querySelector('#nk-tpl-close').addEventListener('click', closePanel);
|
|
560
|
+
panel.querySelector('#nk-tpl-theme').addEventListener('click', toggleTheme);
|
|
561
|
+
panel.querySelector('#nk-tpl-float').addEventListener('click', toggleFloat);
|
|
562
|
+
panel.querySelector('#nk-tpl-add').addEventListener('click', function() {
|
|
563
|
+
editingIdx = -1; showForm(null);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// Search
|
|
567
|
+
var searchInput = panel.querySelector('#nk-tpl-search');
|
|
568
|
+
searchInput.addEventListener('input', function() { filterTemplates(this.value); });
|
|
569
|
+
|
|
570
|
+
// Chips
|
|
571
|
+
panel.querySelector('.nk-tpl-chips').addEventListener('click', function(e) {
|
|
572
|
+
var chip = e.target.closest('.nk-tpl-chip');
|
|
573
|
+
if (!chip) return;
|
|
574
|
+
var wasActive = chip.classList.contains('active');
|
|
575
|
+
panel.querySelectorAll('.nk-tpl-chip').forEach(function(c) { c.classList.remove('active'); });
|
|
576
|
+
if (!wasActive) chip.classList.add('active');
|
|
577
|
+
else panel.querySelector('[data-cat="all"]').classList.add('active');
|
|
578
|
+
filterTemplates(searchInput.value);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// Template list actions (delegated)
|
|
582
|
+
panel.querySelector('#nk-tpl-list').addEventListener('click', function(e) {
|
|
583
|
+
var templates = this._templates || getAllTemplates();
|
|
584
|
+
var copyBtn = e.target.closest('.nk-tpl-btn-copy');
|
|
585
|
+
if (copyBtn) {
|
|
586
|
+
var idx = parseInt(copyBtn.dataset.codeIdx, 10);
|
|
587
|
+
if (templates[idx]) copyCode(templates[idx].code, copyBtn);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
var runBtn = e.target.closest('.nk-tpl-btn-run');
|
|
591
|
+
if (runBtn) {
|
|
592
|
+
var idx = parseInt(runBtn.dataset.codeIdx, 10);
|
|
593
|
+
if (templates[idx]) openInBgScript(templates[idx].code);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
var editBtn = e.target.closest('.nk-tpl-btn-edit');
|
|
597
|
+
if (editBtn) {
|
|
598
|
+
var ci = parseInt(editBtn.dataset.customIdx, 10);
|
|
599
|
+
if (customTemplates[ci]) { editingIdx = ci; showForm(customTemplates[ci]); }
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
var delBtn = e.target.closest('.nk-tpl-btn-del');
|
|
603
|
+
if (delBtn) {
|
|
604
|
+
var ci = parseInt(delBtn.dataset.customIdx, 10);
|
|
605
|
+
if (ci >= 0) deleteTemplate(ci);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
// Expand/collapse card on click (not on button clicks which returned above)
|
|
609
|
+
var card = e.target.closest('.nk-tpl-card');
|
|
610
|
+
if (card) {
|
|
611
|
+
card.classList.toggle('nk-tpl-collapsed');
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// Form save/cancel
|
|
616
|
+
panel.querySelector('#nk-tpl-f-save').addEventListener('click', saveTemplate);
|
|
617
|
+
panel.querySelector('#nk-tpl-f-cancel').addEventListener('click', hideForm);
|
|
618
|
+
|
|
619
|
+
// Escape to close
|
|
620
|
+
panel.addEventListener('keydown', function(e) {
|
|
621
|
+
if (e.key === 'Escape') { e.preventDefault(); if (showingForm) hideForm(); else closePanel(); }
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// ─── Keyboard Shortcut ─────────────────────────────────────────────────────
|
|
626
|
+
|
|
627
|
+
document.addEventListener('keydown', function(e) {
|
|
628
|
+
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'T') {
|
|
629
|
+
e.preventDefault(); togglePanel();
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// Message from popup
|
|
634
|
+
chrome.runtime.onMessage.addListener(function(message) {
|
|
635
|
+
if (message.action === 'nowaikit-open-code-templates') togglePanel();
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// ─── Styles ────────────────────────────────────────────────────────────────
|
|
639
|
+
|
|
640
|
+
function injectStyles() {
|
|
641
|
+
if (document.getElementById('nk-tpl-styles')) return;
|
|
642
|
+
var s = document.createElement('style');
|
|
643
|
+
s.id = 'nk-tpl-styles';
|
|
644
|
+
s.textContent =
|
|
645
|
+
/* Reset — prevent ServiceNow styles from leaking in */
|
|
646
|
+
'#nk-tpl-panel,#nk-tpl-panel *{box-sizing:border-box;margin:0;padding:0;line-height:normal;}' +
|
|
647
|
+
|
|
648
|
+
/* Panel — sidebar mode */
|
|
649
|
+
'#nk-tpl-panel{position:fixed;top:0;right:-480px;width:480px;max-width:100vw;height:100vh;' +
|
|
650
|
+
'background:#0B1020;border-left:1px solid #1e2a3e;z-index:2147483646;' +
|
|
651
|
+
'display:flex;flex-direction:column;overflow:hidden;' +
|
|
652
|
+
'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;' +
|
|
653
|
+
'font-size:13px;color:#e0e5ec;box-shadow:-4px 0 20px rgba(0,0,0,.4);' +
|
|
654
|
+
'transition:right .3s cubic-bezier(.4,0,.2,1);}' +
|
|
655
|
+
'#nk-tpl-panel.nk-tpl-open{right:0;}' +
|
|
656
|
+
|
|
657
|
+
/* Floating mode */
|
|
658
|
+
'#nk-tpl-panel.nk-tpl-floating{top:40px;right:40px;width:540px;height:75vh;' +
|
|
659
|
+
'border-radius:12px;border:1px solid #1e2a3e;box-shadow:0 12px 40px rgba(0,0,0,.6);' +
|
|
660
|
+
'resize:both;overflow:hidden;min-width:340px;min-height:280px;}' +
|
|
661
|
+
'#nk-tpl-panel.nk-tpl-floating .nk-tpl-header{cursor:grab;border-radius:12px 12px 0 0;}' +
|
|
662
|
+
'#nk-tpl-panel.nk-tpl-floating .nk-tpl-header:active{cursor:grabbing;}' +
|
|
663
|
+
'#nk-tpl-panel.nk-tpl-floating .nk-tpl-resize{display:none;}' +
|
|
664
|
+
|
|
665
|
+
/* Resize handle (sidebar mode only) */
|
|
666
|
+
'.nk-tpl-resize{position:absolute;top:0;left:-3px;width:6px;height:100%;cursor:ew-resize;z-index:10;}' +
|
|
667
|
+
'.nk-tpl-resize:hover,.nk-tpl-resize.active{background:rgba(0,212,170,.3);}' +
|
|
668
|
+
|
|
669
|
+
/* Header */
|
|
670
|
+
'.nk-tpl-header{display:flex;align-items:center;justify-content:space-between;' +
|
|
671
|
+
'padding:16px 20px 16px 24px;background:linear-gradient(135deg,#0a2e5c 0%,#065f50 100%);flex-shrink:0;min-height:54px;}' +
|
|
672
|
+
'.nk-tpl-header-left{display:flex;align-items:center;gap:12px;color:#fff;min-width:0;}' +
|
|
673
|
+
'.nk-tpl-title{font-size:16px;font-weight:800;color:#fff;white-space:nowrap;letter-spacing:-0.02em;}' +
|
|
674
|
+
'.nk-tpl-logo-now{background:linear-gradient(135deg,#fff 0%,#B8C4D4 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;}' +
|
|
675
|
+
'.nk-tpl-logo-ai{background:linear-gradient(90deg,#00F0C0,#00D4AA,#0F4C81,#00D4AA,#00F0C0);background-size:300% 100%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;animation:nkTplShimmer 4s ease-in-out infinite;}' +
|
|
676
|
+
'.nk-tpl-logo-kit{background:linear-gradient(135deg,#A0AEBE 0%,#6B7A8E 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;}' +
|
|
677
|
+
'.nk-tpl-logo-suffix{font-weight:400;font-size:0.75em;-webkit-text-fill-color:rgba(255,255,255,0.5);letter-spacing:0.04em;}' +
|
|
678
|
+
'@keyframes nkTplShimmer{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}' +
|
|
679
|
+
'.nk-tpl-header-actions{display:flex;gap:6px;flex-shrink:0;}' +
|
|
680
|
+
'.nk-tpl-hbtn{width:32px;height:32px;border:none;background:rgba(255,255,255,.12);' +
|
|
681
|
+
'color:#fff;border-radius:6px;cursor:pointer;display:inline-flex;align-items:center;' +
|
|
682
|
+
'justify-content:center;transition:background .2s;flex-shrink:0;}' +
|
|
683
|
+
'.nk-tpl-hbtn:hover{background:rgba(255,255,255,.25);}' +
|
|
684
|
+
|
|
685
|
+
/* Search */
|
|
686
|
+
'.nk-tpl-search-bar{padding:12px 20px 12px 24px;flex-shrink:0;border-bottom:1px solid #1e2a3e;}' +
|
|
687
|
+
'#nk-tpl-search{width:100%;background:#111828;border:1px solid #1e2a3e;border-radius:6px;' +
|
|
688
|
+
'padding:10px 12px;color:#e0e5ec;font-size:13px;outline:none;box-sizing:border-box;}' +
|
|
689
|
+
'#nk-tpl-search:focus{border-color:#00D4AA;box-shadow:0 0 0 2px rgba(0,212,170,.12);}' +
|
|
690
|
+
'#nk-tpl-search::placeholder{color:#5a6a80;}' +
|
|
691
|
+
|
|
692
|
+
/* Category chips */
|
|
693
|
+
'.nk-tpl-chips{display:flex;flex-wrap:wrap;gap:6px;padding:10px 20px 10px 24px;flex-shrink:0;border-bottom:1px solid #1e2a3e;}' +
|
|
694
|
+
'.nk-tpl-chip{background:#111828;border:1px solid #1e2a3e;border-radius:12px;' +
|
|
695
|
+
'padding:4px 12px;font-size:11px;color:#6b7a90;cursor:pointer;transition:all .15s;white-space:nowrap;}' +
|
|
696
|
+
'.nk-tpl-chip:hover{border-color:#00D4AA;color:#00D4AA;}' +
|
|
697
|
+
'.nk-tpl-chip.active{background:rgba(0,212,170,.1);border-color:#00D4AA;color:#00D4AA;font-weight:600;}' +
|
|
698
|
+
|
|
699
|
+
/* Template list — scrollable container */
|
|
700
|
+
'.nk-tpl-list{display:none;}' +
|
|
701
|
+
'#nk-tpl-panel.nk-tpl-open .nk-tpl-list{display:flex;}' +
|
|
702
|
+
'#nk-tpl-list{flex:1;overflow-y:auto;overflow-x:hidden;padding:16px;flex-direction:column;gap:14px;min-height:0;}' +
|
|
703
|
+
'#nk-tpl-list::-webkit-scrollbar{width:5px;}' +
|
|
704
|
+
'#nk-tpl-list::-webkit-scrollbar-track{background:transparent;}' +
|
|
705
|
+
'#nk-tpl-list::-webkit-scrollbar-thumb{background:#1e2a3e;border-radius:3px;}' +
|
|
706
|
+
|
|
707
|
+
/* Template card — flex column, collapsible */
|
|
708
|
+
'.nk-tpl-card{background:#111828;border:1px solid #1e2a3e;border-radius:8px;' +
|
|
709
|
+
'overflow:hidden;transition:border-color .15s;display:flex;flex-direction:column;}' +
|
|
710
|
+
'.nk-tpl-card:hover{border-color:#2a3a52;}' +
|
|
711
|
+
|
|
712
|
+
/* Collapsed state */
|
|
713
|
+
'.nk-tpl-card.nk-tpl-collapsed .nk-tpl-card-body{display:none;}' +
|
|
714
|
+
'.nk-tpl-card.nk-tpl-collapsed .nk-tpl-card-top{border-bottom:none;}' +
|
|
715
|
+
|
|
716
|
+
/* Chevron indicator */
|
|
717
|
+
'.nk-tpl-card-chevron{font-size:9px;color:#6b7a90;transition:transform .2s;display:inline-block;flex-shrink:0;width:12px;text-align:center;}' +
|
|
718
|
+
'.nk-tpl-card:not(.nk-tpl-collapsed) .nk-tpl-card-chevron{transform:rotate(90deg);color:#00D4AA;}' +
|
|
719
|
+
|
|
720
|
+
/* Card body (description + code) */
|
|
721
|
+
'.nk-tpl-card-body{display:flex;flex-direction:column;padding-bottom:4px;}' +
|
|
722
|
+
|
|
723
|
+
/* Card header — wraps on narrow widths */
|
|
724
|
+
'.nk-tpl-card-top{display:flex;align-items:flex-start;justify-content:space-between;' +
|
|
725
|
+
'padding:12px 14px;border-bottom:1px solid #1e2a3e;gap:10px;flex-wrap:wrap;cursor:pointer;user-select:none;}' +
|
|
726
|
+
'.nk-tpl-card-info{display:flex;align-items:center;gap:8px;flex:1 1 auto;min-width:0;flex-wrap:wrap;}' +
|
|
727
|
+
'.nk-tpl-card-title{font-size:13px;font-weight:600;color:#e0e5ec;word-break:break-word;line-height:1.4;}' +
|
|
728
|
+
'.nk-tpl-badge{background:rgba(0,212,170,.08);color:#00D4AA;font-size:10px;font-weight:600;' +
|
|
729
|
+
'padding:2px 7px;border-radius:8px;white-space:nowrap;line-height:1.3;}' +
|
|
730
|
+
'.nk-tpl-badge-custom{background:rgba(240,160,48,.1);color:#f0a030;}' +
|
|
731
|
+
'.nk-tpl-card-btns{display:flex;gap:4px;flex-shrink:0;flex-wrap:wrap;}' +
|
|
732
|
+
|
|
733
|
+
/* Action buttons */
|
|
734
|
+
'.nk-tpl-btn{font-size:11px;font-weight:600;padding:4px 10px;border-radius:4px;cursor:pointer;' +
|
|
735
|
+
'border:1px solid transparent;transition:all .15s;font-family:inherit;white-space:nowrap;line-height:1.4;}' +
|
|
736
|
+
'.nk-tpl-btn-copy{background:rgba(0,212,170,.08);color:#00D4AA;border-color:rgba(0,212,170,.15);}' +
|
|
737
|
+
'.nk-tpl-btn-copy:hover{background:rgba(0,212,170,.18);}' +
|
|
738
|
+
'.nk-tpl-btn-run{background:rgba(15,76,129,.15);color:#5ba3d9;border-color:rgba(15,76,129,.25);}' +
|
|
739
|
+
'.nk-tpl-btn-run:hover{background:rgba(15,76,129,.28);}' +
|
|
740
|
+
'.nk-tpl-btn-edit{background:rgba(240,160,48,.08);color:#f0a030;border-color:rgba(240,160,48,.15);}' +
|
|
741
|
+
'.nk-tpl-btn-edit:hover{background:rgba(240,160,48,.18);}' +
|
|
742
|
+
'.nk-tpl-btn-del{background:rgba(255,100,100,.06);color:#ff6464;border-color:rgba(255,100,100,.12);}' +
|
|
743
|
+
'.nk-tpl-btn-del:hover{background:rgba(255,100,100,.15);}' +
|
|
744
|
+
'.nk-tpl-btn-ok{background:#00D4AA!important;color:#0B1020!important;border-color:#00D4AA!important;}' +
|
|
745
|
+
|
|
746
|
+
/* Card description — full text, no truncation */
|
|
747
|
+
'.nk-tpl-card-desc{padding:12px 16px 8px;font-size:12px;color:#6b7a90;line-height:1.6;word-break:break-word;}' +
|
|
748
|
+
|
|
749
|
+
/* Code block — scrollable, respects panel width */
|
|
750
|
+
'.nk-tpl-code{margin:6px 12px 12px;padding:16px 18px;background:#080e1a;border-radius:6px;' +
|
|
751
|
+
'font-family:"JetBrains Mono","Fira Code","Cascadia Code",Consolas,monospace;' +
|
|
752
|
+
'font-size:12px;line-height:1.8;overflow:auto;white-space:pre;' +
|
|
753
|
+
'max-height:320px;border:1px solid #1e2a3e;-webkit-overflow-scrolling:touch;}' +
|
|
754
|
+
'.nk-tpl-code code{font-family:inherit;font-size:inherit;color:#c8d0dc;display:block;word-break:normal;}' +
|
|
755
|
+
'.nk-tpl-code::-webkit-scrollbar{height:5px;width:5px;}' +
|
|
756
|
+
'.nk-tpl-code::-webkit-scrollbar-thumb{background:#1e2a3e;border-radius:3px;}' +
|
|
757
|
+
'.nk-tpl-code::-webkit-scrollbar-corner{background:transparent;}' +
|
|
758
|
+
|
|
759
|
+
/* Syntax highlighting */
|
|
760
|
+
'.nk-hl-keyword{color:#c678dd;font-weight:500;}' +
|
|
761
|
+
'.nk-hl-api{color:#00D4AA;font-weight:600;}' +
|
|
762
|
+
'.nk-hl-string{color:#98c379;}' +
|
|
763
|
+
'.nk-hl-comment{color:#5c6370;font-style:italic;}' +
|
|
764
|
+
'.nk-hl-number{color:#d19a66;}' +
|
|
765
|
+
|
|
766
|
+
/* Empty state */
|
|
767
|
+
'.nk-tpl-empty{text-align:center;padding:40px 16px;color:#5a6a80;font-size:13px;}' +
|
|
768
|
+
|
|
769
|
+
/* Create/Edit form */
|
|
770
|
+
'.nk-tpl-form{display:none;flex-direction:column;flex:1;overflow-y:auto;padding:14px;gap:10px;}' +
|
|
771
|
+
'.nk-tpl-form-title{font-size:15px;font-weight:700;color:#e0e5ec;padding-bottom:4px;}' +
|
|
772
|
+
'.nk-tpl-fg{display:flex;flex-direction:column;gap:3px;}' +
|
|
773
|
+
'.nk-tpl-fg label{font-size:10px;font-weight:600;color:#6b7a90;text-transform:uppercase;letter-spacing:.4px;}' +
|
|
774
|
+
'.nk-tpl-fg input,.nk-tpl-fg select,.nk-tpl-fg textarea{width:100%;padding:8px 10px;background:#111828;' +
|
|
775
|
+
'border:1px solid #1e2a3e;border-radius:5px;color:#e0e5ec;font-size:13px;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .2s;}' +
|
|
776
|
+
'.nk-tpl-fg input:focus,.nk-tpl-fg select:focus,.nk-tpl-fg textarea:focus{border-color:#00D4AA;}' +
|
|
777
|
+
'.nk-tpl-fg textarea{font-family:"JetBrains Mono","Fira Code",Consolas,monospace;font-size:12px;line-height:1.5;resize:vertical;min-height:140px;}' +
|
|
778
|
+
'.nk-tpl-form-btns{display:flex;gap:8px;margin-top:4px;}' +
|
|
779
|
+
'.nk-tpl-fbtn{flex:1;padding:9px;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;transition:opacity .2s;}' +
|
|
780
|
+
'.nk-tpl-fbtn-save{background:linear-gradient(135deg,#00D4AA,#0F4C81);color:#fff;}' +
|
|
781
|
+
'.nk-tpl-fbtn-save:hover{opacity:.9;}' +
|
|
782
|
+
'.nk-tpl-fbtn-cancel{background:#1e2a3e;color:#6b7a90;}' +
|
|
783
|
+
'.nk-tpl-fbtn-cancel:hover{color:#e0e5ec;}' +
|
|
784
|
+
|
|
785
|
+
/* ─── Light Theme ─── */
|
|
786
|
+
'body.nowaikit-light #nk-tpl-panel{background:#ffffff;border-left-color:#e2e8f0;color:#1a202c;box-shadow:-4px 0 20px rgba(0,0,0,.08);}' +
|
|
787
|
+
'body.nowaikit-light .nk-tpl-search-bar{border-bottom-color:#e2e8f0;}' +
|
|
788
|
+
'body.nowaikit-light #nk-tpl-search{background:#f8fafc;border-color:#e2e8f0;color:#1a202c;}' +
|
|
789
|
+
'body.nowaikit-light #nk-tpl-search::placeholder{color:#94a3b8;}' +
|
|
790
|
+
'body.nowaikit-light .nk-tpl-chips{border-bottom-color:#e2e8f0;}' +
|
|
791
|
+
'body.nowaikit-light .nk-tpl-chip{background:#f8fafc;border-color:#e2e8f0;color:#64748b;}' +
|
|
792
|
+
'body.nowaikit-light .nk-tpl-chip:hover{border-color:#0D9488;color:#0D9488;}' +
|
|
793
|
+
'body.nowaikit-light .nk-tpl-chip.active{background:rgba(13,148,136,.08);border-color:#0D9488;color:#0D9488;}' +
|
|
794
|
+
'body.nowaikit-light .nk-tpl-card{background:#f8fafc;border-color:#e2e8f0;}' +
|
|
795
|
+
'body.nowaikit-light .nk-tpl-card:hover{border-color:#cbd5e1;}' +
|
|
796
|
+
'body.nowaikit-light .nk-tpl-card-top{border-bottom-color:#e2e8f0;}' +
|
|
797
|
+
'body.nowaikit-light .nk-tpl-card-title{color:#1a202c;}' +
|
|
798
|
+
'body.nowaikit-light .nk-tpl-badge{background:rgba(13,148,136,.06);color:#0D9488;}' +
|
|
799
|
+
'body.nowaikit-light .nk-tpl-badge-custom{background:rgba(194,120,3,.08);color:#92620a;}' +
|
|
800
|
+
'body.nowaikit-light .nk-tpl-card-desc{color:#64748b;}' +
|
|
801
|
+
'body.nowaikit-light .nk-tpl-code{background:#f1f5f9;border-color:#e2e8f0;}' +
|
|
802
|
+
'body.nowaikit-light .nk-tpl-code code{color:#334155;}' +
|
|
803
|
+
'body.nowaikit-light .nk-hl-keyword{color:#a626a4;}' +
|
|
804
|
+
'body.nowaikit-light .nk-hl-api{color:#0D9488;}' +
|
|
805
|
+
'body.nowaikit-light .nk-hl-string{color:#50a14f;}' +
|
|
806
|
+
'body.nowaikit-light .nk-hl-comment{color:#a0a1a7;}' +
|
|
807
|
+
'body.nowaikit-light .nk-hl-number{color:#986801;}' +
|
|
808
|
+
'body.nowaikit-light .nk-tpl-btn-copy{background:rgba(13,148,136,.06);color:#0D9488;border-color:rgba(13,148,136,.12);}' +
|
|
809
|
+
'body.nowaikit-light .nk-tpl-btn-run{background:rgba(59,130,246,.06);color:#3b82f6;border-color:rgba(59,130,246,.12);}' +
|
|
810
|
+
'body.nowaikit-light .nk-tpl-btn-edit{background:rgba(194,120,3,.06);color:#92620a;border-color:rgba(194,120,3,.1);}' +
|
|
811
|
+
'body.nowaikit-light .nk-tpl-btn-del{background:rgba(239,68,68,.04);color:#dc2626;border-color:rgba(239,68,68,.08);}' +
|
|
812
|
+
'body.nowaikit-light .nk-tpl-empty{color:#94a3b8;}' +
|
|
813
|
+
'body.nowaikit-light .nk-tpl-form-title{color:#1a202c;}' +
|
|
814
|
+
'body.nowaikit-light .nk-tpl-fg label{color:#64748b;}' +
|
|
815
|
+
'body.nowaikit-light .nk-tpl-fg input,body.nowaikit-light .nk-tpl-fg select,body.nowaikit-light .nk-tpl-fg textarea{background:#fff;border-color:#e2e8f0;color:#1a202c;}' +
|
|
816
|
+
'body.nowaikit-light .nk-tpl-fbtn-cancel{background:#e2e8f0;color:#64748b;}' +
|
|
817
|
+
'body.nowaikit-light #nk-tpl-list::-webkit-scrollbar-thumb{background:#cbd5e1;}' +
|
|
818
|
+
'body.nowaikit-light .nk-tpl-resize:hover,body.nowaikit-light .nk-tpl-resize.active{background:rgba(13,148,136,.2);}' +
|
|
819
|
+
'body.nowaikit-light .nk-tpl-card-top{flex-wrap:wrap;}' +
|
|
820
|
+
'body.nowaikit-light .nk-tpl-card-chevron{color:#94a3b8;}' +
|
|
821
|
+
'body.nowaikit-light .nk-tpl-card:not(.nk-tpl-collapsed) .nk-tpl-card-chevron{color:#0D9488;}' +
|
|
822
|
+
|
|
823
|
+
/* Responsive — small screens */
|
|
824
|
+
'@media(max-width:520px){' +
|
|
825
|
+
'#nk-tpl-panel{width:100vw;right:-100vw;}' +
|
|
826
|
+
'#nk-tpl-panel.nk-tpl-open{right:0;}' +
|
|
827
|
+
'#nk-tpl-panel.nk-tpl-floating{top:0;right:0;width:100vw;height:100vh;border-radius:0;}' +
|
|
828
|
+
'.nk-tpl-card-top{flex-direction:column;align-items:stretch;}' +
|
|
829
|
+
'.nk-tpl-card-btns{justify-content:flex-end;}' +
|
|
830
|
+
'}' +
|
|
831
|
+
/* Medium screens — slightly narrower panel */
|
|
832
|
+
'@media(min-width:521px) and (max-width:768px){' +
|
|
833
|
+
'#nk-tpl-panel{width:400px;right:-400px;}' +
|
|
834
|
+
'}';
|
|
835
|
+
|
|
836
|
+
document.head.appendChild(s);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Expose for main-panel.js and other scripts
|
|
840
|
+
window.toggleCodeTemplates = togglePanel;
|
|
841
|
+
window.closeCodeTemplates = closePanel;
|
|
842
|
+
|
|
843
|
+
})();
|