mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-02-12 16:52:46 +00:00
383 lines
17 KiB
JavaScript
383 lines
17 KiB
JavaScript
/**
|
|
* Splitter Tool - Split text into multiple copyable messages
|
|
*/
|
|
class SplitterTool extends Tool {
|
|
constructor() {
|
|
super({
|
|
id: 'splitter',
|
|
name: 'Splitter',
|
|
icon: 'fa-grip-lines',
|
|
title: 'Split text into multiple copyable messages',
|
|
order: 7
|
|
});
|
|
}
|
|
|
|
getVueData() {
|
|
// Load favorites
|
|
const favorites = this.loadFavorites();
|
|
|
|
// Load category order (same as TransformTool)
|
|
const categoryOrder = this.getCategoryOrder();
|
|
|
|
return {
|
|
// Message Splitter Tab
|
|
splitterInput: '',
|
|
splitterMode: 'word', // 'chunk' or 'word' - default to word
|
|
splitterChunkSize: 6,
|
|
splitterWordSplitSide: 'left', // 'left' or 'right' for even-length words
|
|
splitterWordSkip: 0, // number of words to skip between splits
|
|
splitterMinWordLength: 2, // minimum word length to consider for splitting (skip shorter words)
|
|
splitterSplitFirstWord: true, // whether to split the first word (true) or keep it whole (false)
|
|
splitterCopyAsSingleLine: false, // copy as single line (true) or multiline (false)
|
|
splitterTransforms: [''], // array of transform names to apply in sequence (start with one empty slot)
|
|
splitterStartWrap: '',
|
|
splitterEndWrap: '',
|
|
splitMessages: [],
|
|
favorites: favorites,
|
|
categoryOrder: categoryOrder
|
|
};
|
|
}
|
|
|
|
getCategoryOrder() {
|
|
// Get all categories from transforms
|
|
if (!window.transforms) return [];
|
|
|
|
const categorySet = new Set();
|
|
Object.values(window.transforms).forEach(transform => {
|
|
if (transform.category) {
|
|
categorySet.add(transform.category);
|
|
}
|
|
});
|
|
|
|
const allCategories = Array.from(categorySet);
|
|
const savedOrder = this.loadCategoryOrder();
|
|
|
|
return this.mergeCategoryOrder(allCategories, savedOrder);
|
|
}
|
|
|
|
loadCategoryOrder() {
|
|
try {
|
|
const saved = localStorage.getItem('transformCategoryOrder');
|
|
if (saved) {
|
|
return JSON.parse(saved);
|
|
}
|
|
} catch (e) {
|
|
console.warn('Failed to load category order:', e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
mergeCategoryOrder(allCategories, savedOrder) {
|
|
// Always ensure randomizer is last
|
|
const categoriesWithoutRandomizer = allCategories.filter(c => c !== 'randomizer');
|
|
|
|
if (!savedOrder || savedOrder.length === 0) {
|
|
// Default: alphabetical, randomizer last
|
|
const sorted = categoriesWithoutRandomizer.sort((a, b) => a.localeCompare(b));
|
|
return [...sorted, 'randomizer'];
|
|
}
|
|
|
|
// Use saved order, but filter out categories that no longer exist and remove duplicates
|
|
const validSavedOrder = savedOrder
|
|
.filter(cat => allCategories.includes(cat))
|
|
.filter((cat, index, arr) => arr.indexOf(cat) === index); // Remove duplicates
|
|
|
|
// Find new categories not in saved order
|
|
const newCategories = categoriesWithoutRandomizer.filter(cat => !validSavedOrder.includes(cat));
|
|
|
|
// Build final order: saved order (filtered, deduplicated) + new categories (alphabetically) + randomizer
|
|
const finalOrder = [...validSavedOrder];
|
|
if (newCategories.length > 0) {
|
|
finalOrder.push(...newCategories.sort((a, b) => a.localeCompare(b)));
|
|
}
|
|
|
|
// Ensure randomizer is always last and remove any duplicates
|
|
const finalWithoutRandomizer = finalOrder.filter(c => c !== 'randomizer');
|
|
const uniqueFinal = finalWithoutRandomizer.filter((cat, index, arr) => arr.indexOf(cat) === index);
|
|
return [...uniqueFinal, 'randomizer'];
|
|
}
|
|
|
|
loadFavorites() {
|
|
try {
|
|
const saved = localStorage.getItem('transformFavorites');
|
|
if (saved) {
|
|
const data = JSON.parse(saved);
|
|
// Filter to only include transforms that still exist
|
|
if (window.transforms) {
|
|
return data.filter(transformName => {
|
|
return Object.values(window.transforms).some(t => t.name === transformName);
|
|
});
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn('Failed to load favorites:', e);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
getVueMethods() {
|
|
return {
|
|
/**
|
|
* Get favorite transforms
|
|
*/
|
|
getFavoriteTransforms: function() {
|
|
if (!this.favorites || this.favorites.length === 0) {
|
|
return [];
|
|
}
|
|
return this.favorites
|
|
.map(transformName => {
|
|
return this.transforms.find(t => t.name === transformName);
|
|
})
|
|
.filter(t => t !== undefined);
|
|
},
|
|
/**
|
|
* Get transforms by category (excluding favorites)
|
|
*/
|
|
getTransformsByCategory: function(category) {
|
|
const categoryTransforms = this.transforms.filter(t => t.category === category);
|
|
// Exclude favorites from category lists (they're shown separately)
|
|
if (!this.favorites || this.favorites.length === 0) {
|
|
return categoryTransforms;
|
|
}
|
|
return categoryTransforms.filter(t => !this.favorites.includes(t.name));
|
|
},
|
|
/**
|
|
* Get display name for category (capitalized)
|
|
*/
|
|
getCategoryDisplayName: function(category) {
|
|
return category.charAt(0).toUpperCase() + category.slice(1);
|
|
},
|
|
/**
|
|
* Set encapsulation start and end strings
|
|
* @param {string} start - The start string
|
|
* @param {string} end - The end string
|
|
*/
|
|
setEncapsulation(start, end) {
|
|
this.splitterStartWrap = start;
|
|
this.splitterEndWrap = end;
|
|
},
|
|
|
|
/**
|
|
* Handle transform change - auto-add next dropdown or collapse consecutive Nones
|
|
* @param {number} index - The index of the transformation that changed
|
|
*/
|
|
handleTransformChange(index) {
|
|
const value = this.splitterTransforms[index];
|
|
|
|
if (value && value !== '') {
|
|
// Transform was selected - add next dropdown if it doesn't exist
|
|
if (index === this.splitterTransforms.length - 1) {
|
|
this.splitterTransforms.push('');
|
|
}
|
|
} else {
|
|
// Transform was set to None
|
|
// Check if previous dropdown is also None - if so, remove current one and collapse from previous position
|
|
if (index > 0) {
|
|
const prev = this.splitterTransforms[index - 1];
|
|
if (!prev || prev === '') {
|
|
// Collapse: remove this dropdown
|
|
this.splitterTransforms.splice(index, 1);
|
|
}
|
|
} else if (index === 0 && this.splitterTransforms.length === 1) {
|
|
// Only one dropdown and it's set to None - keep it as the starting dropdown
|
|
// Do nothing
|
|
} else if (index === 0 && this.splitterTransforms.length > 1) {
|
|
// First dropdown set to None, check if next is also None
|
|
const next = this.splitterTransforms[1];
|
|
if (!next || next === '') {
|
|
// Remove the first one
|
|
this.splitterTransforms.splice(0, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure there's always at least one dropdown
|
|
if (this.splitterTransforms.length === 0) {
|
|
this.splitterTransforms = [''];
|
|
}
|
|
|
|
// Force Vue to update
|
|
this.$forceUpdate();
|
|
},
|
|
|
|
/**
|
|
* Generate split messages from input text
|
|
* Supports two modes: character chunks or split words in half
|
|
*/
|
|
generateSplitMessages() {
|
|
// Clear previous output at the start
|
|
this.splitMessages = [];
|
|
|
|
const input = this.splitterInput;
|
|
if (!input) {
|
|
return;
|
|
}
|
|
|
|
let chunks = [];
|
|
|
|
if (this.splitterMode === 'chunk') {
|
|
// Character chunk mode
|
|
const chunkSize = Math.max(1, Math.min(500, this.splitterChunkSize || 6));
|
|
for (let i = 0; i < input.length; i += chunkSize) {
|
|
chunks.push(input.slice(i, i + chunkSize));
|
|
}
|
|
} else if (this.splitterMode === 'word') {
|
|
// Word split mode - creates messages with pattern: secondHalf + wholeWords + firstHalf
|
|
// IMPORTANT: ALL words must be included in output, never filtered out
|
|
const words = input.match(/\S+/g) || [];
|
|
if (words.length === 0) return;
|
|
|
|
const skipCount = Math.max(0, Math.min(20, this.splitterWordSkip || 0));
|
|
const minLength = Math.max(1, this.splitterMinWordLength || 2);
|
|
|
|
// Process all words - only split words that meet minimum length
|
|
// Short words are kept whole but still included in the pattern
|
|
let wordsToProcess = words;
|
|
let prependToFirst = [];
|
|
|
|
// Handle "Split First Word" option
|
|
if (!this.splitterSplitFirstWord && words.length > 0) {
|
|
prependToFirst = [words[0]];
|
|
wordsToProcess = words.slice(1);
|
|
}
|
|
|
|
// Build word processing array - track which words can be split vs kept whole
|
|
const wordData = wordsToProcess.map((word, idx) => {
|
|
const canSplit = word.length >= minLength && word.length > 1;
|
|
return {
|
|
word: word,
|
|
canSplit: canSplit,
|
|
index: idx
|
|
};
|
|
});
|
|
|
|
// Determine which words to split (only words that can be split)
|
|
const splittableWords = wordData.filter(w => w.canSplit);
|
|
if (splittableWords.length === 0) {
|
|
// No words can be split, output everything as one message
|
|
chunks.push([...prependToFirst, ...wordsToProcess].join(' '));
|
|
return;
|
|
}
|
|
|
|
// Determine split pattern based on splittable words only
|
|
const splitIndexes = new Set();
|
|
for (let i = 0; i < splittableWords.length; i++) {
|
|
if ((i % (skipCount + 1)) === 0) {
|
|
splitIndexes.add(splittableWords[i].index);
|
|
}
|
|
}
|
|
|
|
// Process all words and build split structure
|
|
const processedWords = wordData.map((wd, idx) => {
|
|
if (splitIndexes.has(idx) && wd.canSplit) {
|
|
// Split this word
|
|
let splitPos;
|
|
if (wd.word.length % 2 === 0) {
|
|
splitPos = wd.word.length / 2;
|
|
} else {
|
|
splitPos = this.splitterWordSplitSide === 'left'
|
|
? Math.ceil(wd.word.length / 2)
|
|
: Math.floor(wd.word.length / 2);
|
|
}
|
|
return {
|
|
firstHalf: wd.word.slice(0, splitPos),
|
|
secondHalf: wd.word.slice(splitPos),
|
|
split: true
|
|
};
|
|
}
|
|
// Keep whole (either too short or skipped)
|
|
return { whole: wd.word, split: false };
|
|
});
|
|
|
|
// Build output messages
|
|
let currentMessage = [...prependToFirst];
|
|
let messageStarted = false;
|
|
|
|
for (let i = 0; i < processedWords.length; i++) {
|
|
const item = processedWords[i];
|
|
|
|
if (item.split) {
|
|
if (!messageStarted) {
|
|
// First split word - add first half to current message
|
|
currentMessage.push(item.firstHalf);
|
|
chunks.push(currentMessage.join(' '));
|
|
currentMessage = [item.secondHalf];
|
|
messageStarted = true;
|
|
} else {
|
|
// Add first half to current message, then start new message with second half
|
|
currentMessage.push(item.firstHalf);
|
|
chunks.push(currentMessage.join(' '));
|
|
currentMessage = [item.secondHalf];
|
|
}
|
|
} else {
|
|
// Whole word - add to current message (ALL words included)
|
|
currentMessage.push(item.whole);
|
|
}
|
|
}
|
|
|
|
// Add any remaining message
|
|
if (currentMessage.length > 0) {
|
|
chunks.push(currentMessage.join(' '));
|
|
}
|
|
}
|
|
|
|
// Apply transformations in sequence (chaining)
|
|
let processedChunks = chunks;
|
|
if (this.splitterTransforms && this.splitterTransforms.length > 0) {
|
|
// Filter out empty transforms
|
|
const activeTransforms = this.splitterTransforms.filter(t => t && t !== '');
|
|
|
|
if (activeTransforms.length > 0) {
|
|
// Apply each transformation in sequence
|
|
for (const transformName of activeTransforms) {
|
|
const selectedTransform = this.transforms.find(t => t.name === transformName);
|
|
if (selectedTransform && selectedTransform.func) {
|
|
processedChunks = processedChunks.map(chunk => {
|
|
try {
|
|
return selectedTransform.func(chunk);
|
|
} catch (e) {
|
|
console.error('Transform error:', e);
|
|
return chunk;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply encapsulation
|
|
const start = this.splitterStartWrap || '';
|
|
const end = this.splitterEndWrap || '';
|
|
this.splitMessages = processedChunks.map(chunk => `${start}${chunk}${end}`);
|
|
},
|
|
|
|
/**
|
|
* Copy all split messages to clipboard
|
|
* Single line: merges messages into one continuous string (keeps encapsulation/transformations)
|
|
* Multiline: copies messages separated by newlines
|
|
*/
|
|
copyAllSplitMessages() {
|
|
if (this.splitMessages.length === 0) return;
|
|
|
|
if (this.splitterCopyAsSingleLine) {
|
|
// Merge all messages back together, keeping encapsulation and transformations
|
|
// Just join without newlines - all encapsulation/transformations are already in splitMessages
|
|
const merged = this.splitMessages.join('');
|
|
this.copyToClipboard(merged);
|
|
} else {
|
|
// Copy all messages separated by newlines
|
|
const allMessages = this.splitMessages.join('\n');
|
|
this.copyToClipboard(allMessages);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
// Export
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = SplitterTool;
|
|
} else {
|
|
window.SplitterTool = SplitterTool;
|
|
}
|