ApiTestManager 소스 추가
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
class EditableInput {
|
||||
constructor(options = {}) {
|
||||
this.value = options.value || '';
|
||||
this.name = options.name || '';
|
||||
this.id = options.id || '';
|
||||
this.class = options.class || 'form-control';
|
||||
this.placeholder = options.placeholder || '';
|
||||
this.label = options.label || '';
|
||||
}
|
||||
|
||||
init(element, callback) {
|
||||
this.element = element;
|
||||
this.element.innerHTML = this.render();
|
||||
this.attachEventListeners(callback);
|
||||
}
|
||||
|
||||
val(newValue) {
|
||||
if (newValue !== undefined) {
|
||||
this.value = newValue;
|
||||
this.updateUI();
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
|
||||
updateUI() {
|
||||
const editableDiv = this.element.querySelector('.editableInputContent');
|
||||
const hiddenInput = this.element.querySelector('input[type="hidden"]');
|
||||
|
||||
if (this.value) {
|
||||
editableDiv.innerHTML = this.renderContent(this.parseContent(this.value));
|
||||
} else {
|
||||
editableDiv.innerHTML = '​'; // Insert a zero-width space
|
||||
}
|
||||
hiddenInput.value = this.value;
|
||||
}
|
||||
|
||||
render() {
|
||||
let contentHtml = this.value ? this.renderContent(this.parseContent(this.value)) : '​';
|
||||
return `
|
||||
<div contenteditable="true" class="editableInputContent ${this.class}" style="border-bottom-left-radius: 0; border-top-left-radius: 0;" placeholder="${this.placeholder}">
|
||||
${contentHtml}
|
||||
</div>
|
||||
<input type="hidden" name="${this.name}" value="${this.value}" />
|
||||
<label>${this.label}</label>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
attachEventListeners(callback) {
|
||||
const editableDiv = this.element.querySelector('.editableInputContent');
|
||||
const hiddenInput = this.element.querySelector('input[type="hidden"]');
|
||||
|
||||
editableDiv.addEventListener('input', (event) => {
|
||||
this.handleInput(event, editableDiv, hiddenInput);
|
||||
if (callback)
|
||||
callback(event);
|
||||
});
|
||||
}
|
||||
|
||||
handleInput(event, editableDiv, hiddenInput) {
|
||||
|
||||
let cursor = this.getCurrentCursor(editableDiv);
|
||||
let mergedString = Array.from(editableDiv.childNodes)
|
||||
.filter(function(node) {
|
||||
// Keep only SPAN elements and text nodes
|
||||
return node.nodeName.toUpperCase() === 'SPAN' || node.nodeType === 3;
|
||||
})
|
||||
.map(node => {
|
||||
return node.nodeType === 3 ? node.data.trim() : node.innerText.trim();
|
||||
})
|
||||
.join('');
|
||||
|
||||
let content = this.parseContent(mergedString);
|
||||
this.value = content.join('');
|
||||
hiddenInput.value = this.value;
|
||||
|
||||
if (!this.value) {
|
||||
editableDiv.innerHTML = '​'; // Insert a zero-width space
|
||||
cursor = 0;
|
||||
} else {
|
||||
editableDiv.innerHTML = this.renderContent(content);
|
||||
}
|
||||
|
||||
this.restoreCursor(editableDiv, cursor);
|
||||
}
|
||||
|
||||
|
||||
renderContent(contentArray) {
|
||||
let content = '';
|
||||
contentArray.forEach((item) => {
|
||||
if (item.startsWith('{{') && item.endsWith('}}')) {
|
||||
content += `<span class="variable">${item}</span>`;
|
||||
} else {
|
||||
content += `<span class="text">${item}</span>`;
|
||||
}
|
||||
});
|
||||
return content;
|
||||
}
|
||||
|
||||
parseContent(inputString) {
|
||||
let returnStringArray = [];
|
||||
let regex = /{{.*?}}/g;
|
||||
let lastIndex = 0;
|
||||
|
||||
inputString.replace(regex, (match, index) => {
|
||||
// Add the string before the match
|
||||
if (index > lastIndex) {
|
||||
returnStringArray.push(inputString.slice(lastIndex, index));
|
||||
}
|
||||
|
||||
// Add the matched string including the delimiters
|
||||
returnStringArray.push(match);
|
||||
|
||||
// Update lastIndex for the next iteration
|
||||
lastIndex = index + match.length;
|
||||
});
|
||||
|
||||
// Add any remaining string after the last match
|
||||
if (lastIndex < inputString.length) {
|
||||
returnStringArray.push(inputString.slice(lastIndex));
|
||||
}
|
||||
|
||||
return returnStringArray;
|
||||
}
|
||||
|
||||
getCurrentCursor(element) {
|
||||
const selection = window.getSelection();
|
||||
if (selection.rangeCount === 0) return null;
|
||||
|
||||
const range = selection.getRangeAt(0);
|
||||
const preCaretRange = range.cloneRange();
|
||||
preCaretRange.selectNodeContents(element);
|
||||
preCaretRange.setEnd(range.endContainer, range.endOffset);
|
||||
return preCaretRange.toString().trim().length;
|
||||
}
|
||||
|
||||
restoreCursor(element, position) {
|
||||
const selection = window.getSelection();
|
||||
const range = document.createRange();
|
||||
range.setStart(element, 0);
|
||||
range.collapse(true);
|
||||
|
||||
const nodeStack = [element];
|
||||
let node, foundStart = false, stop = false;
|
||||
let charIndex = 0;
|
||||
|
||||
while (!stop && (node = nodeStack.pop())) {
|
||||
if (node.nodeType === 3) {
|
||||
const nextCharIndex = charIndex + node.length;
|
||||
if (!foundStart && position >= charIndex && position <= nextCharIndex) {
|
||||
range.setStart(node, position - charIndex);
|
||||
foundStart = true;
|
||||
}
|
||||
if (foundStart && position >= charIndex && position <= nextCharIndex) {
|
||||
range.setEnd(node, position - charIndex);
|
||||
stop = true;
|
||||
}
|
||||
charIndex = nextCharIndex;
|
||||
} else {
|
||||
let i = node.childNodes.length;
|
||||
while (i--) {
|
||||
nodeStack.push(node.childNodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default EditableInput;
|
||||
Reference in New Issue
Block a user