', { class: 'star-container' });
631 | videoCardContainer.css('position', 'relative').append(starContainer);
632 |
633 | // 스타 아이콘 추가
634 | const starIcon = $('
', { class: 'star-icon', html: '★' });
635 | if (!favoriteStreamers.some(fav => fav.channelId === channelId)) {
636 | starIcon.addClass('gray');
637 | }
638 | starContainer.append(starIcon);
639 |
640 | // 스타 클릭 이벤트 처리
641 | starIcon.on('click', async () => {
642 | const favIndex = favoriteStreamers.findIndex(fav => fav.channelId === channelId);
643 | if (favIndex !== -1) {
644 | // 이미 고정된 스트리머의 스타를 클릭한 경우
645 | favoriteStreamers.splice(favIndex, 1);
646 | starIcon.addClass('gray');
647 | item.removeClass('pinned');
648 | } else {
649 | // 고정되지 않은 스트리머의 스타를 클릭한 경우
650 | favoriteStreamers.push({ channelId, name: channelName });
651 | starIcon.removeClass('gray');
652 | item.addClass('pinned');
653 | }
654 | await GM.setValue('favoriteStreamers', favoriteStreamers);
655 | reorderList();
656 | });
657 |
658 | // 초기 상태 설정
659 | if (favoriteStreamers.some(fav => fav.channelId === channelId)) {
660 | item.addClass('pinned');
661 | }
662 | }
663 |
664 | // 리스트 재정렬 함수
665 | function reorderList(favoriteOnly = false) {
666 | const container = $('[class^="component_list__"]').first();
667 | if (!container.length) {
668 | console.error('Container not found');
669 | return;
670 | }
671 |
672 | // 현재 스크롤 위치 저장
673 | const currentScrollPosition = $(window).scrollTop();
674 |
675 | const items = container.find('li[class^="component_item__"]');
676 | const favoriteItems = items.filter((_, item) => {
677 | const channelId = $(item).find('[class^="video_card_channel__"]').attr('href').split('/').pop();
678 | return favoriteStreamers.some(fav => fav.channelId === channelId);
679 | });
680 | const nonFavoriteItems = items.filter((_, item) => {
681 | const channelId = $(item).find('[class^="video_card_channel__"]').attr('href').split('/').pop();
682 | return !favoriteStreamers.some(fav => fav.channelId === channelId);
683 | });
684 |
685 | // 고정된 스트리머 먼저, 나머지는 시청자 수 순으로 정렬
686 | favoriteItems.sort((a, b) => {
687 | const aIndex = favoriteStreamers.findIndex(fav => fav.channelId === $(a).find('[class^="video_card_channel__"]').attr('href').split('/').pop());
688 | const bIndex = favoriteStreamers.findIndex(fav => fav.channelId === $(b).find('[class^="video_card_channel__"]').attr('href').split('/').pop());
689 | return aIndex - bIndex;
690 | });
691 |
692 | nonFavoriteItems.sort((a, b) => {
693 | const aViewers = parseInt($(a).find('[class^="video_card_badge__"]').text().replace(/[^0-9]/g, ''));
694 | const bViewers = parseInt($(b).find('[class^="video_card_badge__"]').text().replace(/[^0-9]/g, ''));
695 | return bViewers - aViewers;
696 | });
697 |
698 | // 정렬된 아이템 재배치
699 | container.empty();
700 | const combinedItems = favoriteOnly ? favoriteItems : favoriteItems.add(nonFavoriteItems);
701 | combinedItems.each((_, item) => {
702 | container.append(item);
703 | addStarIcon($(item).find('[class^="video_card_channel__"]'));
704 | });
705 |
706 | // 스크롤 위치 복원
707 | $(window).scrollTop(currentScrollPosition);
708 | }
709 |
710 | // 채널 이름 가져오는 함수
711 | function getChannelNameFromList(item) {
712 | const nameElem = item.find('span[class^="name_text__"]').first();
713 | if (nameElem.length) {
714 | return nameElem.text().split('\n')[0];
715 | }
716 | return "Unknown";
717 | }
718 |
719 | $(document).arrive('li[class^="component_item__"] [class^="video_card_channel__"]', { existing: true }, function() {
720 | const elem = $(this);
721 | addStarIcon(elem);
722 |
723 | const componentList = $('ul[class^="component_list__"]').first();
724 | if (componentList.length) {
725 |
726 | function updateCurrentItemOnDrag(evt){
727 | const items = Array.from(componentList.children());
728 | const newIndex = items.indexOf(evt.item);
729 | var $prevElement = $(evt.item).prev();
730 | if (newIndex == 0 || ($prevElement.length && $prevElement.hasClass('pinned'))) {
731 | $(evt.item).find('.star-icon').removeClass('gray').addClass('yellow');
732 | $(evt.item).removeClass('ghost-gray');
733 | }
734 | else{
735 | $(evt.item).find('.star-icon').addClass('gray').removeClass('yellow');
736 | $(evt.item).addClass('ghost-gray');
737 | }
738 | }
739 |
740 | new Sortable(componentList[0], {
741 | animation: 150,
742 | //handle: '.star-container',
743 | ghostClass: 'ghost', // 드래그 중 엘리먼트 스타일
744 | //chosenClass: 'chosen', // 선택된 엘리먼트 스타일
745 | handle: '[class^="video_card_container__"]',
746 | onEnd: async function (evt) {
747 | // 마우스 커서 좌표 가져오기
748 | const e = evt.originalEvent || {};
749 | const x = e.clientX, y = e.clientY;
750 | // 리스트의 화면 상 위치
751 | const rect = componentList[0].getBoundingClientRect();
752 | // 밖에 떨궜으면
753 | if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {
754 | reorderList();
755 | return;
756 | }
757 |
758 | const items = Array.from(componentList.children());
759 | const draggedItem = $(evt.item);
760 | const draggedIndex = evt.newIndex;
761 | const draggedChannelId = draggedItem.find('[class^="video_card_channel__"]').attr('href').split('/').pop();
762 |
763 | const pinnedItems = items.filter(item => $(item).hasClass('pinned') && item !== draggedItem[0]);
764 | const startIndex = items.indexOf(pinnedItems[0]);
765 | const endIndex = items.indexOf(pinnedItems[pinnedItems.length - 1]);
766 |
767 | draggedItem.addClass('pinned');
768 | draggedItem.find('.star-icon').removeClass('gray');
769 |
770 | const favIndex = favoriteStreamers.findIndex(fav => fav.channelId === draggedChannelId);
771 | if (favIndex !== -1) {
772 | favoriteStreamers.splice(favIndex, 1);
773 | }
774 |
775 | if (draggedIndex < startIndex) {
776 | const nextItem = $(items[draggedIndex + 1]);
777 | if (nextItem && nextItem.hasClass('pinned')) {
778 | const nextChannelId = nextItem.find('[class^="video_card_channel__"]').attr('href').split('/').pop();
779 | const nextFavIndex = favoriteStreamers.findIndex(fav => fav.channelId === nextChannelId);
780 | if (nextFavIndex !== -1) {
781 | favoriteStreamers.splice(nextFavIndex, 0, { channelId: draggedChannelId, name: getChannelNameFromList(draggedItem) });
782 | }
783 | } else {
784 | favoriteStreamers.unshift({ channelId: draggedChannelId, name: getChannelNameFromList(draggedItem) });
785 | }
786 | } else if (pinnedItems.length === 0) {
787 | favoriteStreamers.push({ channelId: draggedChannelId, name: getChannelNameFromList(draggedItem) });
788 | } else {
789 | const prevItem = $(items[draggedIndex - 1]);
790 | if (prevItem.length && prevItem.hasClass('pinned')) {
791 | const prevChannelId = prevItem.find('[class^="video_card_channel__"]').attr('href').split('/').pop();
792 | const prevFavIndex = favoriteStreamers.findIndex(fav => fav.channelId === prevChannelId);
793 | favoriteStreamers.splice(prevFavIndex + 1, 0, { channelId: draggedChannelId, name: getChannelNameFromList(draggedItem) });
794 | }
795 | }
796 |
797 | await GM.setValue('favoriteStreamers', favoriteStreamers);
798 | $(evt.item).removeClass('ghost-gray');
799 | $(evt.item).find('.star-icon').removeClass('yellow');
800 | reorderList();
801 | },
802 | setData: function (dataTransfer, dragEl) {
803 | const channelLink = $(dragEl).find('[class^="video_card_thumbnail__"]').attr('href');
804 | if (channelLink) {
805 | const fullUrl = new URL(channelLink, window.location.origin).href;
806 | dataTransfer.setData('text/plain', fullUrl);
807 | dataTransfer.setData('text/uri-list', fullUrl);
808 | } else {
809 | dataTransfer.setData('text/plain', $(dragEl).text());
810 | }
811 | },
812 | onStart: updateCurrentItemOnDrag,
813 | onChange: updateCurrentItemOnDrag
814 | });
815 | }
816 | });
817 |
818 | //////////////////////////////
819 | // Play view
820 | //////////////////////////////
821 | // arrive.js를 사용하여 video_information_control__ 클래스 요소 감지
822 | $(document).arrive('[class^="video_information_control__"]', { existing: true }, function() {
823 | const videoInfoControl = $(this);
824 |
825 | setTimeout(function() {
826 | const buttons = videoInfoControl.find('button');
827 | if (!buttons.length) return;
828 |
829 | const buttonContainer = buttons.filter((_, btn) => $(btn).attr('class').includes('button_container__')).last();
830 | const buttonMedium = buttons.filter((_, btn) => $(btn).attr('class').includes('button_medium__')).first();
831 | const buttonCircle = buttons.filter((_, btn) => $(btn).attr('class').includes('button_circle__')).first();
832 | let buttonColor = buttons.filter((_, btn) => $(btn).attr('class').includes('button_dark__')).first();
833 | if (!buttonColor.length) {
834 | buttonColor = buttons.filter((_, btn) => $(btn).attr('class').includes('button_white__')).first();
835 | }
836 |
837 | const buttonContainerClass = getClassWithPrefix(buttonContainer, 'button_container__');
838 | const buttonMediumClass = getClassWithPrefix(buttonMedium, 'button_medium__');
839 | const buttonCircleClass = getClassWithPrefix(buttonCircle, 'button_circle__');
840 | const buttonColorClass = getClassWithPrefix(buttonColor, 'button_dark__') || getClassWithPrefix(buttonColor, 'button_white__');
841 | const buttonIconContainerClass = getClassWithPrefix(buttonContainer, 'button_icon_container__');
842 | const buttonLargerClass = getClassWithPrefix(buttonContainer, 'button_larger__');
843 |
844 | let favClass = "";
845 | const channelId = getChannelId();
846 | const favIndex = favoriteStreamers.findIndex(fav => fav.channelId === channelId);
847 | console.log("buttonIconContainerClass", buttonIconContainerClass);
848 | if (favIndex !== -1) {
849 | favClass = "favorite";
850 | }
851 |
852 | const favoriteButton = $('