1import NestedForm from '@stimulus-components/rails-nested-form' 2 3const PLATFORM_CONFIG = { 4 instagram: { prefix: 'instagram.com/', hosts: ['instagram.com'], stripAt: false, stripPath: null }, 5 facebook: { prefix: 'facebook.com/', hosts: ['facebook.com', 'fb.com', 'fb.me', 'm.facebook.com'], stripAt: false, stripPath: null }, 6 tiktok: { prefix: 'tiktok.com/@', hosts: ['tiktok.com', 'vm.tiktok.com'], stripAt: true, stripPath: null }, 7 x: { prefix: 'x.com/', hosts: ['x.com', 'twitter.com'], stripAt: false, stripPath: null }, 8 youtube: { prefix: 'youtube.com/@', hosts: ['youtube.com', 'youtu.be', 'm.youtube.com'], stripAt: true, stripPath: null }, 9 whatsapp: { prefix: 'wa.me/', hosts: ['wa.me', 'whatsapp.com', 'chat.whatsapp.com'], stripAt: false, stripPath: null }, 10 linkedin: { prefix: 'linkedin.com/', hosts: ['linkedin.com'], stripAt: false, stripPath: null }, 11 threads: { prefix: 'threads.net/@', hosts: ['threads.net', 'threads.com'], stripAt: true, stripPath: null }, 12 pinterest: { prefix: 'pinterest.com/', hosts: ['pinterest.com', 'pin.it'], stripAt: false, stripPath: null }, 13 snapchat: { prefix: 'snapchat.com/add/', hosts: ['snapchat.com'], stripAt: false, stripPath: 'add/' }, 14 telegram: { prefix: 't.me/', hosts: ['t.me', 'telegram.me', 'telegram.org'], stripAt: false, stripPath: null }, 15 other: { prefix: null, hosts: null, stripAt: false, stripPath: null } 16} 17 18export default class extends NestedForm { 19 static targets = [ 20 "platformSelect", "handleWrapper", "handlePrefix", "handleField", 21 "urlWrapper", "urlField", "labelField", "iconPreview" 22 ] 23 24 connect() { 25 super.connect() 26 this.element.addEventListener('rails-nested-form:add', () => { 27 const newRow = this.targetTarget.previousElementSibling 28 if (newRow && newRow.matches(this.wrapperSelectorValue)) this.refreshRow(newRow) 29 }) 30 } 31 32 platformChanged(event) { 33 const wrapper = event.target.closest(this.wrapperSelectorValue) 34 if (wrapper) this.refreshRow(wrapper) 35 } 36 37 refreshRow(wrapper) { 38 const select = wrapper.querySelector('[data-social-links-target="platformSelect"]') 39 if (!select) return 40 const platform = select.value 41 const cfg = PLATFORM_CONFIG[platform] || PLATFORM_CONFIG.other 42 const isOther = platform === 'other' 43 44 const handleWrapper = wrapper.querySelector('[data-social-links-target="handleWrapper"]') 45 const urlWrapper = wrapper.querySelector('[data-social-links-target="urlWrapper"]') 46 const labelField = wrapper.querySelector('[data-social-links-target="labelField"]') 47 const handlePrefix = wrapper.querySelector('[data-social-links-target="handlePrefix"]') 48 const icon = wrapper.querySelector('[data-social-links-target="iconPreview"]') 49 50 if (handleWrapper) handleWrapper.classList.toggle('hidden', isOther) 51 if (urlWrapper) urlWrapper.classList.toggle('hidden', !isOther) 52 if (labelField) labelField.classList.toggle('hidden', !isOther) 53 if (handlePrefix && cfg.prefix) handlePrefix.textContent = cfg.prefix 54 if (icon) icon.src = `/social/${platform}.svg` 55 } 56 57 handlePaste(event) { 58 const wrapper = event.target.closest(this.wrapperSelectorValue) 59 if (!wrapper) return 60 const select = wrapper.querySelector('[data-social-links-target="platformSelect"]') 61 if (!select) return 62 const cfg = PLATFORM_CONFIG[select.value] 63 if (!cfg || !cfg.hosts) return 64 65 const pasted = (event.clipboardData || window.clipboardData)?.getData('text') || '' 66 const handle = this.extractHandle(pasted, cfg) 67 if (handle === null) return 68 69 event.preventDefault() 70 event.target.value = handle 71 } 72 73 extractHandle(text, cfg) { 74 const trimmed = text.trim() 75 if (!trimmed) return null 76 if (!/^https?:\/\//i.test(trimmed)) return null 77 let url 78 try { url = new URL(trimmed) } catch (_) { return null } 79 const host = url.hostname.replace(/^www\./, '') 80 if (!cfg.hosts.some(h => host === h || host.endsWith('.' + h))) return null 81 let path = url.pathname.replace(/^\/+/, '') 82 if (cfg.stripPath && path.startsWith(cfg.stripPath)) path = path.slice(cfg.stripPath.length) 83 if (cfg.stripAt && path.startsWith('@')) path = path.slice(1) 84 return path 85 } 86}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.