Crystina commited on
Commit
0a97af6
Β·
1 Parent(s): 9071f67
DEPLOYMENT.md ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deployment Guide
2
+
3
+ This guide will help you deploy your multilingual paper database to various hosting platforms.
4
+
5
+ ## Prerequisites
6
+
7
+ Before deploying, ensure you have:
8
+ - All project files in your local directory
9
+ - A GitHub account (for most deployment options)
10
+ - Basic familiarity with command line (optional, depending on method)
11
+
12
+ ## Option 1: GitHub Pages (Recommended - Free)
13
+
14
+ GitHub Pages is perfect for static websites and offers free hosting with a custom domain option.
15
+
16
+ ### Step 1: Create GitHub Repository
17
+ 1. Go to [GitHub](https://github.com) and sign in
18
+ 2. Click the "+" icon β†’ "New repository"
19
+ 3. Name it `multilingual-paperbase`
20
+ 4. Make it public (required for free GitHub Pages)
21
+ 5. Click "Create repository"
22
+
23
+ ### Step 2: Upload Your Files
24
+ ```bash
25
+ # Initialize git in your project folder
26
+ git init
27
+
28
+ # Add all files
29
+ git add .
30
+
31
+ # Commit the files
32
+ git commit -m "Initial commit"
33
+
34
+ # Add your GitHub repository as remote
35
+ git remote add origin https://github.com/crystina-z/multilingual-paperbase.git
36
+
37
+ # Push to GitHub
38
+ git push -u origin main
39
+ ```
40
+
41
+ ### Step 3: Enable GitHub Pages
42
+ 1. Go to your repository on GitHub
43
+ 2. Click "Settings" tab
44
+ 3. Scroll down to "Pages" section (in the left sidebar)
45
+ 4. Under "Source", select "Deploy from a branch"
46
+ 5. Choose "main" branch and "/ (root)" folder
47
+ 6. Click "Save"
48
+
49
+ ### Step 4: Access Your Site
50
+ Your site will be available at:
51
+ `https://crystina-z.github.io/multilingual-paperbase`
52
+
53
+ **Note**: It may take a few minutes for the site to become available.
54
+
55
+ ## Option 2: Netlify (Free Tier)
56
+
57
+ Netlify offers excellent performance and features like automatic deployments.
58
+
59
+ ### Method A: Drag & Drop (Easiest)
60
+ 1. Go to [netlify.com](https://netlify.com) and sign up
61
+ 2. Drag your entire project folder to the deploy area
62
+ 3. Your site is instantly live!
63
+ 4. Netlify will give you a random URL (you can customize it)
64
+
65
+ ### Method B: Connect to Git
66
+ 1. Sign up at [netlify.com](https://netlify.com)
67
+ 2. Click "New site from Git"
68
+ 3. Connect your GitHub account
69
+ 4. Select your repository
70
+ 5. Deploy settings:
71
+ - Build command: (leave empty)
72
+ - Publish directory: `.` (root)
73
+ 6. Click "Deploy site"
74
+
75
+ ## Option 3: Vercel (Free Tier)
76
+
77
+ Vercel is great for performance and developer experience.
78
+
79
+ ### Step 1: Install Vercel CLI
80
+ ```bash
81
+ npm install -g vercel
82
+ ```
83
+
84
+ ### Step 2: Deploy
85
+ ```bash
86
+ # Navigate to your project directory
87
+ cd multilingual-paperbase
88
+
89
+ # Deploy
90
+ vercel
91
+
92
+ # Follow the prompts:
93
+ # - Link to existing project? No
94
+ # - Project name: multilingual-paperbase
95
+ # - Directory: ./
96
+ ```
97
+
98
+ ### Step 3: Access Your Site
99
+ Vercel will provide you with a URL like:
100
+ `https://multilingual-paperbase.vercel.app`
101
+
102
+ ## Option 4: Firebase Hosting (Free Tier)
103
+
104
+ Firebase offers reliable hosting with Google's infrastructure.
105
+
106
+ ### Step 1: Install Firebase CLI
107
+ ```bash
108
+ npm install -g firebase-tools
109
+ ```
110
+
111
+ ### Step 2: Login and Initialize
112
+ ```bash
113
+ # Login to Firebase
114
+ firebase login
115
+
116
+ # Initialize Firebase in your project
117
+ firebase init hosting
118
+
119
+ # Answer the prompts:
120
+ # - Use existing project? No
121
+ # - Project name: multilingual-paperbase
122
+ # - Public directory: ./
123
+ # - Configure as single-page app? No
124
+ # - Overwrite index.html? No
125
+ ```
126
+
127
+ ### Step 3: Deploy
128
+ ```bash
129
+ firebase deploy
130
+ ```
131
+
132
+ Your site will be available at the URL provided by Firebase.
133
+
134
+ ## Option 5: Traditional Web Hosting
135
+
136
+ If you have a traditional web hosting account:
137
+
138
+ 1. Upload all files to your web server's public directory
139
+ 2. Ensure `index.html` is in the root directory
140
+ 3. Your site will be available at your domain
141
+
142
+ ## Custom Domain Setup
143
+
144
+ ### GitHub Pages
145
+ 1. Go to repository Settings β†’ Pages
146
+ 2. Under "Custom domain", enter your domain
147
+ 3. Add a CNAME record pointing to `YOUR_USERNAME.github.io`
148
+ 4. Create a `CNAME` file in your repository with your domain name
149
+
150
+ ### Netlify
151
+ 1. Go to Site settings β†’ Domain management
152
+ 2. Click "Add custom domain"
153
+ 3. Follow the DNS configuration instructions
154
+
155
+ ### Vercel
156
+ 1. Go to your project dashboard
157
+ 2. Click "Settings" β†’ "Domains"
158
+ 3. Add your domain and configure DNS
159
+
160
+ ## Post-Deployment Checklist
161
+
162
+ After deploying, verify:
163
+
164
+ - [ ] All pages load correctly
165
+ - [ ] Search functionality works
166
+ - [ ] Filters are working
167
+ - [ ] Mobile responsiveness
168
+ - [ ] No console errors
169
+ - [ ] Images and assets load properly
170
+
171
+ ## Troubleshooting
172
+
173
+ ### Common Issues
174
+
175
+ **Site not loading:**
176
+ - Check if all files were uploaded
177
+ - Verify `index.html` is in the root directory
178
+ - Check browser console for errors
179
+
180
+ **Search not working:**
181
+ - Ensure `script.js` is properly loaded
182
+ - Check if data files are accessible
183
+ - Verify CORS settings if loading external data
184
+
185
+ **Styling issues:**
186
+ - Confirm `styles.css` is loaded
187
+ - Check for CSS syntax errors
188
+ - Verify file paths are correct
189
+
190
+ ### Performance Optimization
191
+
192
+ 1. **Compress images** if you have any
193
+ 2. **Minify CSS/JS** for production
194
+ 3. **Enable gzip compression** on your server
195
+ 4. **Use a CDN** for better global performance
196
+
197
+ ## Security Considerations
198
+
199
+ - Keep your repository public if using GitHub Pages
200
+ - Don't include sensitive data in your code
201
+ - Use HTTPS (most platforms provide this automatically)
202
+ - Regularly update dependencies if you add any
203
+
204
+ ## Maintenance
205
+
206
+ - Regularly update your paper database
207
+ - Monitor site performance
208
+ - Keep backups of your data
209
+ - Test on different browsers and devices
210
+
211
+ ---
212
+
213
+ **Need help?** Open an issue on GitHub or check the platform's documentation for specific troubleshooting steps.
PYTHON_README.md ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python Multilingual BibTeX Generator
2
+
3
+ This Python script generates `multilingual_papers.bib` by filtering the original `anthology+abstracts.bib` file for multilingual NLP research papers.
4
+
5
+ ## Features
6
+
7
+ - **Identical Logic**: Uses the same filtering logic as the JavaScript web application
8
+ - **Comprehensive Detection**: Detects multilingual papers using keywords and language names
9
+ - **LaTeX Cleaning**: Properly handles LaTeX commands and formatting
10
+ - **Statistics**: Provides detailed statistics about the filtering process
11
+ - **Safe Operation**: Checks for existing files and asks for confirmation before overwriting
12
+
13
+ ## Requirements
14
+
15
+ - Python 3.6 or higher
16
+ - No external dependencies (uses only standard library)
17
+
18
+ ## Usage
19
+
20
+ 1. **Place your files**: Ensure `anthology+abstracts.bib` is in the same directory as the script
21
+ 2. **Run the script**:
22
+ ```bash
23
+ python generate_multilingual_bib.py
24
+ ```
25
+ 3. **Follow prompts**: The script will ask for confirmation if `multilingual_papers.bib` already exists
26
+
27
+ ## Output
28
+
29
+ The script will:
30
+ - Generate `multilingual_papers.bib` containing only multilingual papers
31
+ - Display statistics about the filtering process
32
+ - Show the top 10 most common keywords found
33
+
34
+ ## Example Output
35
+
36
+ ```
37
+ Reading anthology+abstracts.bib...
38
+ Parsing BibTeX entries...
39
+ Found 50000 total papers
40
+ Found 2500 multilingual papers
41
+ Generating BibTeX content...
42
+ Writing to multilingual_papers.bib...
43
+ Successfully generated multilingual_papers.bib with 2500 papers!
44
+
45
+ Statistics:
46
+ Total papers processed: 50000
47
+ Multilingual papers found: 2500
48
+ Percentage multilingual: 5.0%
49
+
50
+ Top 10 keywords found:
51
+ multilingual: 1200 papers
52
+ chinese: 800 papers
53
+ crosslingual: 600 papers
54
+ hindi: 400 papers
55
+ low-resource: 350 papers
56
+ korean: 300 papers
57
+ arabic: 250 papers
58
+ japanese: 200 papers
59
+ spanish: 180 papers
60
+ french: 150 papers
61
+ ```
62
+
63
+ ## Filtering Criteria
64
+
65
+ The script uses the same criteria as the web application:
66
+
67
+ ### Multilingual Keywords
68
+ - multilingual, crosslingual, multi-lingual, cross-lingual
69
+ - low-resource language, low resource language
70
+ - low-resource, low resource
71
+
72
+ ### Language Names
73
+ - 100+ language names including: Hindi, Chinese, Korean, Arabic, Spanish, French, German, Japanese, etc.
74
+ - Regional language variations and dialects
75
+
76
+ ## Customization
77
+
78
+ You can modify the filtering criteria by editing the constants at the top of the script:
79
+
80
+ ```python
81
+ MULTILINGUAL_KEYWORDS = [
82
+ 'multilingual', 'crosslingual', 'multi lingual',
83
+ # Add your custom keywords here
84
+ ]
85
+
86
+ LANGUAGE_NAMES = [
87
+ 'afrikaans', 'albanian', 'amharic', 'arabic',
88
+ # Add more language names here
89
+ ]
90
+ ```
91
+
92
+ ## Error Handling
93
+
94
+ The script includes robust error handling:
95
+ - Checks for input file existence
96
+ - Handles malformed BibTeX entries gracefully
97
+ - Provides clear error messages
98
+ - Asks for confirmation before overwriting existing files
99
+
100
+ ## Performance
101
+
102
+ - Efficient regex-based parsing
103
+ - Memory-efficient processing for large files
104
+ - Fast keyword matching using set operations
105
+
106
+ ## Troubleshooting
107
+
108
+ ### File Not Found
109
+ ```
110
+ Error: anthology+abstracts.bib not found in current directory.
111
+ ```
112
+ **Solution**: Ensure the input file is in the same directory as the script.
113
+
114
+ ### No Papers Found
115
+ ```
116
+ No multilingual papers found. Check your keywords and language lists.
117
+ ```
118
+ **Solution**: Verify your BibTeX file contains papers with multilingual content, or adjust the keyword lists.
119
+
120
+ ### Encoding Issues
121
+ If you encounter encoding errors, the script uses UTF-8 encoding. Ensure your BibTeX file is properly encoded.
122
+
123
+ ## Comparison with JavaScript Version
124
+
125
+ This Python script produces identical results to the JavaScript web application:
126
+ - Same filtering logic
127
+ - Same LaTeX cleaning
128
+ - Same BibTeX output format
129
+ - Same keyword detection
130
+
131
+ The main advantage is that it can be run independently without a web browser and provides detailed statistics about the filtering process.
README.md CHANGED
@@ -1,10 +1,152 @@
1
- ---
2
- title: Multilingual Paperbase
3
- emoji: 🏒
4
- colorFrom: yellow
5
- colorTo: purple
6
- sdk: static
7
- pinned: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ # Multilingual Paper Database
2
+
3
+ A modern, responsive web application for searching and browsing academic papers with multilingual support.
4
+
5
+ ## Features
6
+
7
+ - πŸ” **Advanced Search**: Search across titles, abstracts, and authors with multiple keywords
8
+ - 🌍 **Multilingual Support**: Search in multiple languages simultaneously
9
+ - πŸ“… **Year Filtering**: Filter papers by publication year with dual-range slider
10
+ - 🎯 **Conference Filtering**: Filter by specific conferences or venues
11
+ - πŸ“ **Abstract Filtering**: Show/hide papers with or without abstracts
12
+ - πŸ“± **Responsive Design**: Works perfectly on desktop, tablet, and mobile
13
+ - ⚑ **Fast Performance**: Optimized for quick search and filtering
14
+ - 🎨 **Modern UI**: Beautiful, intuitive interface with smooth animations
15
+
16
+ ## Quick Start
17
+
18
+ 1. **Clone the repository**:
19
+ ```bash
20
+ git clone https://github.com/crystina-z/multilingual-paper-database.git
21
+ cd multilingual-paper-database
22
+ ```
23
+
24
+ 2. **Open in your browser**:
25
+ Simply open `index.html` in your web browser, or serve it using a local server:
26
+ ```bash
27
+ # Using Python
28
+ python -m http.server 8000
29
+
30
+ # Using Node.js
31
+ npx serve .
32
+
33
+ # Using PHP
34
+ php -S localhost:8000
35
+ ```
36
+
37
+ 3. **Access the application**:
38
+ Open your browser and go to `http://localhost:8000`
39
+
40
+ ## Data Management
41
+
42
+ ### Adding Papers
43
+ Use the Python script to add new papers to the database:
44
+ ```bash
45
+ python generate_multilingual_bib.py
46
+ ```
47
+
48
+ ### Managing Conferences
49
+ Edit `conferences.txt` to add or remove conferences from the filter options.
50
+
51
+ ## Deployment Options
52
+
53
+ ### 1. GitHub Pages (Recommended - Free)
54
+
55
+ 1. **Push to GitHub**:
56
+ ```bash
57
+ git add .
58
+ git commit -m "Initial commit"
59
+ git push origin main
60
+ ```
61
+
62
+ 2. **Enable GitHub Pages**:
63
+ - Go to your repository on GitHub
64
+ - Click "Settings" β†’ "Pages"
65
+ - Select "Deploy from a branch"
66
+ - Choose "main" branch and "/ (root)" folder
67
+ - Click "Save"
68
+
69
+ 3. **Your site will be available at**:
70
+ `https://crystina-z.github.io/multilingual-paper-database`
71
+
72
+ ### 2. Netlify (Free Tier)
73
+
74
+ 1. **Drag & Drop**:
75
+ - Go to [netlify.com](https://netlify.com)
76
+ - Drag your project folder to the deploy area
77
+ - Your site is instantly live!
78
+
79
+ 2. **Or connect to Git**:
80
+ - Connect your GitHub repository
81
+ - Netlify will automatically deploy on every push
82
+
83
+ ### 3. Vercel (Free Tier)
84
+
85
+ 1. **Install Vercel CLI**:
86
+ ```bash
87
+ npm i -g vercel
88
+ ```
89
+
90
+ 2. **Deploy**:
91
+ ```bash
92
+ vercel
93
+ ```
94
+
95
+ ### 4. Firebase Hosting (Free Tier)
96
+
97
+ 1. **Install Firebase CLI**:
98
+ ```bash
99
+ npm install -g firebase-tools
100
+ ```
101
+
102
+ 2. **Initialize and deploy**:
103
+ ```bash
104
+ firebase login
105
+ firebase init hosting
106
+ firebase deploy
107
+ ```
108
+
109
+ ## Customization
110
+
111
+ ### Styling
112
+ - Edit `styles.css` to customize colors, fonts, and layout
113
+ - The design uses CSS custom properties for easy theming
114
+
115
+ ### Functionality
116
+ - Modify `script.js` to add new features or change search behavior
117
+ - Update the data structure in the JavaScript file to match your paper format
118
+
119
+ ### Data
120
+ - Replace the sample data with your own paper database
121
+ - Ensure your data follows the expected JSON structure
122
+
123
+ ## Browser Support
124
+
125
+ - βœ… Chrome (recommended)
126
+ - βœ… Firefox
127
+ - βœ… Safari
128
+ - βœ… Edge
129
+ - ⚠️ Internet Explorer (limited support)
130
+
131
+ ## Contributing
132
+
133
+ 1. Fork the repository
134
+ 2. Create a feature branch
135
+ 3. Make your changes
136
+ 4. Test thoroughly
137
+ 5. Submit a pull request
138
+
139
+ ## License
140
+
141
+ This project is open source and available under the [MIT License](LICENSE).
142
+
143
+ ## Support
144
+
145
+ If you encounter any issues or have questions:
146
+ - Open an issue on GitHub
147
+ - Check the browser console for error messages
148
+ - Ensure all files are in the correct directory structure
149
+
150
  ---
151
 
152
+ **Happy researching! πŸŽ“**
add_conferences.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import json
3
+ from collections import defaultdict
4
+
5
+
6
+ # KNOWN_CONFERENCE_NAMES = ["COLING", "COLM", "EACL", "NAACL", "EMNLP", "AACL", "ACL"] # NOTE: NAACL and EACL need to come earlier than ACL
7
+ CONFERENCE_NAME_TO_ABBR = {
8
+ "Conference on Dependency Linguistics": "DepLing",
9
+ "Conference on Language Modeling": "COLM",
10
+ "European Chapter of the Association for Computational Linguistics": "EACL",
11
+ "North American Chapter of the Association for Computational Linguistics": "NAACL",
12
+ "Empirical Methods in Natural Language Processing": "EMNLP",
13
+ "Association for Computational Linguistics": "ACL",
14
+ "Annual Meeting of the Association for Computational Linguistics": "ACL",
15
+ "International Workshop on Health Text Mining and Information Analysis": "LUOHI",
16
+ "Conference on Computational Semantics": "IWCS",
17
+ "Conference on Machine Translation": "WMT",
18
+ "Conference Recent Advances in Natural Language Processing": "RANLP",
19
+ "Conference on Computational Linguistics": "COLING",
20
+ "Conference of Computational Linguistics": "NODALIDA",
21
+ "Conference on Language Resources and Evaluation": "LREC",
22
+ }
23
+
24
+ UNNEEDED_DESCRIPTIONS = [
25
+ "Shared Task",
26
+ "Short Papers",
27
+ "Poster Papers",
28
+ "Poster",
29
+ ]
30
+
31
+ KNOWN_CONFERENCE_NAMES = list(CONFERENCE_NAME_TO_ABBR.values())
32
+
33
+ def extract_conference_info():
34
+ """Extract unique conferences from conferences.txt and save to JSON"""
35
+
36
+ # Read the conferences file
37
+ with open('data/conferences.txt', 'r', encoding='utf-8') as f:
38
+ content = f.read()
39
+
40
+ # Split by lines and clean up
41
+ all_lines = [line.strip() for line in content.split('\n') if line.strip()]
42
+ lines = list(set(all_lines))
43
+
44
+ # Dictionary to store unique conferences with their years
45
+ conferences = defaultdict(set)
46
+ abbr2count = defaultdict(int)
47
+
48
+ for line in lines:
49
+ # Remove leading/trailing braces and clean up
50
+ _count = all_lines.count(line)
51
+
52
+ line = line.strip(' \{\},')
53
+ # line = line.replace("{", "")
54
+ # line = line.replace("}", "")
55
+ # line = line.strip()
56
+
57
+ # Skip empty lines
58
+ if not line:
59
+ continue
60
+
61
+ # Extract year from the conference name
62
+ year_match = re.search(r'\b(19|20)\d{2}\b', line)
63
+ year = year_match.group() if year_match else None
64
+
65
+ # Extract the base conference name (remove year and common suffixes)
66
+ # Remove year from the name for grouping
67
+ base_name = re.sub(r'\b(19|20)\d{2}\b', '', line)
68
+
69
+ # Remove common suffixes that don't affect the core conference name
70
+ if base_name.startswith("Findings"):
71
+ base_name = base_name.split(":")[-1].strip()
72
+ else:
73
+ base_name = re.sub(r'\s*:\s*.*$', '', base_name) # Remove everything after colon
74
+ base_name = re.sub(r'\s*--\s*.*$', '', base_name) # Remove everything after double dash
75
+ # remove everything within parentheses
76
+ base_name = re.sub(r'\s*\([^)]*\)\s*$', '', base_name) # Remove trailing parentheses
77
+
78
+ base_name = re.sub(r'\s*\([^)]*\)\s*$', '', base_name) # Remove trailing parentheses
79
+ base_name = re.sub(r'\s*Volume\s+\d+.*$', '', base_name, flags=re.IGNORECASE) # Remove volume info
80
+ base_name = re.sub(r'\s*Proceedings\s+of\s+', '', base_name, flags=re.IGNORECASE) # Remove "Proceedings of"
81
+
82
+ # Remove ordinal numbers (1st, 2nd, 3rd, 4th, 5th, 6th, 7th, 8th, 9th, 10th, 11th, 12th, etc.)
83
+ base_name = re.sub(r'\b\d+(?:st|nd|rd|th)\s+', '', base_name, flags=re.IGNORECASE)
84
+ base_name = base_name.replace("}", "")
85
+
86
+ # Remove any words before the first occurrence of "Conference"
87
+ conference_match = re.search(r'\bConference\b', base_name, re.IGNORECASE)
88
+ if conference_match:
89
+ start_pos = conference_match.start()
90
+ base_name = base_name[start_pos:]
91
+
92
+ # Remove "the First", "the Second", etc. from the beginning
93
+ base_name = re.sub(r'^the\s+(?:First|Second|Third|Fourth|Fifth|Sixth|Seventh|Eighth|Ninth|Tenth|Eleventh|Twelfth|Thirteenth|Fourteenth|Fifteenth|Sixteenth|Seventeenth|Eighteenth|Nineteenth|Twentieth|Twenty-first|Twenty-second|Twenty-third|Twenty-fourth|Twenty-fifth|Twenty-sixth|Twenty-seventh|Twenty-eighth|Twenty-ninth|Thirtieth|Thirty-first|Thirty-second|Thirty-third|Thirty-fourth|Thirty-fifth|Thirty-sixth|Thirty-seventh|Thirty-eighth|Thirty-ninth|Fortieth|Forty-first|Forty-second|Forty-third|Forty-fourth|Forty-fifth|Forty-sixth|Forty-seventh|Forty-eighth|Forty-ninth|Fiftieth|Fifty-first|Fifty-second|Fifty-third|Fifty-fourth|Fifty-fifth|Fifty-sixth|Fifty-seventh|Fifty-eighth|Fifty-ninth|Sixtieth|Sixty-first|Sixty-second|Sixty-third|Sixty-fourth|Sixty-fifth|Sixty-sixth|Sixty-seventh|Sixty-eighth|Sixty-ninth|Seventieth|Seventy-first|Seventy-second|Seventy-third|Seventy-fourth|Seventy-fifth|Seventy-sixth|Seventy-seventh|Seventy-eighth|Seventy-ninth|Eightieth|Eighty-first|Eighty-second|Eighty-third|Eighty-fourth|Eighty-fifth|Eighty-sixth|Eighty-seventh|Eighty-eighth|Eighty-ninth|Ninetieth|Ninety-first|Ninety-second|Ninety-third|Ninety-fourth|Ninety-fifth|Ninety-sixth|Ninety-seventh|Ninety-eighth|Ninety-ninth|Hundredth)\s+', '', base_name, flags=re.IGNORECASE)
94
+
95
+ # Remove Roman numerals (I, II, III, IV, V, VI, VII, VIII, IX, X, XI, XII, XIII, XIV, XV, XVI, XVII, XVIII, XIX, XX, etc.)
96
+ # This needs to happen BEFORE punctuation removal to catch Roman numerals properly
97
+ # More comprehensive pattern to catch all Roman numerals
98
+ base_name = re.sub(r'\b(?:I{1,3}|IV|VI{0,3}|IX|X{1,3}|XI{0,3}|XV|XX{0,3}|XXX{0,3}|XL|L|LX{0,3}|LXX{0,3}|LXXX|XC|C|CC{0,3}|CD|D|DC{0,3}|DCC{0,3}|DCCC|CM|M{0,3})\b', '', base_name)
99
+
100
+ # Also try a simpler approach - remove any sequence of I, V, X, L, C, D, M that looks like a Roman numeral
101
+ base_name = re.sub(r'\b[IVXLCDM]+\b', '', base_name)
102
+
103
+ # Replace punctuation with whitespace
104
+ base_name = re.sub(r'[^\w\s]', ' ', base_name)
105
+
106
+ # Replace all numbers with whitespace
107
+ base_name = re.sub(r'\d+', ' ', base_name)
108
+
109
+ # base_name = base_name.replace("Shared Task ", "")
110
+ for unneeded_description in UNNEEDED_DESCRIPTIONS:
111
+ base_name = base_name.replace(unneeded_description, "")
112
+
113
+ for conf_name, conf_abbr in CONFERENCE_NAME_TO_ABBR.items():
114
+ if conf_name.lower() in base_name.lower():
115
+ base_name = base_name.replace(conf_name, conf_abbr)
116
+ break
117
+
118
+ for conf in KNOWN_CONFERENCE_NAMES:
119
+ if conf.lower() in base_name.lower():
120
+ base_name = conf
121
+ break
122
+
123
+ if "de la" in base_name or " le " in base_name or base_name == "Conference":
124
+ base_name = "Others"
125
+
126
+ if "Multi lingual" in base_name:
127
+ base_name = base_name.replace("Multi lingual", "Multilingual")
128
+
129
+ # Clean up extra whitespace and consecutive whitespace
130
+ base_name = re.sub(r'\s+', ' ', base_name).strip()
131
+
132
+ # Skip if base name is too short
133
+ # if len(base_name) < 5:
134
+ # base_name = "Unknown"
135
+
136
+ # Add to conferences dictionary
137
+ # if year:
138
+ # conferences[base_name].add(int(year))
139
+ # else:
140
+ # conferences[base_name].add(None)
141
+ conferences[base_name].add(line)
142
+ abbr2count[base_name] += _count
143
+
144
+ # conf_abbr2keywords = {
145
+ # "ACL": ["Association for Computational Linguistics"],
146
+ # "EMNLP": ["Empirical Methods in Natural Language Processing"],
147
+ # "NAACL": ["North American Chapter of the Association for Computational Linguistics"],
148
+ # "EACL": ["European Chapter of the Association for Computational Linguistics"],
149
+ # "COLM": ["Conference on Computational Linguistics"],
150
+ # }
151
+
152
+ # print(f"Found {len(conferences)} unique conferences from {len(lines)} lines")
153
+ # for i, conf in enumerate(sorted(conferences.keys())):
154
+ # print(f"{i+1}. {conf}")
155
+ # if i > 200: break
156
+ # import pdb; pdb.set_trace()
157
+
158
+ conference_to_save = {}
159
+ others = []
160
+ for i, (conf, count) in enumerate(sorted(abbr2count.items(), key=lambda x: x[1], reverse=True)):
161
+ ratio = count / len(all_lines)
162
+ if ratio < 0.001 or conf == "Others":
163
+ others.append((conf, count))
164
+ continue
165
+
166
+ conference_to_save[conf] = {
167
+ "count": count,
168
+ "conferences": tuple(conferences[conf]),
169
+ }
170
+ print(f"{i+1}. {conf}: {count} [{ratio * 100:.1f}%]")
171
+
172
+ conference_to_save[f"Others ({len(others)} Venues)"] = {
173
+ "count": sum(count for conf, count in others),
174
+ "conferences": tuple(conf for conf, count in others),
175
+ }
176
+
177
+ # Save to JSON file
178
+ with open('data/unique_conferences.json', 'w', encoding='utf-8') as f:
179
+ json.dump(conference_to_save, f, indent=2, ensure_ascii=False)
180
+
181
+ print(f"Extracted {len(conference_to_save)} unique conferences")
182
+ print(f"Saved to data/unique_conferences.json")
183
+
184
+ if __name__ == "__main__":
185
+ extract_conference_info()
conferences.txt ADDED
File without changes
data/.gitattributes ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ multilingual_papers.bib filter=lfs diff=lfs merge=lfs -text
2
+ unique_conferences.json filter=lfs diff=lfs merge=lfs -text
3
+ anthology+abstracts.bib filter=lfs diff=lfs merge=lfs -text
4
+ conferences.txt filter=lfs diff=lfs merge=lfs -text
5
+ multilingual_papers-v0.bib filter=lfs diff=lfs merge=lfs -text
data/anthology+abstracts.bib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4b7a39730b6ca587233c6fdea9391d4a24442e79ef71a07df6deb5e43cb6be09
3
+ size 142896111
data/conferences.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9d925ff7b4fb2b0c557f076e4f7bac9e565d18f53258e3289576e09e5a3fdfca
3
+ size 3190178
data/multilingual_papers-v0.bib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:39d4b42383f677e85f4b6776594b40ef411aea031e40b74f4b4881f3ab55a3eb
3
+ size 32970642
data/multilingual_papers.bib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f769ba08f9c20651e0a23324a099ade206fd028a115c67eebbe64894e149d033
3
+ size 32429626
data/unique_conferences.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0189f438acf3ec8f1c98d09c56eb7b9222484af7cd8d7413d54388cb93dc708a
3
+ size 190073
generate_multilingual_bib.py ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Multilingual Paper BibTeX Generator
4
+
5
+ This script parses anthology+abstracts.bib and generates multilingual_papers.bib
6
+ containing only papers related to multilingual NLP research.
7
+
8
+ Usage:
9
+ python generate_multilingual_bib.py
10
+
11
+ Requirements:
12
+ - anthology+abstracts.bib file in the same directory
13
+ """
14
+
15
+ import re
16
+ import os
17
+ from typing import List, Dict, Set
18
+ from tqdm import tqdm
19
+ from collections import defaultdict
20
+
21
+ # Multilingual keywords for filtering (same as JavaScript version)
22
+ MULTILINGUAL_KEYWORDS = [
23
+ 'multilingual', 'crosslingual', 'multi lingual', 'cross lingual',
24
+ 'multi-lingual', 'cross-lingual', 'low-resource language', 'low resource language',
25
+ # 'low-resource', 'low resource',
26
+ 'multi-language', 'multi language', 'cross-language', 'cross language',
27
+ 'language transfer',
28
+ 'code-switching', 'code switching', 'language adaptation',
29
+ 'language pair', 'bilingual', 'trilingual', 'polyglot',
30
+ # 'machine translation', 'neural machine translation', 'speech translation', 'translation', 'nmt',
31
+ 'translation', "nmt",
32
+ 'transliteration',
33
+ 'multilingual bert', 'xlm', 'mbert', 'xlm-roberta',
34
+ 'language identification', 'language detection'
35
+ ]
36
+
37
+ # Language names for filtering (same as JavaScript version)
38
+ LANGUAGE_NAMES = [
39
+ 'afrikaans', 'albanian', 'amharic', 'arabic', 'armenian', 'azerbaijani', 'basque', 'belarusian', 'bengali', 'bosnian', 'bulgarian', 'catalan', 'cebuano', 'chinese', 'croatian', 'czech', 'danish', 'dutch', 'esperanto', 'estonian', 'filipino', 'finnish', 'french', 'galician', 'georgian', 'german', 'greek', 'gujarati', 'haitian', 'hausa', 'hawaiian', 'hebrew', 'hindi', 'hmong', 'hungarian', 'icelandic', 'igbo', 'indonesian', 'irish', 'italian', 'japanese', 'javanese', 'kannada', 'kazakh', 'khmer', 'korean', 'kurdish', 'kyrgyz', 'lao', 'latin', 'latvian', 'lithuanian', 'luxembourgish', 'macedonian', 'malagasy', 'malay', 'malayalam', 'maltese', 'maori', 'marathi', 'mongolian', 'myanmar', 'nepali', 'norwegian', 'odia', 'pashto', 'persian', 'polish', 'portuguese', 'punjabi', 'romanian', 'russian', 'samoan', 'scots gaelic', 'serbian', 'sesotho', 'shona', 'sindhi', 'sinhala', 'slovak', 'slovenian', 'somali', 'spanish', 'sundanese', 'swahili', 'swedish', 'tagalog', 'tajik', 'tamil', 'telugu', 'thai', 'turkish', 'ukrainian', 'urdu', 'uzbek', 'vietnamese', 'welsh', 'xhosa', 'yiddish', 'yoruba', 'zulu',
40
+ # Additional language variations and names
41
+ 'mandarin', 'cantonese', 'hindi', 'urdu', 'bengali', 'tamil', 'telugu', 'marathi', 'gujarati', 'kannada', 'malayalam', 'punjabi', 'odia', 'assamese', 'maithili', 'sanskrit', 'kashmiri', 'konkani', 'manipuri', 'nepali', 'sindhi', 'dogri', 'bodo', 'santali', 'khasi', 'mizo', 'garo', 'naga', 'tibetan', 'dzongkha', 'sikkimese', 'lepcha', 'limbu', 'tamang', 'gurung', 'magar', 'tharu', 'tulu'
42
+ 'african', 'indian', "asian", "indigenous",
43
+ ]
44
+
45
+ def clean_latex_commands(text: str) -> str:
46
+ """
47
+ Clean LaTeX commands from text (same logic as JavaScript version).
48
+ """
49
+ if not text:
50
+ return ''
51
+
52
+ # Remove LaTeX commands with braces
53
+ text = re.sub(r'\\[a-zA-Z]+\{([^}]*)\}', r'\1', text)
54
+ # Remove simple LaTeX commands
55
+ text = re.sub(r'\\[a-zA-Z]+', '', text)
56
+ # Remove braces
57
+ text = re.sub(r'\{\\?([^}]*)\}', r'\1', text)
58
+ # Replace escaped characters
59
+ text = text.replace('\\"', '"')
60
+ text = text.replace("\\'", "'")
61
+ text = text.replace('\\&', '&')
62
+ text = text.replace('\\%', '%')
63
+ text = text.replace('\\_', '_')
64
+ text = text.replace('\\$', '$')
65
+ # Normalize whitespace
66
+ text = re.sub(r'\s+', ' ', text)
67
+
68
+ return text.strip()
69
+
70
+ def is_multilingual_paper(paper: Dict[str, str]) -> bool:
71
+ """
72
+ Determine if a paper is multilingual (same logic as JavaScript version).
73
+ """
74
+ text = f"{paper.get('title', '')} {paper.get('abstract', '')}".lower()
75
+
76
+ # Check for multilingual keywords
77
+ for keyword in MULTILINGUAL_KEYWORDS:
78
+ if keyword.lower() in text:
79
+ return True, keyword
80
+
81
+ # Check for language names
82
+ for language in LANGUAGE_NAMES:
83
+ # require language to be matched perfectly
84
+ if language.lower() in text.split():
85
+ return True, language
86
+
87
+ return False, None
88
+
89
+ def extract_keywords(paper: Dict[str, str]) -> Set[str]:
90
+ """
91
+ Extract multilingual keywords from a paper (same logic as JavaScript version).
92
+ """
93
+ keywords = set()
94
+ text = f"{paper.get('title', '')} {paper.get('abstract', '')}".lower()
95
+
96
+ # Extract multilingual keywords
97
+ for keyword in MULTILINGUAL_KEYWORDS:
98
+ if keyword.lower() in text:
99
+ keywords.add(keyword)
100
+
101
+ # Extract language names
102
+ for language in LANGUAGE_NAMES:
103
+ if language.lower() in text:
104
+ keywords.add(language)
105
+
106
+ return keywords
107
+
108
+ def parse_bibtex_entry(entry: str) -> Dict[str, str]:
109
+ """
110
+ Parse a single BibTeX entry (same logic as JavaScript version).
111
+ """
112
+ paper = {}
113
+
114
+ # Extract entry type and key
115
+ type_match = re.match(r'@(\w+)\{([^,]+)', entry)
116
+ if type_match:
117
+ paper['type'] = type_match.group(1)
118
+ paper['key'] = type_match.group(2)
119
+ else:
120
+ # If we can't parse the type/key, skip this entry
121
+ return None
122
+
123
+ # Extract fields
124
+ fields = ['title', 'author', 'abstract', 'year', 'booktitle', 'journal', 'pages']
125
+
126
+ for field in fields:
127
+ # Match both {content} and "content" formats
128
+ pattern = rf'{field}\s*=\s*{{([^}}]*)}}|{field}\s*=\s*"([^"]*)"'
129
+ match = re.search(pattern, entry, re.IGNORECASE)
130
+ if match:
131
+ value = match.group(1) or match.group(2)
132
+ # Clean up LaTeX commands
133
+ value = clean_latex_commands(value)
134
+ paper[field] = value.strip()
135
+
136
+ # Extract year from key if not found in fields
137
+ if not paper.get('year') and paper.get('key'):
138
+ year_match = re.search(r'\d{4}', paper['key'])
139
+ if year_match:
140
+ paper['year'] = year_match.group(0)
141
+
142
+ # Determine if paper is multilingual
143
+ paper['is_multilingual'], matched_keyword = is_multilingual_paper(paper)
144
+ paper['keywords'] = list(extract_keywords(paper))
145
+ paper['matched_keyword'] = matched_keyword
146
+
147
+ return paper
148
+
149
+ def parse_bibtex(bib_text: str) -> List[Dict[str, str]]:
150
+ """
151
+ Parse BibTeX text into list of paper dictionaries (same logic as JavaScript version).
152
+ """
153
+ papers = []
154
+ # Split by @ to get individual entries
155
+ entries = re.split(r'(?=@)', bib_text)
156
+ n_missing, n_total = 0, len(entries)
157
+
158
+ for entry in tqdm(entries, desc="Parsing BibTeX entries"):
159
+ if not entry.strip():
160
+ continue
161
+
162
+ # try:
163
+ paper = parse_bibtex_entry(entry)
164
+ if paper and (paper.get('title') or paper.get('abstract')):
165
+ papers.append(paper)
166
+ elif paper is None:
167
+ n_missing += 1
168
+ # print(f"Warning: Skipping malformed entry (no type/key found)")
169
+ # except Exception as e:
170
+ # print(f"Warning: Error parsing entry: {e}")
171
+ continue
172
+
173
+ keyword2count = defaultdict(int)
174
+ for paper in papers:
175
+ if not paper['matched_keyword']: continue
176
+ keyword2count[paper['matched_keyword']] += 1
177
+ n_multilingual_papers = sum(keyword2count.values())
178
+
179
+ print(f"Found {len(papers)} papers out of {n_total} total papers. Ratio: {len(papers)/n_total*100:.1f}%")
180
+ print(f"Missing {n_missing} papers out of {n_total} total papers. Ratio: {n_missing/n_total*100:.1f}%")
181
+
182
+ # sort by keyword count
183
+ keyword2count = sorted(keyword2count.items(), key=lambda x: x[1], reverse=True)
184
+ for keyword, count in keyword2count:
185
+ print(f"\t {keyword}: {count} papers ({count/n_multilingual_papers*100:.1f}%)")
186
+
187
+ return papers
188
+
189
+ def generate_bibtex_content(papers: List[Dict[str, str]]) -> str:
190
+ """
191
+ Generate BibTeX content from paper dictionaries (same logic as JavaScript version).
192
+ """
193
+ content = ''
194
+
195
+ for paper in tqdm(papers, desc="Generating BibTeX content"):
196
+ # Check if paper has required fields
197
+ if not paper.get('type') or not paper.get('key'):
198
+ print(f"Warning: Skipping paper without type or key: {paper.get('title', 'Unknown')[:50]}...")
199
+ continue
200
+
201
+ # Reconstruct the original BibTeX entry
202
+ content += f"@{paper['type']}{{{paper['key']},\n"
203
+
204
+ fields = ['title', 'author', 'abstract', 'year', 'booktitle', 'journal', 'pages']
205
+ for field in fields:
206
+ if paper.get(field):
207
+ content += f" {field} = {{{paper[field]}}},\n"
208
+
209
+ # Remove trailing comma and add closing brace
210
+ content = content.rstrip(',\n') + '\n'
211
+ content += '}\n\n'
212
+
213
+ return content
214
+
215
+ def main():
216
+ """
217
+ Main function to generate multilingual_papers.bib.
218
+ """
219
+ input_file = 'data/anthology+abstracts.bib'
220
+ output_file = 'data/multilingual_papers.bib'
221
+
222
+ # Check if input file exists
223
+ if not os.path.exists(input_file):
224
+ print(f"Error: {input_file} not found in current directory.")
225
+ print("Please ensure the file exists and run the script again.")
226
+ return
227
+
228
+ # Check if output file already exists
229
+ if os.path.exists(output_file):
230
+ print(f"Warning: {output_file} already exists.")
231
+ response = input("Do you want to overwrite it? (y/N): ")
232
+ if response.lower() != 'y':
233
+ print("Operation cancelled.")
234
+ return
235
+
236
+ print(f"Reading {input_file}...")
237
+
238
+ # try:
239
+ if True:
240
+ # Read the input file
241
+ with open(input_file, 'r', encoding='utf-8') as f:
242
+ bib_text = f.read()
243
+
244
+ all_papers = parse_bibtex(bib_text)
245
+ print(f"Found {len(all_papers)} total papers")
246
+
247
+ # Filter multilingual papers
248
+ multilingual_papers = [paper for paper in all_papers if paper['is_multilingual']]
249
+ print(f"Found {len(multilingual_papers)} multilingual papers out of {len(all_papers)} total papers. Ratio: {len(multilingual_papers)/len(all_papers)*100:.1f}%")
250
+
251
+ if not multilingual_papers:
252
+ print("No multilingual papers found. Check your keywords and language lists.")
253
+ return
254
+
255
+ # Generate BibTeX content
256
+ print("Generating BibTeX content...")
257
+ bib_content = generate_bibtex_content(multilingual_papers)
258
+
259
+ # Write to output file
260
+ print(f"Writing to {output_file}...")
261
+ with open(output_file, 'w', encoding='utf-8') as f:
262
+ f.write(bib_content)
263
+
264
+ print(f"Successfully generated {output_file} with {len(multilingual_papers)} papers!")
265
+
266
+ # Show some statistics
267
+ print("\nStatistics:")
268
+ print(f" Total papers processed: {len(all_papers)}")
269
+ print(f" Multilingual papers found: {len(multilingual_papers)}")
270
+ print(f" Percentage multilingual: {len(multilingual_papers)/len(all_papers)*100:.1f}%")
271
+
272
+ # Show top keywords
273
+ all_keywords = []
274
+ for paper in multilingual_papers:
275
+ all_keywords.extend(paper['keywords'])
276
+
277
+ keyword_counts = {}
278
+ for keyword in all_keywords:
279
+ keyword_counts[keyword] = keyword_counts.get(keyword, 0) + 1
280
+
281
+ top_keywords = sorted(keyword_counts.items(), key=lambda x: x[1], reverse=True)[:10]
282
+ print("\nTop 10 keywords found:")
283
+ for keyword, count in top_keywords:
284
+ print(f" {keyword}: {count} papers")
285
+
286
+ # except Exception as e:
287
+ # print(f"Error: {e}")
288
+ # return
289
+
290
+ if __name__ == "__main__":
291
+ main()
index.html CHANGED
@@ -1,19 +1,305 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Multilingual Paper Database</title>
7
+ <link rel="stylesheet" href="styles.css">
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
10
+ <!-- <base href="/multilingual-paperbase/"> -->
11
+ </head>
12
+ <body>
13
+ <div class="container">
14
+ <header class="header">
15
+ <div class="header-content">
16
+ <h1 class="title">
17
+ <i class="fas fa-globe"></i>
18
+ Multilingual Paper Database
19
+ </h1>
20
+ <p class="subtitle">Filter and search through multilingual NLP research papers</p>
21
+ </div>
22
+ </header>
23
+
24
+ <main class="main-content">
25
+ <div class="search-section">
26
+ <div class="search-container">
27
+ <div class="search-stats">
28
+ <span id="resultCount">Loading papers...</span>
29
+ </div>
30
+ <div class="search-box">
31
+ <i class="fas fa-search search-icon"></i>
32
+ <input
33
+ type="text"
34
+ id="searchInput"
35
+ placeholder="Add keyword to search papers..."
36
+ class="search-input"
37
+ >
38
+ <button id="addSearchKeyword" class="add-keyword-btn search-add-btn" title="Add search keyword">
39
+ <i class="fas fa-plus"></i>
40
+ </button>
41
+ <button id="clearSearch" class="clear-btn" title="Clear all search keywords">
42
+ <i class="fas fa-times"></i>
43
+ </button>
44
+ </div>
45
+ <div class="search-keywords-container" id="searchKeywordsContainer">
46
+ <!-- Search keywords will be displayed here -->
47
+ </div>
48
+ </div>
49
+
50
+ <div class="search-container">
51
+ <div class="search-stats">
52
+ <span id="removeResultCount">All papers included</span>
53
+ </div>
54
+ <div class="search-box remove-search-box">
55
+ <i class="fas fa-minus-circle search-icon"></i>
56
+ <input
57
+ type="text"
58
+ id="removeSearchInput"
59
+ placeholder="Add keyword to remove papers..."
60
+ class="search-input"
61
+ >
62
+ <button id="addRemoveKeyword" class="add-keyword-btn" title="Add removal keyword">
63
+ <i class="fas fa-plus"></i>
64
+ </button>
65
+ <button id="clearRemoveSearch" class="clear-btn" title="Clear all removal keywords">
66
+ <i class="fas fa-times"></i>
67
+ </button>
68
+ </div>
69
+ <div class="remove-keywords-container" id="removeKeywordsContainer">
70
+ <!-- Removal keywords will be displayed here -->
71
+ </div>
72
+ </div>
73
+ </div>
74
+
75
+ <div class="filters-section">
76
+ <div class="year-filter">
77
+ <label class="filter-label">
78
+ <i class="fas fa-calendar-alt"></i>
79
+ Publication Year: <span id="yearRange">All Years</span>
80
+ </label>
81
+ <div class="year-input-container">
82
+ <div class="year-input-group">
83
+ <label for="yearInputMin">From:</label>
84
+ <input
85
+ type="number"
86
+ id="yearInputMin"
87
+ class="year-input"
88
+ min="1960"
89
+ max="2025"
90
+ value="1960"
91
+ placeholder="1960"
92
+ >
93
+ </div>
94
+ <div class="year-input-group">
95
+ <label for="yearInputMax">To:</label>
96
+ <input
97
+ type="number"
98
+ id="yearInputMax"
99
+ class="year-input"
100
+ min="1960"
101
+ max="2025"
102
+ value="2025"
103
+ placeholder="2025"
104
+ >
105
+ </div>
106
+ </div>
107
+ <button id="clearYearFilter" class="clear-year-btn" title="Clear year filter">
108
+ <i class="fas fa-times"></i>
109
+ Clear
110
+ </button>
111
+ </div>
112
+
113
+ <div class="conference-filter">
114
+ <label class="filter-label">
115
+ <i class="fas fa-building"></i>
116
+ Conference: <span id="conferenceCount">All Conferences</span>
117
+ <span class="conference-hint">(Hold Ctrl/Cmd to select multiple conferences)</span>
118
+ </label>
119
+ <div class="conference-select-container">
120
+ <select id="conferenceSelect" class="conference-select" multiple>
121
+ <!-- Conference options will be populated here -->
122
+ </select>
123
+ <div class="selected-conferences" id="selectedConferences">
124
+ <!-- Selected conferences will be displayed here -->
125
+ </div>
126
+ </div>
127
+ <button id="clearConferenceFilter" class="clear-conference-btn" title="Clear conference filter">
128
+ <i class="fas fa-times"></i>
129
+ Clear
130
+ </button>
131
+ </div>
132
+
133
+ <div class="abstract-filter">
134
+ <label class="filter-label">
135
+ <i class="fas fa-file-text"></i>
136
+ Missing Information Filter
137
+ </label>
138
+ <div class="abstract-filter-container">
139
+ <div class="filter-option">
140
+ <label class="checkbox-label">
141
+ <input type="checkbox" id="includeNoAbstract" checked>
142
+ <span class="checkmark"></span>
143
+ Include papers without abstracts
144
+ <span class="filter-info" id="abstractFilterInfo">(Showing all papers)</span>
145
+ </label>
146
+ </div>
147
+ <div class="filter-option">
148
+ <label class="checkbox-label">
149
+ <input type="checkbox" id="includeNoAuthor" checked>
150
+ <span class="checkmark"></span>
151
+ Include papers without author information
152
+ <span class="filter-info" id="authorFilterInfo">(Showing all papers)</span>
153
+ </label>
154
+ </div>
155
+ </div>
156
+ </div>
157
+
158
+ <div class="starred-filter">
159
+ <!-- <label class="filter-label">
160
+ <i class="fas fa-star"></i>
161
+ Starred Papers Filter
162
+ </label> -->
163
+ <!-- <div class="starred-filter-container"> -->
164
+ <div class="filter-option">
165
+ <label class="checkbox-label">
166
+ <input type="checkbox" id="showOnlyStarred">
167
+ <span class="checkmark"></span>
168
+ Show only starred papers
169
+ <span class="filter-info" id="starredFilterInfo">(Showing all papers)</span>
170
+ </label>
171
+ </div>
172
+ <!-- </div> -->
173
+ </div>
174
+ </div>
175
+
176
+ <div class="export-section">
177
+ <div class="export-container">
178
+ <h3 class="export-title">
179
+ <i class="fas fa-save"></i>
180
+ Save & Load Filters
181
+ </h3>
182
+ <div class="export-buttons">
183
+ <button id="saveStatus" class="export-btn save-btn" title="Save current filter status and results">
184
+ <i class="fas fa-download"></i>
185
+ Save Status
186
+ </button>
187
+ <button id="loadStatus" class="export-btn load-btn" title="Load previously saved filter status">
188
+ <i class="fas fa-upload"></i>
189
+ Load Status
190
+ </button>
191
+ <button id="exportBibTeX" class="export-btn export-bib-btn" title="Export current filtered results to BibTeX">
192
+ <i class="fas fa-file-export"></i>
193
+ Export BibTeX
194
+ </button>
195
+ <button id="exportStarred" class="export-btn export-starred-btn" title="Export starred papers to BibTeX">
196
+ <i class="fas fa-star"></i>
197
+ Export Starred (0)
198
+ </button>
199
+ </div>
200
+ </div>
201
+ <!-- <div class="export-info">
202
+ <p><i class="fas fa-info-circle"></i> Save current search filters and results to restore later, or export the filtered papers as BibTeX.</p>
203
+ </div> -->
204
+ </div>
205
+
206
+ <div class="pagination-section">
207
+ <div class="pagination-info">
208
+ <span id="paginationInfo">Showing 1-10 of 0 papers</span>
209
+ </div>
210
+ <div class="pagination-controls">
211
+ <button id="prevPage" class="pagination-btn" disabled>
212
+ <i class="fas fa-chevron-left"></i>
213
+ Previous
214
+ </button>
215
+ <div class="page-numbers" id="pageNumbers">
216
+ <!-- Page numbers will be generated here -->
217
+ </div>
218
+ <div class="page-input-container">
219
+ <label for="pageInput" class="page-input-label">Go to:</label>
220
+ <input
221
+ type="number"
222
+ id="pageInput"
223
+ class="page-input"
224
+ min="1"
225
+ placeholder="Page"
226
+ title="Enter page number"
227
+ >
228
+ <button id="goToPage" class="page-go-btn" title="Go to page">
229
+ <i class="fas fa-arrow-right"></i>
230
+ </button>
231
+ </div>
232
+ <button id="nextPage" class="pagination-btn" disabled>
233
+ Next
234
+ <i class="fas fa-chevron-right"></i>
235
+ </button>
236
+ </div>
237
+ </div>
238
+
239
+ <div class="papers-container">
240
+ <div id="papersList" class="papers-list">
241
+ <!-- Papers will be loaded here -->
242
+ </div>
243
+
244
+ <div id="loadingIndicator" class="loading">
245
+ <div class="spinner"></div>
246
+ <p>Loading papers...</p>
247
+ </div>
248
+
249
+ <div id="noResults" class="no-results" style="display: none;">
250
+ <i class="fas fa-search"></i>
251
+ <h3>No papers found</h3>
252
+ <p>Try adjusting your search terms or filters</p>
253
+ </div>
254
+ </div>
255
+ </main>
256
+
257
+ <footer class="footer">
258
+ <p>&copy; 2025 Multilingual Paper Database</p>
259
+ </footer>
260
+ </div>
261
+
262
+ <!-- Paper Detail Modal -->
263
+ <div id="paperModal" class="modal">
264
+ <div class="modal-content">
265
+ <div class="modal-header">
266
+ <div class="modal-title-container">
267
+ <h2 id="modalTitle"></h2>
268
+ <button class="star-button" id="modalStarButton" title="Add to starred">
269
+ <i class="far fa-star"></i>
270
+ </button>
271
+ <button class="find-paper-btn" id="modalFindPaperButton" title="Search for this paper on Google">
272
+ <i class="fas fa-search"></i>
273
+ </button>
274
+ </div>
275
+ </div>
276
+ <div class="modal-body">
277
+ <div class="paper-details">
278
+ <div class="detail-section">
279
+ <h3><i class="fas fa-users"></i> Authors</h3>
280
+ <p id="modalAuthors"></p>
281
+ </div>
282
+ <div class="detail-section">
283
+ <h3><i class="fas fa-book"></i> Abstract</h3>
284
+ <p id="modalAbstract"></p>
285
+ </div>
286
+ <div class="detail-section">
287
+ <h3><i class="fas fa-calendar"></i> Year</h3>
288
+ <p id="modalYear"></p>
289
+ </div>
290
+ <div class="detail-section">
291
+ <h3><i class="fas fa-building"></i> Conference</h3>
292
+ <p id="modalConference"></p>
293
+ </div>
294
+ <div class="detail-section">
295
+ <h3><i class="fas fa-tags"></i> Keywords</h3>
296
+ <p id="modalKeywords"></p>
297
+ </div>
298
+ </div>
299
+ </div>
300
+ </div>
301
+ </div>
302
+
303
+ <script src="script.js"></script>
304
+ </body>
305
+ </html>
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # No external dependencies required for this script
2
+ # Uses only Python standard library modules:
3
+ # - re (regular expressions)
4
+ # - os (operating system interface)
5
+ # - typing (type hints)
script.js ADDED
@@ -0,0 +1,1598 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Global variables
2
+ let allPapers = [];
3
+ let filteredPapers = [];
4
+ let searchKeywords = [];
5
+ let removeSearchKeywords = [];
6
+ let currentPage = 1;
7
+ let papersPerPage = 10;
8
+ let selectedYearMin = 1960;
9
+ let selectedYearMax = 2025;
10
+ let yearFilterActive = false;
11
+ let selectedConferences = [];
12
+ let conferenceFilterActive = false;
13
+ let conferencesData = {};
14
+ let includeNoAbstract = true;
15
+ let abstractFilterActive = false;
16
+ let includeNoAuthor = true;
17
+ let authorFilterActive = false;
18
+ let showOnlyStarred = false;
19
+ let starredFilterActive = false;
20
+ let starredPapers = []; // Array to store starred paper keys
21
+
22
+ // Multilingual keywords for filtering
23
+ const multilingualKeywords = [
24
+ 'multilingual', 'crosslingual', 'multi lingual', 'cross lingual',
25
+ 'multi-lingual', 'cross-lingual', 'low-resource language', 'low resource language',
26
+ 'multi-language', 'multi language', 'cross-language', 'cross language',
27
+ 'language transfer',
28
+ 'code-switching', 'code switching', 'language adaptation',
29
+ 'language pair', 'bilingual', 'trilingual', 'polyglot',
30
+ 'translation', "nmt",
31
+ 'transliteration',
32
+ 'multilingual bert', 'xlm', 'mbert', 'xlm-roberta',
33
+ 'language identification', 'language detection'
34
+ ]
35
+
36
+ // Language names for filtering
37
+ const languageNames = [
38
+ 'english', 'afrikaans', 'albanian', 'amharic', 'arabic', 'armenian', 'azerbaijani', 'basque', 'belarusian', 'bengali', 'bosnian', 'bulgarian', 'catalan', 'cebuano', 'chinese', 'croatian', 'czech', 'danish', 'dutch', 'esperanto', 'estonian', 'filipino', 'finnish', 'french', 'galician', 'georgian', 'german', 'greek', 'gujarati', 'haitian', 'hausa', 'hawaiian', 'hebrew', 'hindi', 'hmong', 'hungarian', 'icelandic', 'igbo', 'indonesian', 'irish', 'italian', 'japanese', 'javanese', 'kannada', 'kazakh', 'khmer', 'korean', 'kurdish', 'kyrgyz', 'lao', 'latin', 'latvian', 'lithuanian', 'luxembourgish', 'macedonian', 'malagasy', 'malay', 'malayalam', 'maltese', 'maori', 'marathi', 'mongolian', 'myanmar', 'nepali', 'norwegian', 'odia', 'pashto', 'persian', 'polish', 'portuguese', 'punjabi', 'romanian', 'russian', 'samoan', 'scots gaelic', 'serbian', 'sesotho', 'shona', 'sindhi', 'sinhala', 'slovak', 'slovenian', 'somali', 'spanish', 'sundanese', 'swahili', 'swedish', 'tagalog', 'tajik', 'tamil', 'telugu', 'thai', 'turkish', 'ukrainian', 'urdu', 'uzbek', 'vietnamese', 'welsh', 'xhosa', 'yiddish', 'yoruba', 'zulu',
39
+ // Additional language variations and names
40
+ 'mandarin', 'cantonese', 'hindi', 'urdu', 'bengali', 'tamil', 'telugu', 'marathi', 'gujarati', 'kannada', 'malayalam', 'punjabi', 'odia', 'assamese', 'maithili', 'sanskrit', 'kashmiri', 'konkani', 'manipuri', 'nepali', 'sindhi', 'dogri', 'bodo', 'santali', 'khasi', 'mizo', 'garo', 'naga', 'tibetan', 'dzongkha', 'sikkimese', 'lepcha', 'limbu', 'tamang', 'gurung', 'magar', 'tharu', 'rajbanshi', 'bhojpuri', 'awadhi', 'chhattisgarhi', 'haryanvi', 'braj', 'bundeli', 'bagheli', 'chhattisgarhi', 'garhwali', 'kumaoni', 'jaunsari', 'kangri', 'mandeali', 'himachali', 'pahari', 'dogri', 'kashmiri', 'ladakhi', 'balti', 'shina', 'burushaski', 'khowar', 'wakhi', 'domaki', 'kohistani', 'torwali', 'palula', 'sawi', 'gawar-bati', 'kalasha', 'dameli', 'phalura', 'savi', 'kundal shahi', 'bateri', 'chilisso', 'gawro', 'kalkoti', 'kashmiri', 'kohistani', 'palula', 'sawi', 'shina', 'torwali', 'wakhi', 'yidgha', 'zangskari', 'balti', 'burushaski', 'domaki', 'khowar', 'shina', 'wakhi', 'domaki', 'kohistani', 'torwali', 'palula', 'sawi', 'gawar-bati', 'kalasha', 'dameli', 'phalura', 'savi', 'kundal shahi', 'bateri', 'chilisso', 'gawro', 'kalkoti', 'kashmiri', 'kohistani', 'palula', 'sawi', 'shina', 'torwali', 'wakhi', 'yidgha', 'zangskari',
41
+ 'african', 'indian', 'indic', 'asian', "indigenous",
42
+ ];
43
+
44
+ // Initialize the application
45
+ document.addEventListener('DOMContentLoaded', function() {
46
+ initializeApp();
47
+ });
48
+
49
+ async function initializeApp() {
50
+ try {
51
+ await Promise.all([
52
+ loadBibTeXFile(),
53
+ loadConferencesData()
54
+ ]);
55
+ setupEventListeners();
56
+ populateConferenceSelect();
57
+ updateAbstractFilterInfo();
58
+ updateAuthorFilterInfo();
59
+ updateStarredFilterInfo();
60
+ updateSearchKeywordsDisplay();
61
+ updateRemoveKeywordsDisplay();
62
+ updateStarredCount(); // Initialize starred count
63
+ console.log('Initial filter states:', {
64
+ includeNoAbstract,
65
+ abstractFilterActive,
66
+ includeNoAuthor,
67
+ authorFilterActive
68
+ });
69
+ filterPapers();
70
+ } catch (error) {
71
+ console.error('Error initializing app:', error);
72
+ showError('Failed to load papers. Please refresh the page.');
73
+ }
74
+ }
75
+
76
+ async function loadBibTeXFile() {
77
+ const loadingIndicator = document.getElementById('loadingIndicator');
78
+ const papersList = document.getElementById('papersList');
79
+
80
+ try {
81
+ loadingIndicator.style.display = 'block';
82
+ papersList.style.display = 'none';
83
+
84
+ // Try to load the multilingual papers file first
85
+ let response = await fetch('data/multilingual_papers.bib');
86
+ // let response = await fetch('https://github.com/crystina-z/multilingual-paperbase/raw/refs/heads/master/data/multilingual_papers.bib');
87
+
88
+ if (response.ok) {
89
+ // Load from pre-filtered multilingual papers file
90
+ const bibText = await response.text();
91
+ allPapers = parseBibTeX(bibText);
92
+ console.log(`Loaded ${allPapers.length} multilingual papers from multilingual_papers.bib`);
93
+ } else {
94
+ // If multilingual file doesn't exist, report error and ask user to run python script
95
+ console.error('Multilingual papers file not found. Please run the python script to generate the file.');
96
+ throw new Error('Multilingual papers file not found. Please run the python script to generate the file.');
97
+ }
98
+
99
+ filteredPapers = [...allPapers];
100
+ updateResultCount();
101
+
102
+ loadingIndicator.style.display = 'none';
103
+ papersList.style.display = 'grid';
104
+
105
+ } catch (error) {
106
+ console.error('Error loading BibTeX file:', error);
107
+ loadingIndicator.style.display = 'none';
108
+ showError('Failed to load the BibTeX file. Please check if the file exists.');
109
+ }
110
+ }
111
+
112
+ async function loadConferencesData() {
113
+ try {
114
+ const response = await fetch('data/unique_conferences.json');
115
+ // const response = await fetch('https://github.com/crystina-z/multilingual-paperbase/raw/refs/heads/master/data/unique_conferences.json');
116
+ // const response = await fetch('https://github.com/crystina-z/multilingual-paperbase/data/unique_conferences.json');
117
+ if (!response.ok) {
118
+ throw new Error(`HTTP error! status: ${response.status}`);
119
+ }
120
+ conferencesData = await response.json();
121
+ console.log('Available conference keys:', Object.keys(conferencesData));
122
+ } catch (error) {
123
+ console.error('Error loading conferences data:', error);
124
+ conferencesData = {};
125
+ }
126
+ }
127
+
128
+ function populateConferenceSelect() {
129
+ const conferenceSelect = document.getElementById('conferenceSelect');
130
+ if (!conferenceSelect || !conferencesData) return;
131
+
132
+ // Clear existing options
133
+ conferenceSelect.innerHTML = '';
134
+
135
+ // Sort conferences by count (descending), but put "Others (820 Venues)" at the bottom
136
+ const sortedConferences = Object.entries(conferencesData)
137
+ .sort((a, b) => {
138
+ // Put "Others (820 Venues)" at the bottom
139
+ if (a[0].includes('Others')) return 1;
140
+ if (b[0].includes('Others')) return -1;
141
+ // Sort by count descending
142
+ return b[1].count - a[1].count;
143
+ });
144
+
145
+ // Add options to select
146
+ sortedConferences.forEach(([conferenceName, conferenceData]) => {
147
+ const option = document.createElement('option');
148
+ option.value = conferenceName;
149
+ option.textContent = `${conferenceName} (${conferenceData.count})`;
150
+ conferenceSelect.appendChild(option);
151
+ });
152
+
153
+ // Initialize the display
154
+ updateConferenceCountDisplay();
155
+ updateSelectedConferencesDisplay();
156
+ }
157
+
158
+ function parseBibTeX(bibText) {
159
+ const papers = [];
160
+ const entries = bibText.split(/(?=@)/);
161
+
162
+ for (const entry of entries) {
163
+ if (!entry.trim()) continue;
164
+
165
+ try {
166
+ const paper = parseBibTeXEntry(entry);
167
+ if (paper && (paper.title || paper.abstract)) {
168
+ papers.push(paper);
169
+ }
170
+ } catch (error) {
171
+ console.warn('Error parsing entry:', error);
172
+ }
173
+ }
174
+
175
+ return papers;
176
+ }
177
+
178
+ function parseBibTeXEntry(entry) {
179
+ const paper = {};
180
+
181
+ // Extract entry type and key
182
+ const typeMatch = entry.match(/@(\w+)\{([^,]+)/);
183
+ if (typeMatch) {
184
+ paper.type = typeMatch[1];
185
+ paper.key = typeMatch[2];
186
+ }
187
+
188
+ // Extract fields
189
+ const fields = ['title', 'author', 'abstract', 'year', 'booktitle', 'journal', 'pages'];
190
+
191
+ for (const field of fields) {
192
+ const regex = new RegExp(`${field}\\s*=\\s*\\{([^}]*)\\}|${field}\\s*=\\s*"([^"]*)"`, 'gi');
193
+ const match = entry.match(regex);
194
+ if (match) {
195
+ let value = match[0].replace(new RegExp(`${field}\\s*=\\s*[{"']?|["}']?$`, 'gi'), '');
196
+ // Clean up LaTeX commands
197
+ value = cleanLatexCommands(value);
198
+ paper[field] = value.trim();
199
+ }
200
+ }
201
+
202
+ // Extract conference information
203
+ if (paper.booktitle) {
204
+ paper.conference = paper.booktitle;
205
+ } else if (paper.journal) {
206
+ paper.conference = paper.journal;
207
+ }
208
+
209
+ // Extract year from key if not found in fields
210
+ if (!paper.year && paper.key) {
211
+ const yearMatch = paper.key.match(/\d{4}/);
212
+ if (yearMatch) {
213
+ paper.year = yearMatch[0];
214
+ }
215
+ }
216
+
217
+ // Determine if paper is multilingual
218
+ paper.isMultilingual = isMultilingualPaper(paper);
219
+ paper.keywords = extractKeywords(paper);
220
+
221
+ return paper;
222
+ }
223
+
224
+ function cleanLatexCommands(text) {
225
+ if (!text) return '';
226
+
227
+ return text
228
+ .replace(/\\[a-zA-Z]+\{([^}]*)\}/g, '$1') // Remove LaTeX commands with braces
229
+ .replace(/\\[a-zA-Z]+/g, '') // Remove simple LaTeX commands
230
+ .replace(/\{\\?([^}]*)\}/g, '$1') // Remove braces
231
+ .replace(/\\"/g, '"') // Replace escaped quotes
232
+ .replace(/\\'/g, "'") // Replace escaped apostrophes
233
+ .replace(/\\&/g, '&') // Replace escaped ampersands
234
+ .replace(/\\%/g, '%') // Replace escaped percent signs
235
+ .replace(/\\_/g, '_') // Replace escaped underscores
236
+ .replace(/\\\$/g, '$') // Replace escaped dollar signs
237
+ .replace(/\s+/g, ' ') // Normalize whitespace
238
+ .trim();
239
+ }
240
+
241
+ function isMultilingualPaper(paper) {
242
+ const text = `${paper.title || ''} ${paper.abstract || ''}`.toLowerCase();
243
+
244
+ // Check for multilingual keywords
245
+ for (const keyword of multilingualKeywords) {
246
+ if (text.includes(keyword.toLowerCase())) {
247
+ return true;
248
+ }
249
+ }
250
+
251
+ // Check for language names
252
+ for (const language of languageNames) {
253
+ if (text.includes(language.toLowerCase())) {
254
+ return true;
255
+ }
256
+ }
257
+
258
+ return false;
259
+ }
260
+
261
+ function extractKeywords(paper) {
262
+ const keywords = [];
263
+ const text = `${paper.title || ''} ${paper.abstract || ''}`.toLowerCase();
264
+
265
+ // Extract multilingual keywords
266
+ for (const keyword of multilingualKeywords) {
267
+ if (text.includes(keyword.toLowerCase())) {
268
+ keywords.push(keyword);
269
+ }
270
+ }
271
+
272
+ // Extract language names
273
+ for (const language of languageNames) {
274
+ if (text.includes(language.toLowerCase())) {
275
+ keywords.push(language);
276
+ }
277
+ }
278
+
279
+ // Additional keyword extraction based on common patterns
280
+ const additionalPatterns = [
281
+ { pattern: /(\d+)\s*languages?/gi, keyword: 'multiple languages' },
282
+ { pattern: /(\d+)\s*lingual/gi, keyword: 'multiple lingual' },
283
+ { pattern: /translation\s+model/gi, keyword: 'translation model' },
284
+ { pattern: /cross\s*lingual/gi, keyword: 'cross-lingual' },
285
+ { pattern: /multi\s*lingual/gi, keyword: 'multilingual' },
286
+ { pattern: /zero\s*shot/gi, keyword: 'zero-shot' },
287
+ { pattern: /few\s*shot/gi, keyword: 'few-shot' },
288
+ { pattern: /code\s*switch/gi, keyword: 'code-switching' },
289
+ { pattern: /language\s+transfer/gi, keyword: 'language transfer' },
290
+ { pattern: /language\s+adaptation/gi, keyword: 'language adaptation' },
291
+ { pattern: /language\s+identification/gi, keyword: 'language identification' },
292
+ { pattern: /language\s+detection/gi, keyword: 'language detection' }
293
+ ];
294
+
295
+ for (const { pattern, keyword } of additionalPatterns) {
296
+ if (pattern.test(text)) {
297
+ keywords.push(keyword);
298
+ }
299
+ }
300
+
301
+ // Remove duplicates and sort
302
+ return [...new Set(keywords)].sort();
303
+ }
304
+
305
+ function updateAuthorFilterInfo() {
306
+ const authorFilterInfo = document.getElementById('authorFilterInfo');
307
+ if (authorFilterInfo) {
308
+ if (authorFilterActive) {
309
+ authorFilterInfo.textContent = '(Showing only papers with author information)';
310
+ } else {
311
+ authorFilterInfo.textContent = '(Showing all papers)';
312
+ }
313
+ }
314
+ }
315
+
316
+ function updateStarredFilterInfo() {
317
+ const starredFilterInfo = document.getElementById('starredFilterInfo');
318
+ if (starredFilterInfo) {
319
+ if (starredFilterActive) {
320
+ starredFilterInfo.textContent = `(Showing only ${starredPapers.length} starred papers)`;
321
+ } else {
322
+ starredFilterInfo.textContent = '(Showing all papers)';
323
+ }
324
+ }
325
+ }
326
+
327
+ function setupEventListeners() {
328
+ // Search input
329
+ const searchInput = document.getElementById('searchInput');
330
+ const addSearchKeyword = document.getElementById('addSearchKeyword');
331
+ const clearSearch = document.getElementById('clearSearch');
332
+
333
+ searchInput.addEventListener('keypress', (e) => {
334
+ if (e.key === 'Enter') {
335
+ addSearchKeywordFromInput();
336
+ }
337
+ });
338
+ addSearchKeyword.addEventListener('click', addSearchKeywordFromInput);
339
+ clearSearch.addEventListener('click', clearAllSearchKeywords);
340
+
341
+ // Remove search input
342
+ const removeSearchInput = document.getElementById('removeSearchInput');
343
+ const addRemoveKeyword = document.getElementById('addRemoveKeyword');
344
+ const clearRemoveSearch = document.getElementById('clearRemoveSearch');
345
+
346
+ removeSearchInput.addEventListener('keypress', (e) => {
347
+ if (e.key === 'Enter') {
348
+ addRemoveKeywordFromInput();
349
+ }
350
+ });
351
+ addRemoveKeyword.addEventListener('click', addRemoveKeywordFromInput);
352
+ clearRemoveSearch.addEventListener('click', clearAllRemoveKeywords);
353
+
354
+ // Year filter
355
+ const yearInputMin = document.getElementById('yearInputMin');
356
+ const yearInputMax = document.getElementById('yearInputMax');
357
+ const yearRange = document.getElementById('yearRange');
358
+ const clearYearFilter = document.getElementById('clearYearFilter');
359
+
360
+ yearInputMin.addEventListener('input', debounce((e) => {
361
+ const value = parseInt(e.target.value) || 1960;
362
+ const maxValue = parseInt(yearInputMax.value) || 2025;
363
+
364
+ if (value >= 1960 && value <= 2025) {
365
+ if (value <= maxValue) {
366
+ selectedYearMin = value;
367
+ yearFilterActive = true;
368
+ updateYearRangeDisplay();
369
+ filterPapers();
370
+ } else {
371
+ // If min is greater than max, set max to min
372
+ selectedYearMin = value;
373
+ selectedYearMax = value;
374
+ yearInputMax.value = value;
375
+ yearFilterActive = true;
376
+ updateYearRangeDisplay();
377
+ filterPapers();
378
+ }
379
+ }
380
+ }, 300));
381
+
382
+ yearInputMax.addEventListener('input', debounce((e) => {
383
+ const value = parseInt(e.target.value) || 2025;
384
+ const minValue = parseInt(yearInputMin.value) || 1960;
385
+
386
+ if (value >= 1960 && value <= 2025) {
387
+ if (value >= minValue) {
388
+ selectedYearMax = value;
389
+ yearFilterActive = true;
390
+ updateYearRangeDisplay();
391
+ filterPapers();
392
+ } else {
393
+ // If max is less than min, set min to max
394
+ selectedYearMax = value;
395
+ selectedYearMin = value;
396
+ yearInputMin.value = value;
397
+ yearFilterActive = true;
398
+ updateYearRangeDisplay();
399
+ filterPapers();
400
+ }
401
+ }
402
+ }, 300));
403
+
404
+ clearYearFilter.addEventListener('click', () => {
405
+ yearInputMin.value = 1960;
406
+ yearInputMax.value = 2025;
407
+ selectedYearMin = 1960;
408
+ selectedYearMax = 2025;
409
+ yearFilterActive = false;
410
+ updateYearRangeDisplay();
411
+ filterPapers();
412
+ });
413
+
414
+ // Conference filter
415
+ const conferenceSelect = document.getElementById('conferenceSelect');
416
+ const clearConferenceFilter = document.getElementById('clearConferenceFilter');
417
+
418
+ conferenceSelect.addEventListener('change', () => {
419
+ const selectedOptions = Array.from(conferenceSelect.selectedOptions).map(option => option.value);
420
+ selectedConferences = selectedOptions;
421
+ conferenceFilterActive = selectedConferences.length > 0;
422
+ console.log('Selected conferences:', selectedConferences);
423
+ updateConferenceCountDisplay();
424
+ updateSelectedConferencesDisplay();
425
+ filterPapers();
426
+ });
427
+
428
+ clearConferenceFilter.addEventListener('click', () => {
429
+ conferenceSelect.selectedIndex = -1;
430
+ selectedConferences = [];
431
+ conferenceFilterActive = false;
432
+ updateConferenceCountDisplay();
433
+ updateSelectedConferencesDisplay();
434
+ filterPapers();
435
+ });
436
+
437
+ // Abstract filter
438
+ const includeNoAbstractCheckbox = document.getElementById('includeNoAbstract');
439
+
440
+ if (includeNoAbstractCheckbox) {
441
+ includeNoAbstractCheckbox.addEventListener('change', () => {
442
+ includeNoAbstract = includeNoAbstractCheckbox.checked;
443
+ abstractFilterActive = !includeNoAbstract; // When unchecked, we filter out papers without abstracts
444
+ console.log('Abstract filter changed:', { includeNoAbstract, abstractFilterActive });
445
+ updateAbstractFilterInfo();
446
+ filterPapers();
447
+ });
448
+ console.log('Abstract filter checkbox found and event listener added');
449
+ } else {
450
+ console.error('Abstract filter checkbox not found!');
451
+ }
452
+
453
+ // Author filter
454
+ const includeNoAuthorCheckbox = document.getElementById('includeNoAuthor');
455
+
456
+ if (includeNoAuthorCheckbox) {
457
+ includeNoAuthorCheckbox.addEventListener('change', () => {
458
+ includeNoAuthor = includeNoAuthorCheckbox.checked;
459
+ authorFilterActive = !includeNoAuthor; // When unchecked, we filter out papers without author info
460
+ console.log('Author filter changed:', { includeNoAuthor, authorFilterActive });
461
+ updateAuthorFilterInfo();
462
+ filterPapers();
463
+ });
464
+ console.log('Author filter checkbox found and event listener added');
465
+ } else {
466
+ console.error('Author filter checkbox not found!');
467
+ }
468
+
469
+ // Starred filter
470
+ const showOnlyStarredCheckbox = document.getElementById('showOnlyStarred');
471
+
472
+ if (showOnlyStarredCheckbox) {
473
+ showOnlyStarredCheckbox.addEventListener('change', () => {
474
+ showOnlyStarred = showOnlyStarredCheckbox.checked;
475
+ starredFilterActive = showOnlyStarred; // When checked, we filter to show only starred papers
476
+ console.log('Starred filter changed:', { showOnlyStarred, starredFilterActive });
477
+ updateStarredFilterInfo();
478
+ filterPapers();
479
+ });
480
+ console.log('Starred filter checkbox found and event listener added');
481
+ } else {
482
+ console.error('Starred filter checkbox not found!');
483
+ }
484
+
485
+ // Pagination controls
486
+ const prevPage = document.getElementById('prevPage');
487
+ const nextPage = document.getElementById('nextPage');
488
+
489
+ prevPage.addEventListener('click', () => {
490
+ if (currentPage > 1) {
491
+ currentPage--;
492
+ displayPapers();
493
+ }
494
+ });
495
+
496
+ nextPage.addEventListener('click', () => {
497
+ const totalPages = Math.ceil(filteredPapers.length / papersPerPage);
498
+ if (currentPage < totalPages) {
499
+ currentPage++;
500
+ displayPapers();
501
+ }
502
+ });
503
+
504
+ // Page input functionality
505
+ const pageInput = document.getElementById('pageInput');
506
+ const goToPageBtn = document.getElementById('goToPage');
507
+
508
+ pageInput.addEventListener('keypress', (e) => {
509
+ if (e.key === 'Enter') {
510
+ goToPage();
511
+ }
512
+ });
513
+
514
+ goToPageBtn.addEventListener('click', goToPage);
515
+
516
+ // Modal
517
+ const modal = document.getElementById('paperModal');
518
+
519
+ window.addEventListener('click', (event) => {
520
+ if (event.target === modal) {
521
+ modal.style.display = 'none';
522
+ }
523
+ });
524
+
525
+ // Export and save/load buttons
526
+ const saveStatus = document.getElementById('saveStatus');
527
+ const loadStatus = document.getElementById('loadStatus');
528
+ const exportBibTeX = document.getElementById('exportBibTeX');
529
+ const exportStarred = document.getElementById('exportStarred');
530
+
531
+ if (saveStatus) {
532
+ saveStatus.addEventListener('click', saveCurrentStatus);
533
+ }
534
+
535
+ if (loadStatus) {
536
+ loadStatus.addEventListener('click', loadStatusFromFile);
537
+ }
538
+
539
+ if (exportBibTeX) {
540
+ exportBibTeX.addEventListener('click', exportFilteredResults);
541
+ }
542
+
543
+ if (exportStarred) {
544
+ exportStarred.addEventListener('click', exportStarredPapers);
545
+ }
546
+ }
547
+
548
+ function addSearchKeywordFromInput() {
549
+ const searchInput = document.getElementById('searchInput');
550
+ const keyword = searchInput.value.trim();
551
+
552
+ if (keyword && !searchKeywords.includes(keyword.toLowerCase())) {
553
+ searchKeywords.push(keyword.toLowerCase());
554
+ searchInput.value = '';
555
+ updateSearchKeywordsDisplay();
556
+ filterPapers();
557
+ }
558
+ }
559
+
560
+ function removeSearchKeyword(keyword) {
561
+ searchKeywords = searchKeywords.filter(k => k !== keyword);
562
+ updateSearchKeywordsDisplay();
563
+ filterPapers();
564
+ }
565
+
566
+ function clearAllSearchKeywords() {
567
+ searchKeywords = [];
568
+ updateSearchKeywordsDisplay();
569
+ filterPapers();
570
+ }
571
+
572
+ function updateSearchKeywordsDisplay() {
573
+ const container = document.getElementById('searchKeywordsContainer');
574
+ const clearSearch = document.getElementById('clearSearch');
575
+
576
+ container.innerHTML = '';
577
+
578
+ searchKeywords.forEach(keyword => {
579
+ const tag = document.createElement('div');
580
+ tag.className = 'search-keyword-tag';
581
+ tag.innerHTML = `
582
+ <span>${keyword}</span>
583
+ <button class="remove-keyword" onclick="removeSearchKeyword('${keyword}')" title="Remove keyword">
584
+ <i class="fas fa-times"></i>
585
+ </button>
586
+ `;
587
+ container.appendChild(tag);
588
+ });
589
+
590
+ if (searchKeywords.length > 0) {
591
+ clearSearch.classList.add('visible');
592
+ } else {
593
+ clearSearch.classList.remove('visible');
594
+ }
595
+ }
596
+
597
+ function addRemoveKeywordFromInput() {
598
+ const removeSearchInput = document.getElementById('removeSearchInput');
599
+ const keyword = removeSearchInput.value.trim();
600
+
601
+ if (keyword && !removeSearchKeywords.includes(keyword.toLowerCase())) {
602
+ removeSearchKeywords.push(keyword.toLowerCase());
603
+ removeSearchInput.value = '';
604
+ updateRemoveKeywordsDisplay();
605
+ filterPapers();
606
+ }
607
+ }
608
+
609
+ function removeKeyword(keyword) {
610
+ removeSearchKeywords = removeSearchKeywords.filter(k => k !== keyword);
611
+ updateRemoveKeywordsDisplay();
612
+ filterPapers();
613
+ }
614
+
615
+ function clearAllRemoveKeywords() {
616
+ removeSearchKeywords = [];
617
+ updateRemoveKeywordsDisplay();
618
+ filterPapers();
619
+ }
620
+
621
+ function updateRemoveKeywordsDisplay() {
622
+ const container = document.getElementById('removeKeywordsContainer');
623
+ const clearRemoveSearch = document.getElementById('clearRemoveSearch');
624
+
625
+ container.innerHTML = '';
626
+
627
+ removeSearchKeywords.forEach(keyword => {
628
+ const tag = document.createElement('div');
629
+ tag.className = 'remove-keyword-tag';
630
+ tag.innerHTML = `
631
+ <span>${keyword}</span>
632
+ <button class="remove-keyword" onclick="removeKeyword('${keyword}')" title="Remove keyword">
633
+ <i class="fas fa-times"></i>
634
+ </button>
635
+ `;
636
+ container.appendChild(tag);
637
+ });
638
+
639
+ if (removeSearchKeywords.length > 0) {
640
+ clearRemoveSearch.classList.add('visible');
641
+ } else {
642
+ clearRemoveSearch.classList.remove('visible');
643
+ }
644
+ }
645
+
646
+ function updatePaginationInfo(start, end, total) {
647
+ const paginationInfo = document.getElementById('paginationInfo');
648
+ if (total === 0) {
649
+ paginationInfo.textContent = 'No papers found';
650
+ } else {
651
+ paginationInfo.textContent = `Showing ${start}-${end} of ${total} papers`;
652
+ }
653
+ }
654
+
655
+ function updatePaginationControls(totalPages) {
656
+ const prevPage = document.getElementById('prevPage');
657
+ const nextPage = document.getElementById('nextPage');
658
+ const pageNumbers = document.getElementById('pageNumbers');
659
+
660
+ // Update previous/next buttons
661
+ prevPage.disabled = currentPage <= 1;
662
+ nextPage.disabled = currentPage >= totalPages;
663
+
664
+ // Generate page numbers
665
+ pageNumbers.innerHTML = '';
666
+
667
+ if (totalPages <= 1) {
668
+ updatePageInput();
669
+ return;
670
+ }
671
+
672
+ const maxVisiblePages = 5;
673
+ let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
674
+ let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
675
+
676
+ // Adjust start page if we're near the end
677
+ if (endPage - startPage + 1 < maxVisiblePages) {
678
+ startPage = Math.max(1, endPage - maxVisiblePages + 1);
679
+ }
680
+
681
+ // Add first page and ellipsis if needed
682
+ if (startPage > 1) {
683
+ addPageNumber(1);
684
+ if (startPage > 2) {
685
+ addEllipsis();
686
+ }
687
+ }
688
+
689
+ // Add visible page numbers
690
+ for (let i = startPage; i <= endPage; i++) {
691
+ addPageNumber(i);
692
+ }
693
+
694
+ // Add last page and ellipsis if needed
695
+ if (endPage < totalPages) {
696
+ if (endPage < totalPages - 1) {
697
+ addEllipsis();
698
+ }
699
+ addPageNumber(totalPages);
700
+ }
701
+
702
+ updatePageInput();
703
+ }
704
+
705
+ function addEllipsis() {
706
+ const pageNumbers = document.getElementById('pageNumbers');
707
+ const ellipsis = document.createElement('span');
708
+ ellipsis.className = 'page-ellipsis';
709
+ ellipsis.textContent = '...';
710
+ pageNumbers.appendChild(ellipsis);
711
+ }
712
+
713
+ function updatePageInput() {
714
+ const pageInput = document.getElementById('pageInput');
715
+ const goToPageBtn = document.getElementById('goToPage');
716
+ const totalPages = Math.ceil(filteredPapers.length / papersPerPage);
717
+
718
+ if (pageInput && goToPageBtn) {
719
+ pageInput.max = totalPages;
720
+ pageInput.value = currentPage;
721
+ goToPageBtn.disabled = currentPage < 1 || currentPage > totalPages;
722
+ }
723
+ }
724
+
725
+ function goToPage() {
726
+ const pageInput = document.getElementById('pageInput');
727
+ const totalPages = Math.ceil(filteredPapers.length / papersPerPage);
728
+ const inputValue = parseInt(pageInput.value);
729
+
730
+ if (inputValue && inputValue >= 1 && inputValue <= totalPages) {
731
+ currentPage = inputValue;
732
+ displayPapers();
733
+ } else {
734
+ // Reset input to current page if invalid
735
+ pageInput.value = currentPage;
736
+ showNotification('Please enter a valid page number between 1 and ' + totalPages, 'warning');
737
+ }
738
+ }
739
+
740
+ function addPageNumber(pageNum) {
741
+ const pageNumbers = document.getElementById('pageNumbers');
742
+ const pageBtn = document.createElement('button');
743
+ pageBtn.className = `page-number ${pageNum === currentPage ? 'active' : ''}`;
744
+ pageBtn.textContent = pageNum;
745
+ pageBtn.addEventListener('click', () => {
746
+ currentPage = pageNum;
747
+ displayPapers();
748
+ });
749
+ pageNumbers.appendChild(pageBtn);
750
+ }
751
+
752
+ function filterPapers() {
753
+ let filtered = [...allPapers];
754
+ console.log('Starting filterPapers with:', {
755
+ totalPapers: allPapers.length,
756
+ abstractFilterActive,
757
+ includeNoAbstract,
758
+ authorFilterActive,
759
+ includeNoAuthor
760
+ });
761
+
762
+ // Apply search filter
763
+ if (searchKeywords.length > 0) {
764
+ filtered = filtered.filter(paper => {
765
+ // Check if paper contains any of the search keywords in any field
766
+ const title = (paper.title || '').toLowerCase();
767
+ const author = (paper.author || '').toLowerCase();
768
+ const abstract = (paper.abstract || '').toLowerCase();
769
+
770
+ return searchKeywords.some(keyword =>
771
+ title.includes(keyword) || author.includes(keyword) || abstract.includes(keyword)
772
+ );
773
+ });
774
+ }
775
+
776
+ // Apply remove search filter (remove papers containing the specified keywords)
777
+ if (removeSearchKeywords.length > 0) {
778
+ const beforeCount = filtered.length;
779
+ filtered = filtered.filter(paper => {
780
+ // Check if paper contains any of the removal keywords in title or abstract only
781
+ const title = (paper.title || '').toLowerCase();
782
+ const abstract = (paper.abstract || '').toLowerCase();
783
+
784
+ return !removeSearchKeywords.some(keyword =>
785
+ title.includes(keyword) || abstract.includes(keyword)
786
+ );
787
+ });
788
+ const removedCount = beforeCount - filtered.length;
789
+ console.log(`Remove search filter: ${beforeCount} -> ${filtered.length} papers (removed ${removedCount} papers with keywords: ${removeSearchKeywords.join(', ')})`);
790
+ }
791
+
792
+ // Apply year filter
793
+ if (yearFilterActive) {
794
+ filtered = filtered.filter(paper => {
795
+ const paperYear = parseInt(paper.year) || 0;
796
+ return paperYear >= selectedYearMin && paperYear <= selectedYearMax;
797
+ });
798
+ }
799
+
800
+ // Apply conference filter
801
+ if (conferenceFilterActive) {
802
+ console.log('Applying conference filter for:', selectedConferences);
803
+ const beforeCount = filtered.length;
804
+ filtered = filtered.filter(paper => {
805
+ const paperConference = paper.conference || '';
806
+ const matches = selectedConferences.some(selectedConf => {
807
+ // Check if the paper's conference name contains the selected conference name
808
+ // This handles cases where the conference name might be a substring
809
+ const paperConfLower = paperConference.toLowerCase();
810
+ const selectedConfLower = selectedConf.toLowerCase();
811
+
812
+ // Check if the selected conference is a key in conferencesData
813
+ // and if the paper's conference is in the conferences list
814
+ if (conferencesData[selectedConf] && conferencesData[selectedConf].conferences) {
815
+ const confMatch = conferencesData[selectedConf].conferences.some(confName => {
816
+ const confNameLower = confName.toLowerCase();
817
+ const matches = (paperConfLower == confNameLower);
818
+ if (matches) {
819
+ console.log('Conference list match found for:', selectedConf, 'with conference name:', confName);
820
+ }
821
+ return matches;
822
+ });
823
+ return confMatch;
824
+ }
825
+
826
+ return false;
827
+ });
828
+ return matches;
829
+ });
830
+ console.log(`Conference filter: ${beforeCount} -> ${filtered.length} papers`);
831
+ }
832
+
833
+ // Apply abstract filter
834
+ if (abstractFilterActive) {
835
+ const beforeCount = filtered.length;
836
+ const papersWithAbstract = filtered.filter(paper => {
837
+ const hasAbstract = paper.abstract && paper.abstract.trim().length > 0;
838
+ return hasAbstract;
839
+ }).length;
840
+ const papersWithoutAbstract = beforeCount - papersWithAbstract;
841
+
842
+ filtered = filtered.filter(paper => {
843
+ const hasAbstract = paper.abstract && paper.abstract.trim().length > 0;
844
+ return hasAbstract;
845
+ });
846
+ console.log(`Abstract filter: ${beforeCount} -> ${filtered.length} papers (excluding ${papersWithoutAbstract} papers without abstracts)`);
847
+ } else {
848
+ const papersWithAbstract = filtered.filter(paper => {
849
+ const hasAbstract = paper.abstract && paper.abstract.trim().length > 0;
850
+ return hasAbstract;
851
+ }).length;
852
+ const papersWithoutAbstract = filtered.length - papersWithAbstract;
853
+ console.log(`Abstract filter inactive - showing all papers (${papersWithAbstract} with abstracts, ${papersWithoutAbstract} without abstracts)`);
854
+ }
855
+
856
+ // Apply author filter
857
+ if (authorFilterActive) {
858
+ const beforeCount = filtered.length;
859
+ const papersWithAuthor = filtered.filter(paper => {
860
+ const hasAuthor = paper.author && paper.author.trim().length > 0 && paper.author.toLowerCase() !== 'unknown authors';
861
+ return hasAuthor;
862
+ }).length;
863
+ const papersWithoutAuthor = beforeCount - papersWithAuthor;
864
+
865
+ filtered = filtered.filter(paper => {
866
+ const hasAuthor = paper.author && paper.author.trim().length > 0 && paper.author.toLowerCase() !== 'unknown authors';
867
+ return hasAuthor;
868
+ });
869
+ console.log(`Author filter: ${beforeCount} -> ${filtered.length} papers (excluding ${papersWithoutAuthor} papers without author info)`);
870
+ } else {
871
+ const papersWithAuthor = filtered.filter(paper => {
872
+ const hasAuthor = paper.author && paper.author.trim().length > 0 && paper.author.toLowerCase() !== 'unknown authors';
873
+ return hasAuthor;
874
+ }).length;
875
+ const papersWithoutAuthor = filtered.length - papersWithAuthor;
876
+ console.log(`Author filter inactive - showing all papers (${papersWithAuthor} with author info, ${papersWithoutAuthor} without author info)`);
877
+ }
878
+
879
+ // Apply starred filter
880
+ if (starredFilterActive) {
881
+ const beforeCount = filtered.length;
882
+ filtered = filtered.filter(paper => {
883
+ return starredPapers.includes(paper.key);
884
+ });
885
+ console.log(`Starred filter: ${beforeCount} -> ${filtered.length} papers (showing only starred papers)`);
886
+ } else {
887
+ console.log(`Starred filter inactive - showing all papers`);
888
+ }
889
+
890
+ // Sort papers: papers with abstracts and known conferences first, then papers with abstracts but unknown conferences, then papers without abstracts
891
+ filtered.sort((a, b) => {
892
+ const aHasAbstract = a.abstract && a.abstract.trim().length > 0;
893
+ const bHasAbstract = b.abstract && b.abstract.trim().length > 0;
894
+ // const aConferenceKey = getConferenceKey(a);
895
+ // const bConferenceKey = getConferenceKey(b);
896
+ // const aHasUnknownConference = aConferenceKey === 'Unknown';
897
+ // const bHasUnknownConference = bConferenceKey === 'Unknown';
898
+
899
+ if (aHasAbstract && !bHasAbstract) return -1;
900
+ if (!aHasAbstract && bHasAbstract) return 1;
901
+
902
+ // // First priority: papers with abstracts and known conferences
903
+ // if (aHasAbstract && !aHasUnknownConference && (!bHasAbstract || bHasUnknownConference)) return -1;
904
+ // if (bHasAbstract && !bHasUnknownConference && (!aHasAbstract || aHasUnknownConference)) return 1;
905
+
906
+ // // Second priority: papers with abstracts but unknown conferences
907
+ // if (aHasAbstract && aHasUnknownConference && (!bHasAbstract || (bHasAbstract && !bHasUnknownConference))) return -1;
908
+ // if (bHasAbstract && bHasUnknownConference && (!aHasAbstract || (aHasAbstract && !aHasUnknownConference))) return 1;
909
+
910
+ // // Third priority: papers without abstracts (unknown conferences come last)
911
+ // if (!aHasAbstract && !bHasAbstract) {
912
+ // if (aHasUnknownConference && !bHasUnknownConference) return 1;
913
+ // if (!aHasUnknownConference && bHasUnknownConference) return -1;
914
+ // }
915
+
916
+ return 0;
917
+ });
918
+
919
+ filteredPapers = filtered;
920
+ currentPage = 1; // Reset to first page when filtering
921
+ displayPapers();
922
+ }
923
+
924
+ function updateYearRangeDisplay() {
925
+ const yearRange = document.getElementById('yearRange');
926
+ if (yearFilterActive) {
927
+ if (selectedYearMin === 1960 && selectedYearMax === 2025) {
928
+ yearRange.textContent = 'All Years';
929
+ } else {
930
+ yearRange.textContent = `${selectedYearMin} - ${selectedYearMax}`;
931
+ }
932
+ } else {
933
+ yearRange.textContent = 'All Years';
934
+ }
935
+ }
936
+
937
+ function updateConferenceCountDisplay() {
938
+ const conferenceCount = document.getElementById('conferenceCount');
939
+ if (conferenceFilterActive) {
940
+ conferenceCount.textContent = `${selectedConferences.length} Conference${selectedConferences.length !== 1 ? 's' : ''} Selected`;
941
+ } else {
942
+ conferenceCount.textContent = 'All Conferences';
943
+ }
944
+ }
945
+
946
+ function updateAbstractFilterInfo() {
947
+ const abstractFilterInfo = document.getElementById('abstractFilterInfo');
948
+ if (abstractFilterActive) {
949
+ abstractFilterInfo.textContent = '(Showing only papers with abstracts)';
950
+ } else {
951
+ abstractFilterInfo.textContent = '(Showing all papers)';
952
+ }
953
+ }
954
+
955
+ function updateSelectedConferencesDisplay() {
956
+ const selectedConferencesDiv = document.getElementById('selectedConferences');
957
+ if (!selectedConferencesDiv) return;
958
+
959
+ selectedConferencesDiv.innerHTML = '';
960
+
961
+ selectedConferences.forEach(conference => {
962
+ const tag = document.createElement('div');
963
+ tag.className = 'conference-tag';
964
+ tag.innerHTML = `
965
+ ${conference}
966
+ <button class="remove-tag" onclick="removeConference('${conference}')">
967
+ <i class="fas fa-times"></i>
968
+ </button>
969
+ `;
970
+ selectedConferencesDiv.appendChild(tag);
971
+ });
972
+ }
973
+
974
+ function removeConference(conference) {
975
+ const conferenceSelect = document.getElementById('conferenceSelect');
976
+ const option = Array.from(conferenceSelect.options).find(opt => opt.value === conference);
977
+ if (option) {
978
+ option.selected = false;
979
+ }
980
+
981
+ selectedConferences = selectedConferences.filter(c => c !== conference);
982
+ conferenceFilterActive = selectedConferences.length > 0;
983
+ updateConferenceCountDisplay();
984
+ updateSelectedConferencesDisplay();
985
+ filterPapers();
986
+ }
987
+
988
+ function displayPapers() {
989
+ const papersList = document.getElementById('papersList');
990
+ const noResults = document.getElementById('noResults');
991
+
992
+ if (filteredPapers.length === 0) {
993
+ papersList.style.display = 'none';
994
+ noResults.style.display = 'block';
995
+ updatePaginationInfo(0, 0, 0);
996
+ updatePaginationControls(0);
997
+ } else {
998
+ papersList.style.display = 'grid';
999
+ noResults.style.display = 'none';
1000
+
1001
+ // Calculate pagination
1002
+ const totalPages = Math.ceil(filteredPapers.length / papersPerPage);
1003
+ const startIndex = (currentPage - 1) * papersPerPage;
1004
+ const endIndex = Math.min(startIndex + papersPerPage, filteredPapers.length);
1005
+ const currentPagePapers = filteredPapers.slice(startIndex, endIndex);
1006
+
1007
+ papersList.innerHTML = currentPagePapers.map(paper => createPaperCard(paper)).join('');
1008
+
1009
+ // Add click listeners to paper cards
1010
+ const paperCards = document.querySelectorAll('.paper-card');
1011
+ paperCards.forEach((card, index) => {
1012
+ card.addEventListener('click', () => {
1013
+ showPaperModal(currentPagePapers[index]);
1014
+ });
1015
+ });
1016
+
1017
+ updatePaginationInfo(startIndex + 1, endIndex, filteredPapers.length);
1018
+ updatePaginationControls(totalPages);
1019
+ updatePageInput();
1020
+ }
1021
+
1022
+ updateResultCount();
1023
+ }
1024
+
1025
+ function getConferenceKey(paper) {
1026
+ if (!paper.conference || !conferencesData) return 'Unknown';
1027
+
1028
+ const paperConference = paper.conference.toLowerCase();
1029
+ console.log('Looking for conference key for:', paper.conference);
1030
+
1031
+ // Find the conference key that matches this paper
1032
+ for (const [conferenceKey, conferenceData] of Object.entries(conferencesData)) {
1033
+ // Check if the paper's conference is in the conferences list
1034
+ if (conferenceData.conferences) {
1035
+ for (const confName of conferenceData.conferences) {
1036
+ // if (paperConference.includes(confName.toLowerCase())) {
1037
+ if (paperConference.toLowerCase() == confName.toLowerCase()) {
1038
+ console.log('Found match by conference name:', conferenceKey, 'for:', confName);
1039
+ return conferenceKey;
1040
+ }
1041
+ }
1042
+ }
1043
+ }
1044
+
1045
+ // If no specific match found, check if it should be in "Others (820 Venues)"
1046
+ // This handles cases where the paper's conference is not in any specific category
1047
+ if (conferencesData["Others (820 Venues)"]) {
1048
+ console.log('No specific match found, returning Others (820 Venues)');
1049
+ return "Others (820 Venues)";
1050
+ }
1051
+
1052
+ console.log('No match found, returning Unknown');
1053
+ return 'Unknown';
1054
+ }
1055
+
1056
+ function createPaperCard(paper) {
1057
+ const title = paper.title || 'Untitled';
1058
+ const authors = paper.author || 'Unknown authors';
1059
+ const abstract = paper.abstract || 'No abstract available';
1060
+ const year = paper.year || 'Unknown year';
1061
+ const conferenceKey = getConferenceKey(paper);
1062
+ const hasAbstract = paper.abstract && paper.abstract.trim().length > 0;
1063
+ const hasAuthor = paper.author && paper.author.trim().length > 0 && paper.author.toLowerCase() !== 'unknown authors';
1064
+ const isStarred = isPaperStarred(paper.key);
1065
+
1066
+ let cardClasses = 'paper-card';
1067
+ if (!hasAbstract) cardClasses += ' no-abstract';
1068
+ if (!hasAuthor) cardClasses += ' no-author';
1069
+
1070
+ return `
1071
+ <div class="${cardClasses}">
1072
+ <div class="paper-header">
1073
+ <h3 class="paper-title">${title}</h3>
1074
+ <button class="star-button ${isStarred ? 'starred' : ''}"
1075
+ data-paper-key="${paper.key}"
1076
+ onclick="toggleStar('${paper.key}', event)"
1077
+ title="${isStarred ? 'Remove from starred' : 'Add to starred'}">
1078
+ <i class="${isStarred ? 'fas' : 'far'} fa-star"></i>
1079
+ </button>
1080
+ </div>
1081
+ <p class="paper-authors">${authors}</p>
1082
+ <p class="paper-abstract">${abstract}</p>
1083
+ <div class="paper-meta">
1084
+ <span class="paper-year">${year}</span>
1085
+ <span class="paper-conference">${conferenceKey}</span>
1086
+ </div>
1087
+ </div>
1088
+ `;
1089
+ }
1090
+
1091
+ function showPaperModal(paper) {
1092
+ const modal = document.getElementById('paperModal');
1093
+ const modalTitle = document.getElementById('modalTitle');
1094
+ const modalAuthors = document.getElementById('modalAuthors');
1095
+ const modalAbstract = document.getElementById('modalAbstract');
1096
+ const modalYear = document.getElementById('modalYear');
1097
+ const modalConference = document.getElementById('modalConference');
1098
+ const modalKeywords = document.getElementById('modalKeywords');
1099
+ const modalStarButton = document.getElementById('modalStarButton');
1100
+ const modalFindPaperButton = document.getElementById('modalFindPaperButton');
1101
+
1102
+ modalTitle.textContent = paper.title || 'Untitled';
1103
+ modalAuthors.textContent = paper.author || 'Unknown authors';
1104
+ modalAbstract.textContent = paper.abstract || 'No abstract available';
1105
+ modalYear.textContent = paper.year || 'Unknown year';
1106
+
1107
+ // Get conference information using the same logic as in createPaperCard
1108
+ // const conferenceKey = getConferenceKey(paper);
1109
+
1110
+ if (!paper.conference || !conferencesData){
1111
+ modalConference.textContent = 'Unknown conference';
1112
+ } else {
1113
+ modalConference.textContent = paper.conference || 'Unknown conference'
1114
+ }
1115
+ // const conferenceKey = paper.conference || 'Unknown conference';
1116
+ // modalConference.textContent = paper.conference || 'Unknown conference';
1117
+
1118
+ modalKeywords.textContent = paper.keywords.length > 0 ? paper.keywords.join(', ') : 'No keywords';
1119
+
1120
+ // Update modal star button
1121
+ if (modalStarButton) {
1122
+ const isStarred = isPaperStarred(paper.key);
1123
+ modalStarButton.className = `star-button ${isStarred ? 'starred' : ''}`;
1124
+ modalStarButton.setAttribute('data-paper-key', paper.key);
1125
+ modalStarButton.setAttribute('title', isStarred ? 'Remove from starred' : 'Add to starred');
1126
+ modalStarButton.onclick = (event) => toggleStar(paper.key, event);
1127
+
1128
+ const icon = modalStarButton.querySelector('i');
1129
+ if (icon) {
1130
+ icon.className = `${isStarred ? 'fas' : 'far'} fa-star`;
1131
+ }
1132
+ }
1133
+
1134
+ // Set up Find Paper button
1135
+ if (modalFindPaperButton) {
1136
+ modalFindPaperButton.onclick = () => searchPaperOnGoogle(paper.title);
1137
+ }
1138
+
1139
+ modal.style.display = 'block';
1140
+ }
1141
+
1142
+ function searchPaperOnGoogle(paperTitle) {
1143
+ if (!paperTitle || paperTitle === 'Untitled') {
1144
+ return;
1145
+ }
1146
+
1147
+ // Encode the paper title for URL
1148
+ const encodedTitle = encodeURIComponent(paperTitle);
1149
+ const googleSearchUrl = `https://www.google.com/search?q=${encodedTitle}`;
1150
+
1151
+ // Open Google search in a new tab
1152
+ window.open(googleSearchUrl, '_blank');
1153
+ }
1154
+
1155
+ function generateBibTeXContent(papers) {
1156
+ let content = '';
1157
+
1158
+ for (const paper of papers) {
1159
+ // Reconstruct the original BibTeX entry
1160
+ content += `@${paper.type}{${paper.key},\n`;
1161
+
1162
+ if (paper.title) {
1163
+ content += ` title = {${paper.title}},\n`;
1164
+ }
1165
+ if (paper.author) {
1166
+ content += ` author = {${paper.author}},\n`;
1167
+ }
1168
+ if (paper.abstract) {
1169
+ content += ` abstract = {${paper.abstract}},\n`;
1170
+ }
1171
+ if (paper.year) {
1172
+ content += ` year = {${paper.year}},\n`;
1173
+ }
1174
+ if (paper.booktitle) {
1175
+ content += ` booktitle = {${paper.booktitle}},\n`;
1176
+ }
1177
+ if (paper.journal) {
1178
+ content += ` journal = {${paper.journal}},\n`;
1179
+ }
1180
+ if (paper.pages) {
1181
+ content += ` pages = {${paper.pages}},\n`;
1182
+ }
1183
+
1184
+ // Remove trailing comma and add closing brace
1185
+ content = content.replace(/,\n$/, '\n');
1186
+ content += '}\n\n';
1187
+ }
1188
+
1189
+ return content;
1190
+ }
1191
+
1192
+ function showNotification(message, type = 'success', duration = 10000) {
1193
+ // Create notification element
1194
+ const notification = document.createElement('div');
1195
+ notification.className = `notification notification-${type}`;
1196
+
1197
+ let icon = 'fa-info-circle';
1198
+ if (type === 'error') icon = 'fa-exclamation-triangle';
1199
+ else if (type === 'warning') icon = 'fa-exclamation-circle';
1200
+ else if (type === 'success') icon = 'fa-check-circle';
1201
+
1202
+ notification.innerHTML = `
1203
+ <div class="notification-content">
1204
+ <i class="fas ${icon}"></i>
1205
+ <span>${message}</span>
1206
+ <button class="notification-close" onclick="this.parentElement.parentElement.remove()">
1207
+ <i class="fas fa-times"></i>
1208
+ </button>
1209
+ </div>
1210
+ `;
1211
+
1212
+ // Add to page
1213
+ document.body.appendChild(notification);
1214
+
1215
+ // Auto-remove after duration
1216
+ setTimeout(() => {
1217
+ if (notification.parentElement) {
1218
+ notification.remove();
1219
+ }
1220
+ }, duration);
1221
+ }
1222
+
1223
+ function updateResultCount() {
1224
+ const resultCount = document.getElementById('resultCount');
1225
+ const removeResultCount = document.getElementById('removeResultCount');
1226
+ const total = allPapers.length;
1227
+ const filtered = filteredPapers.length;
1228
+
1229
+ if (searchKeywords.length === 0) {
1230
+ resultCount.textContent = `Total: ${total} papers`;
1231
+ } else {
1232
+ resultCount.textContent = `Found ${filtered} of ${total} papers (${searchKeywords.length} keyword${searchKeywords.length !== 1 ? 's' : ''})`;
1233
+ }
1234
+
1235
+ if (removeSearchKeywords.length === 0) {
1236
+ removeResultCount.textContent = 'All papers included';
1237
+ } else {
1238
+ // Calculate how many papers were removed by the remove search
1239
+ const papersAfterSearch = searchKeywords.length > 0 ?
1240
+ allPapers.filter(paper => {
1241
+ const title = (paper.title || '').toLowerCase();
1242
+ const author = (paper.author || '').toLowerCase();
1243
+ const abstract = (paper.abstract || '').toLowerCase();
1244
+ return searchKeywords.some(keyword =>
1245
+ title.includes(keyword) || author.includes(keyword) || abstract.includes(keyword)
1246
+ );
1247
+ }) :
1248
+ allPapers;
1249
+ const removedCount = papersAfterSearch.length - filtered;
1250
+ removeResultCount.textContent = `Removed ${removedCount} papers (${removeSearchKeywords.length} keyword${removeSearchKeywords.length !== 1 ? 's' : ''})`;
1251
+ }
1252
+ }
1253
+
1254
+ function showError(message) {
1255
+ const papersList = document.getElementById('papersList');
1256
+ const loadingIndicator = document.getElementById('loadingIndicator');
1257
+
1258
+ loadingIndicator.style.display = 'none';
1259
+ papersList.style.display = 'none';
1260
+
1261
+ papersList.innerHTML = `
1262
+ <div class="no-results">
1263
+ <i class="fas fa-exclamation-triangle"></i>
1264
+ <h3>Error</h3>
1265
+ <p>${message}</p>
1266
+ </div>
1267
+ `;
1268
+ papersList.style.display = 'block';
1269
+ }
1270
+
1271
+ function matchesSearchField(paper, query, field) {
1272
+ const title = (paper.title || '').toLowerCase();
1273
+ const author = (paper.author || '').toLowerCase();
1274
+ const abstract = (paper.abstract || '').toLowerCase();
1275
+
1276
+ switch (field) {
1277
+ case 'title':
1278
+ return title.includes(query);
1279
+ case 'author':
1280
+ return author.includes(query);
1281
+ case 'abstract':
1282
+ return abstract.includes(query);
1283
+ case 'title+author':
1284
+ return title.includes(query) || author.includes(query);
1285
+ case 'title+abstract':
1286
+ return title.includes(query) || abstract.includes(query);
1287
+ case 'author+abstract':
1288
+ return author.includes(query) || abstract.includes(query);
1289
+ case 'all':
1290
+ default:
1291
+ return title.includes(query) || author.includes(query) || abstract.includes(query);
1292
+ }
1293
+ }
1294
+
1295
+ // Utility function for debouncing
1296
+ function debounce(func, wait) {
1297
+ let timeout;
1298
+ return function executedFunction(...args) {
1299
+ const later = () => {
1300
+ clearTimeout(timeout);
1301
+ func(...args);
1302
+ };
1303
+ clearTimeout(timeout);
1304
+ timeout = setTimeout(later, wait);
1305
+ };
1306
+ }
1307
+
1308
+ // Star functionality
1309
+ function toggleStar(paperKey, event) {
1310
+ event.stopPropagation(); // Prevent triggering the card click
1311
+
1312
+ const index = starredPapers.indexOf(paperKey);
1313
+ if (index > -1) {
1314
+ // Remove from starred
1315
+ starredPapers.splice(index, 1);
1316
+ showNotification('Paper removed from starred list', 'success', 1000);
1317
+ } else {
1318
+ // Add to starred
1319
+ starredPapers.push(paperKey);
1320
+ showNotification('Paper added to starred list', 'success', 1000);
1321
+ }
1322
+
1323
+ // Update the star button appearance
1324
+ updateStarButton(paperKey);
1325
+ updateStarredCount();
1326
+ updateStarredFilterInfo(); // Update filter info when starred papers change
1327
+ }
1328
+
1329
+ function isPaperStarred(paperKey) {
1330
+ return starredPapers.includes(paperKey);
1331
+ }
1332
+
1333
+ function updateStarButton(paperKey) {
1334
+ const starButtons = document.querySelectorAll(`[data-paper-key="${paperKey}"]`);
1335
+ starButtons.forEach(button => {
1336
+ const icon = button.querySelector('i');
1337
+ if (isPaperStarred(paperKey)) {
1338
+ button.classList.add('starred');
1339
+ icon.className = 'fas fa-star';
1340
+ } else {
1341
+ button.classList.remove('starred');
1342
+ icon.className = 'far fa-star';
1343
+ }
1344
+ });
1345
+ }
1346
+
1347
+ function updateStarredCount() {
1348
+ const exportStarred = document.getElementById('exportStarred');
1349
+ if (exportStarred) {
1350
+ // Clear the button content and rebuild it with the updated count
1351
+ exportStarred.innerHTML = `
1352
+ <i class="fas fa-star"></i>
1353
+ Export Starred (${starredPapers.length})
1354
+ `;
1355
+ }
1356
+ }
1357
+
1358
+ function exportStarredPapers() {
1359
+ if (starredPapers.length === 0) {
1360
+ showNotification('No starred papers to export', 'warning');
1361
+ return;
1362
+ }
1363
+
1364
+ // Get the actual paper objects for starred papers
1365
+ const starredPaperObjects = allPapers.filter(paper => starredPapers.includes(paper.key));
1366
+
1367
+ const bibTeXContent = generateBibTeXContent(starredPaperObjects);
1368
+ const blob = new Blob([bibTeXContent], { type: 'text/plain' });
1369
+ const url = URL.createObjectURL(blob);
1370
+ const a = document.createElement('a');
1371
+ a.href = url;
1372
+ const now = new Date();
1373
+ const timestamp = now.toISOString().slice(0, 19).replace(/:/g, '-').replace('T', '_');
1374
+ a.download = `starred_papers_${starredPapers.length}_papers_${timestamp}.bib`;
1375
+ document.body.appendChild(a);
1376
+ a.click();
1377
+ document.body.removeChild(a);
1378
+ URL.revokeObjectURL(url);
1379
+
1380
+ showNotification(`Exported ${starredPapers.length} starred papers to BibTeX file!`);
1381
+ }
1382
+
1383
+ // Save current filtering status and document IDs to JSON file
1384
+ function saveCurrentStatus() {
1385
+ const status = {
1386
+ timestamp: new Date().toISOString(),
1387
+ filters: {
1388
+ searchKeywords: searchKeywords,
1389
+ removeSearchKeywords: removeSearchKeywords,
1390
+ yearFilter: {
1391
+ min: selectedYearMin,
1392
+ max: selectedYearMax,
1393
+ active: yearFilterActive
1394
+ },
1395
+ conferenceFilter: {
1396
+ selectedConferences: selectedConferences,
1397
+ active: conferenceFilterActive
1398
+ },
1399
+ abstractFilter: {
1400
+ includeNoAbstract: includeNoAbstract,
1401
+ active: abstractFilterActive
1402
+ },
1403
+ authorFilter: {
1404
+ includeNoAuthor: includeNoAuthor,
1405
+ active: authorFilterActive
1406
+ },
1407
+ starredFilter: {
1408
+ showOnlyStarred: showOnlyStarred,
1409
+ active: starredFilterActive
1410
+ }
1411
+ },
1412
+ pagination: {
1413
+ currentPage: currentPage,
1414
+ papersPerPage: papersPerPage
1415
+ },
1416
+ results: {
1417
+ totalPapers: allPapers.length,
1418
+ filteredPapersCount: filteredPapers.length,
1419
+ filteredPaperIds: filteredPapers.map(paper => paper.key)
1420
+ },
1421
+ starredPapers: starredPapers // Save starred papers list
1422
+ };
1423
+
1424
+ const blob = new Blob([JSON.stringify(status, null, 2)], { type: 'application/json' });
1425
+ const url = URL.createObjectURL(blob);
1426
+ const a = document.createElement('a');
1427
+ a.href = url;
1428
+ const now = new Date();
1429
+ const timestamp = now.toISOString().slice(0, 19).replace(/:/g, '-').replace('T', '_');
1430
+ a.download = `multilingual_papers_filter_${timestamp}.json`;
1431
+ document.body.appendChild(a);
1432
+ a.click();
1433
+ document.body.removeChild(a);
1434
+ URL.revokeObjectURL(url);
1435
+
1436
+ showNotification('Filter status saved successfully!');
1437
+ }
1438
+
1439
+ // Load filtering status from JSON file
1440
+ function loadStatusFromFile() {
1441
+ const input = document.createElement('input');
1442
+ input.type = 'file';
1443
+ input.accept = '.json';
1444
+ input.onchange = function(event) {
1445
+ const file = event.target.files[0];
1446
+ if (file) {
1447
+ const reader = new FileReader();
1448
+ reader.onload = function(e) {
1449
+ try {
1450
+ const status = JSON.parse(e.target.result);
1451
+ applyLoadedStatus(status);
1452
+ showNotification('Filter status loaded successfully!');
1453
+ } catch (error) {
1454
+ console.error('Error parsing status file:', error);
1455
+ showNotification('Error: Invalid status file format', 'error');
1456
+ }
1457
+ };
1458
+ reader.readAsText(file);
1459
+ }
1460
+ };
1461
+ input.click();
1462
+ }
1463
+
1464
+ // Apply loaded filtering status
1465
+ function applyLoadedStatus(status) {
1466
+ if (!status.filters) {
1467
+ showNotification('Error: Invalid status file format', 'error');
1468
+ return;
1469
+ }
1470
+
1471
+ const filters = status.filters;
1472
+
1473
+ // Apply search keywords
1474
+ searchKeywords = filters.searchKeywords || [];
1475
+ removeSearchKeywords = filters.removeSearchKeywords || [];
1476
+
1477
+ // Apply year filter
1478
+ if (filters.yearFilter) {
1479
+ selectedYearMin = filters.yearFilter.min || 1960;
1480
+ selectedYearMax = filters.yearFilter.max || 2025;
1481
+ yearFilterActive = filters.yearFilter.active || false;
1482
+
1483
+ // Update UI
1484
+ const yearInputMin = document.getElementById('yearInputMin');
1485
+ const yearInputMax = document.getElementById('yearInputMax');
1486
+ if (yearInputMin) yearInputMin.value = selectedYearMin;
1487
+ if (yearInputMax) yearInputMax.value = selectedYearMax;
1488
+ updateYearRangeDisplay();
1489
+ }
1490
+
1491
+ // Apply conference filter
1492
+ if (filters.conferenceFilter) {
1493
+ selectedConferences = filters.conferenceFilter.selectedConferences || [];
1494
+ conferenceFilterActive = filters.conferenceFilter.active || false;
1495
+
1496
+ // Update UI
1497
+ const conferenceSelect = document.getElementById('conferenceSelect');
1498
+ if (conferenceSelect) {
1499
+ // Clear current selection
1500
+ conferenceSelect.selectedIndex = -1;
1501
+ // Select the loaded conferences
1502
+ selectedConferences.forEach(conference => {
1503
+ const option = Array.from(conferenceSelect.options).find(opt => opt.value === conference);
1504
+ if (option) {
1505
+ option.selected = true;
1506
+ }
1507
+ });
1508
+ }
1509
+ updateConferenceCountDisplay();
1510
+ updateSelectedConferencesDisplay();
1511
+ }
1512
+
1513
+ // Apply abstract filter
1514
+ if (filters.abstractFilter) {
1515
+ includeNoAbstract = filters.abstractFilter.includeNoAbstract !== undefined ?
1516
+ filters.abstractFilter.includeNoAbstract : true;
1517
+ abstractFilterActive = filters.abstractFilter.active || false;
1518
+
1519
+ // Update UI
1520
+ const includeNoAbstractCheckbox = document.getElementById('includeNoAbstract');
1521
+ if (includeNoAbstractCheckbox) {
1522
+ includeNoAbstractCheckbox.checked = includeNoAbstract;
1523
+ }
1524
+ updateAbstractFilterInfo();
1525
+ }
1526
+
1527
+ // Apply author filter
1528
+ if (filters.authorFilter) {
1529
+ includeNoAuthor = filters.authorFilter.includeNoAuthor !== undefined ?
1530
+ filters.authorFilter.includeNoAuthor : true;
1531
+ authorFilterActive = filters.authorFilter.active || false;
1532
+
1533
+ // Update UI
1534
+ const includeNoAuthorCheckbox = document.getElementById('includeNoAuthor');
1535
+ if (includeNoAuthorCheckbox) {
1536
+ includeNoAuthorCheckbox.checked = includeNoAuthor;
1537
+ }
1538
+ updateAuthorFilterInfo();
1539
+ }
1540
+
1541
+ // Apply starred filter
1542
+ if (filters.starredFilter) {
1543
+ showOnlyStarred = filters.starredFilter.showOnlyStarred !== undefined ?
1544
+ filters.starredFilter.showOnlyStarred : false;
1545
+ starredFilterActive = filters.starredFilter.active || false;
1546
+
1547
+ // Update UI
1548
+ const showOnlyStarredCheckbox = document.getElementById('showOnlyStarred');
1549
+ if (showOnlyStarredCheckbox) {
1550
+ showOnlyStarredCheckbox.checked = showOnlyStarred;
1551
+ }
1552
+ updateStarredFilterInfo();
1553
+ }
1554
+
1555
+ // Apply pagination
1556
+ if (status.pagination) {
1557
+ currentPage = status.pagination.currentPage || 1;
1558
+ papersPerPage = status.pagination.papersPerPage || 10;
1559
+ }
1560
+
1561
+ // Apply starred papers
1562
+ if (status.starredPapers) {
1563
+ starredPapers = status.starredPapers || [];
1564
+ }
1565
+
1566
+ // Update displays
1567
+ updateSearchKeywordsDisplay();
1568
+ updateRemoveKeywordsDisplay();
1569
+ updateStarredCount(); // Update starred count display
1570
+
1571
+ // Re-apply filters
1572
+ filterPapers();
1573
+
1574
+ console.log('Applied loaded status:', status);
1575
+ }
1576
+
1577
+ // Export current filtered results to BibTeX
1578
+ function exportFilteredResults() {
1579
+ if (filteredPapers.length === 0) {
1580
+ showNotification('No papers to export', 'warning');
1581
+ return;
1582
+ }
1583
+
1584
+ const bibTeXContent = generateBibTeXContent(filteredPapers);
1585
+ const blob = new Blob([bibTeXContent], { type: 'text/plain' });
1586
+ const url = URL.createObjectURL(blob);
1587
+ const a = document.createElement('a');
1588
+ a.href = url;
1589
+ const now = new Date();
1590
+ const timestamp = now.toISOString().slice(0, 19).replace(/:/g, '-').replace('T', '_');
1591
+ a.download = `multilingual_papers_filtered_${filteredPapers.length}_papers_${timestamp}.bib`;
1592
+ document.body.appendChild(a);
1593
+ a.click();
1594
+ document.body.removeChild(a);
1595
+ URL.revokeObjectURL(url);
1596
+
1597
+ showNotification(`Exported ${filteredPapers.length} papers to BibTeX file!`);
1598
+ }
styles.css ADDED
@@ -0,0 +1,1835 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Reset and base styles */
2
+ * {
3
+ margin: 0;
4
+ padding: 0;
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ body {
9
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10
+ line-height: 1.6;
11
+ color: #333;
12
+ background: linear-gradient(135deg, #e0ecce 0%, #c8e6c9 100%);
13
+ min-height: 100vh;
14
+ }
15
+
16
+ .container {
17
+ max-width: 1200px;
18
+ margin: 0 auto;
19
+ padding: 0 20px;
20
+ }
21
+
22
+ /* Header */
23
+ .header {
24
+ background: rgba(255, 255, 255, 0.95);
25
+ backdrop-filter: blur(10px);
26
+ border-radius: 0 0 20px 20px;
27
+ margin-bottom: 30px;
28
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
29
+ }
30
+
31
+ .header-content {
32
+ padding: 40px 20px;
33
+ text-align: center;
34
+ }
35
+
36
+ .title {
37
+ font-size: 2.5rem;
38
+ font-weight: 700;
39
+ color: #2d3748;
40
+ margin-bottom: 10px;
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: center;
44
+ gap: 15px;
45
+ }
46
+
47
+ .title i {
48
+ color: #689f38;
49
+ }
50
+
51
+ .subtitle {
52
+ font-size: 1.1rem;
53
+ color: #718096;
54
+ font-weight: 400;
55
+ }
56
+
57
+ /* Main content */
58
+ .main-content {
59
+ background: rgba(255, 255, 255, 0.95);
60
+ backdrop-filter: blur(10px);
61
+ border-radius: 20px;
62
+ padding: 30px;
63
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
64
+ margin-bottom: 30px;
65
+ }
66
+
67
+ /* Search section */
68
+ .search-section {
69
+ margin-bottom: 30px;
70
+ display: flex;
71
+ gap: 20px;
72
+ flex-wrap: wrap;
73
+ justify-content: center;
74
+ }
75
+
76
+ .search-container {
77
+ max-width: 600px;
78
+ margin: 0 auto;
79
+ flex: 1;
80
+ min-width: 300px;
81
+ }
82
+
83
+ .search-box {
84
+ position: relative;
85
+ display: flex;
86
+ align-items: center;
87
+ background: white;
88
+ border-radius: 50px;
89
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
90
+ overflow: hidden;
91
+ transition: all 0.3s ease;
92
+ }
93
+
94
+ .search-box:focus-within {
95
+ box-shadow: 0 6px 25px rgba(104, 159, 56, 0.3);
96
+ transform: translateY(-2px);
97
+ }
98
+
99
+ .remove-search-box:focus-within {
100
+ box-shadow: 0 6px 25px rgba(220, 53, 69, 0.3);
101
+ }
102
+
103
+ .remove-search-box .search-icon {
104
+ color: #dc3545;
105
+ }
106
+
107
+ .add-keyword-btn {
108
+ position: absolute;
109
+ right: 50px;
110
+ background: #dc3545;
111
+ border: none;
112
+ color: white;
113
+ cursor: pointer;
114
+ padding: 8px;
115
+ border-radius: 50%;
116
+ transition: all 0.2s ease;
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ width: 32px;
121
+ height: 32px;
122
+ z-index: 2;
123
+ }
124
+
125
+ .add-keyword-btn:hover {
126
+ background: #c82333;
127
+ transform: scale(1.1);
128
+ }
129
+
130
+ .search-add-btn {
131
+ background: #689f38;
132
+ }
133
+
134
+ .search-add-btn:hover {
135
+ background: #558b2f;
136
+ }
137
+
138
+ .search-keywords-container {
139
+ margin-top: 10px;
140
+ display: flex;
141
+ flex-wrap: wrap;
142
+ gap: 8px;
143
+ min-height: 20px;
144
+ }
145
+
146
+ .search-keyword-tag {
147
+ display: inline-flex;
148
+ align-items: center;
149
+ gap: 6px;
150
+ padding: 6px 12px;
151
+ background: #689f38;
152
+ color: white;
153
+ border-radius: 20px;
154
+ font-size: 12px;
155
+ font-weight: 500;
156
+ cursor: pointer;
157
+ transition: all 0.2s ease;
158
+ animation: fadeIn 0.3s ease;
159
+ }
160
+
161
+ .search-keyword-tag:hover {
162
+ background: #558b2f;
163
+ transform: translateY(-1px);
164
+ }
165
+
166
+ .search-keyword-tag .remove-keyword {
167
+ background: none;
168
+ border: none;
169
+ color: white;
170
+ cursor: pointer;
171
+ font-size: 10px;
172
+ padding: 0;
173
+ width: 16px;
174
+ height: 16px;
175
+ display: flex;
176
+ align-items: center;
177
+ justify-content: center;
178
+ border-radius: 50%;
179
+ transition: background-color 0.2s ease;
180
+ }
181
+
182
+ .search-keyword-tag .remove-keyword:hover {
183
+ background: rgba(255, 255, 255, 0.2);
184
+ }
185
+
186
+ .remove-keywords-container {
187
+ margin-top: 10px;
188
+ display: flex;
189
+ flex-wrap: wrap;
190
+ gap: 8px;
191
+ min-height: 20px;
192
+ }
193
+
194
+ .remove-keyword-tag {
195
+ display: inline-flex;
196
+ align-items: center;
197
+ gap: 6px;
198
+ padding: 6px 12px;
199
+ background: #dc3545;
200
+ color: white;
201
+ border-radius: 20px;
202
+ font-size: 12px;
203
+ font-weight: 500;
204
+ cursor: pointer;
205
+ transition: all 0.2s ease;
206
+ animation: fadeIn 0.3s ease;
207
+ }
208
+
209
+ .remove-keyword-tag:hover {
210
+ background: #c82333;
211
+ transform: translateY(-1px);
212
+ }
213
+
214
+ .remove-keyword-tag .remove-keyword {
215
+ background: none;
216
+ border: none;
217
+ color: white;
218
+ cursor: pointer;
219
+ font-size: 10px;
220
+ padding: 0;
221
+ width: 16px;
222
+ height: 16px;
223
+ display: flex;
224
+ align-items: center;
225
+ justify-content: center;
226
+ border-radius: 50%;
227
+ transition: background-color 0.2s ease;
228
+ }
229
+
230
+ .remove-keyword-tag .remove-keyword:hover {
231
+ background: rgba(255, 255, 255, 0.2);
232
+ }
233
+
234
+ @keyframes fadeIn {
235
+ from {
236
+ opacity: 0;
237
+ transform: translateY(-10px);
238
+ }
239
+ to {
240
+ opacity: 1;
241
+ transform: translateY(0);
242
+ }
243
+ }
244
+
245
+ .search-icon {
246
+ position: absolute;
247
+ left: 20px;
248
+ color: #a0aec0;
249
+ font-size: 1.1rem;
250
+ z-index: 1;
251
+ }
252
+
253
+ .search-input {
254
+ flex: 1;
255
+ padding: 18px 50px 18px 50px;
256
+ border: none;
257
+ outline: none;
258
+ font-size: 1rem;
259
+ background: transparent;
260
+ }
261
+
262
+ .search-input::placeholder {
263
+ color: #a0aec0;
264
+ }
265
+
266
+ .clear-btn {
267
+ position: absolute;
268
+ right: 15px;
269
+ background: none;
270
+ border: none;
271
+ color: #a0aec0;
272
+ cursor: pointer;
273
+ padding: 5px;
274
+ border-radius: 50%;
275
+ transition: all 0.2s ease;
276
+ display: none;
277
+ z-index: 3;
278
+ }
279
+
280
+ .clear-btn:hover {
281
+ background: #f7fafc;
282
+ color: #4a5568;
283
+ }
284
+
285
+ .clear-btn.visible {
286
+ display: block;
287
+ }
288
+
289
+ .search-field-select {
290
+ padding: 8px 12px;
291
+ border: none;
292
+ outline: none;
293
+ background: #f7fafc;
294
+ color: #2d3748;
295
+ font-size: 0.9rem;
296
+ border-radius: 6px;
297
+ margin-right: 10px;
298
+ cursor: pointer;
299
+ transition: all 0.3s ease;
300
+ }
301
+
302
+ .search-field-select:hover {
303
+ background: #edf2f7;
304
+ }
305
+
306
+ .search-field-select:focus {
307
+ background: #e2e8f0;
308
+ box-shadow: 0 0 0 2px rgba(104, 159, 56, 0.2);
309
+ }
310
+
311
+ .search-stats {
312
+ text-align: center;
313
+ margin-bottom: 15px;
314
+ color: #718096;
315
+ font-size: 0.9rem;
316
+ min-height: 20px;
317
+ display: flex;
318
+ align-items: center;
319
+ justify-content: center;
320
+ }
321
+
322
+ /* Filters section */
323
+ .filters-section {
324
+ margin-bottom: 25px;
325
+ display: flex !important;
326
+ gap: 15px !important;
327
+ flex-wrap: wrap !important;
328
+ flex-direction: column !important;
329
+ width: 100% !important;
330
+ align-items: stretch !important;
331
+ }
332
+
333
+ .year-filter {
334
+ display: flex;
335
+ align-items: center;
336
+ gap: 15px;
337
+ flex-wrap: wrap;
338
+ background: rgba(224, 236, 206, 0.8) !important;
339
+ border-radius: 12px !important;
340
+ padding: 15px !important;
341
+ border: 2px solid #689f38 !important;
342
+ width: 100% !important;
343
+ margin: 0 !important;
344
+ }
345
+
346
+ .filter-label {
347
+ font-weight: 600;
348
+ color: #2d3748;
349
+ font-size: 1rem;
350
+ display: flex;
351
+ align-items: center;
352
+ gap: 8px;
353
+ flex-wrap: wrap;
354
+ }
355
+
356
+ .filter-label i {
357
+ color: #689f38;
358
+ }
359
+
360
+ .year-input-container {
361
+ flex: 1;
362
+ min-width: 200px;
363
+ max-width: 400px;
364
+ display: flex;
365
+ gap: 15px;
366
+ align-items: center;
367
+ }
368
+
369
+ .year-input-group {
370
+ display: flex;
371
+ flex-direction: column;
372
+ gap: 5px;
373
+ }
374
+
375
+ .year-input-group label {
376
+ font-size: 0.8rem;
377
+ color: #718096;
378
+ font-weight: 500;
379
+ }
380
+
381
+ .year-input {
382
+ width: 80px;
383
+ padding: 8px 12px;
384
+ border: 1px solid #e2e8f0;
385
+ border-radius: 6px;
386
+ font-size: 0.9rem;
387
+ color: #2d3748;
388
+ background: white;
389
+ outline: none;
390
+ transition: all 0.3s ease;
391
+ }
392
+
393
+ .year-input:focus {
394
+ border-color: #689f38;
395
+ box-shadow: 0 0 0 2px rgba(104, 159, 56, 0.2);
396
+ }
397
+
398
+ .year-input::-webkit-inner-spin-button,
399
+ .year-input::-webkit-outer-spin-button {
400
+ opacity: 1;
401
+ }
402
+
403
+ .dual-slider {
404
+ position: relative;
405
+ margin-bottom: 8px;
406
+ }
407
+
408
+ .year-slider {
409
+ width: 100%;
410
+ height: 6px;
411
+ border-radius: 3px;
412
+ background: transparent;
413
+ outline: none;
414
+ -webkit-appearance: none;
415
+ appearance: none;
416
+ position: absolute;
417
+ top: 0;
418
+ left: 0;
419
+ pointer-events: none;
420
+ }
421
+
422
+ .year-slider-min {
423
+ z-index: 2;
424
+ pointer-events: auto;
425
+ }
426
+
427
+ .year-slider-max {
428
+ z-index: 1;
429
+ pointer-events: auto;
430
+ }
431
+
432
+ .dual-slider::before {
433
+ content: '';
434
+ position: absolute;
435
+ top: 0;
436
+ left: 0;
437
+ right: 0;
438
+ height: 6px;
439
+ background: #e2e8f0;
440
+ border-radius: 3px;
441
+ z-index: 0;
442
+ }
443
+
444
+ .dual-slider::after {
445
+ content: '';
446
+ position: absolute;
447
+ top: 0;
448
+ left: var(--track-left, 0%);
449
+ width: var(--track-width, 100%);
450
+ height: 6px;
451
+ background: #689f38;
452
+ border-radius: 3px;
453
+ z-index: 0;
454
+ transition: all 0.3s ease;
455
+ }
456
+
457
+ .year-slider::-webkit-slider-thumb {
458
+ -webkit-appearance: none;
459
+ appearance: none;
460
+ width: 20px;
461
+ height: 20px;
462
+ border-radius: 50%;
463
+ background: #689f38;
464
+ cursor: pointer;
465
+ box-shadow: 0 2px 6px rgba(104, 159, 56, 0.3);
466
+ transition: all 0.3s ease;
467
+ }
468
+
469
+ .year-slider::-webkit-slider-thumb:hover {
470
+ transform: scale(1.1);
471
+ box-shadow: 0 4px 12px rgba(104, 159, 56, 0.4);
472
+ }
473
+
474
+ .year-slider::-moz-range-thumb {
475
+ width: 20px;
476
+ height: 20px;
477
+ border-radius: 50%;
478
+ background: #689f38;
479
+ cursor: pointer;
480
+ border: none;
481
+ box-shadow: 0 2px 6px rgba(104, 159, 56, 0.3);
482
+ transition: all 0.3s ease;
483
+ }
484
+
485
+ .year-slider::-moz-range-thumb:hover {
486
+ transform: scale(1.1);
487
+ box-shadow: 0 4px 12px rgba(104, 159, 56, 0.4);
488
+ }
489
+
490
+ .year-labels {
491
+ display: flex;
492
+ justify-content: space-between;
493
+ font-size: 0.8rem;
494
+ color: #718096;
495
+ font-weight: 500;
496
+ }
497
+
498
+ .clear-year-btn {
499
+ display: flex;
500
+ align-items: center;
501
+ gap: 6px;
502
+ padding: 8px 16px;
503
+ background: #d32f2f;
504
+ border: none;
505
+ border-radius: 6px;
506
+ color: white;
507
+ font-weight: 500;
508
+ cursor: pointer;
509
+ transition: all 0.3s ease;
510
+ font-size: 14px;
511
+ margin-left: auto;
512
+ }
513
+
514
+ .clear-year-btn:hover {
515
+ background-color: #b71c1c;
516
+ transform: translateY(-1px);
517
+ }
518
+
519
+ /* Conference filter */
520
+ .conference-filter {
521
+ margin-top: 0 !important;
522
+ padding: 15px !important;
523
+ background: rgba(224, 236, 206, 0.6) !important;
524
+ border-radius: 12px !important;
525
+ border: 2px solid #8bc34a !important;
526
+ width: 100% !important;
527
+ position: relative !important;
528
+ }
529
+
530
+ .conference-select-container {
531
+ margin-top: 20px;
532
+ position: relative;
533
+ }
534
+
535
+ .conference-hint {
536
+ font-size: 0.9rem;
537
+ color: #718096;
538
+ margin-left: 8px;
539
+ font-weight: normal;
540
+ }
541
+
542
+ .conference-select {
543
+ width: 100%;
544
+ min-height: 120px;
545
+ padding: 12px;
546
+ border: 2px solid #e2e8f0;
547
+ border-radius: 8px;
548
+ background: white;
549
+ font-size: 14px;
550
+ line-height: 1.5;
551
+ resize: vertical;
552
+ transition: all 0.3s ease;
553
+ cursor: pointer;
554
+ }
555
+
556
+
557
+
558
+ .conference-select:focus {
559
+ outline: none;
560
+ border-color: #689f38;
561
+ box-shadow: 0 0 0 3px rgba(104, 159, 56, 0.1);
562
+ }
563
+
564
+ .conference-select option {
565
+ padding: 8px 12px;
566
+ margin: 2px 0;
567
+ border-radius: 4px;
568
+ cursor: pointer;
569
+ transition: background-color 0.2s ease;
570
+ }
571
+
572
+ .conference-select option:hover {
573
+ background-color: #f7fafc;
574
+ }
575
+
576
+ .conference-select option:checked {
577
+ background-color: #689f38;
578
+ color: white;
579
+ }
580
+
581
+ .selected-conferences {
582
+ margin-top: 8px;
583
+ display: flex;
584
+ flex-wrap: wrap;
585
+ gap: 6px;
586
+ min-height: 16px;
587
+ }
588
+
589
+ .conference-tag {
590
+ display: inline-flex;
591
+ align-items: center;
592
+ gap: 6px;
593
+ padding: 4px 8px;
594
+ background: #689f38;
595
+ color: white;
596
+ border-radius: 16px;
597
+ font-size: 12px;
598
+ font-weight: 500;
599
+ cursor: pointer;
600
+ transition: all 0.2s ease;
601
+ }
602
+
603
+ .conference-tag:hover {
604
+ background: #558b2f;
605
+ transform: translateY(-1px);
606
+ }
607
+
608
+ .conference-tag .remove-tag {
609
+ background: none;
610
+ border: none;
611
+ color: white;
612
+ cursor: pointer;
613
+ font-size: 10px;
614
+ padding: 0;
615
+ width: 16px;
616
+ height: 16px;
617
+ display: flex;
618
+ align-items: center;
619
+ justify-content: center;
620
+ border-radius: 50%;
621
+ transition: background-color 0.2s ease;
622
+ }
623
+
624
+ .conference-tag .remove-tag:hover {
625
+ background: rgba(255, 255, 255, 0.2);
626
+ }
627
+
628
+ .clear-conference-btn {
629
+ position: absolute !important;
630
+ top: 20px !important;
631
+ right: 15px !important;
632
+ padding: 8px 16px !important;
633
+ background: #d32f2f;
634
+ color: white;
635
+ border: none;
636
+ border-radius: 6px;
637
+ font-size: 14px;
638
+ font-weight: 500;
639
+ cursor: pointer;
640
+ transition: all 0.3s ease;
641
+ display: flex;
642
+ align-items: center;
643
+ gap: 6px;
644
+ }
645
+
646
+ .clear-conference-btn:hover {
647
+ background-color: #b71c1c;
648
+ transform: translateY(-1px);
649
+ }
650
+
651
+ /* Abstract filter */
652
+ .abstract-filter {
653
+ margin-top: 0 !important;
654
+ padding: 15px !important;
655
+ background: rgba(224, 236, 206, 0.4) !important;
656
+ border-radius: 12px !important;
657
+ border: 2px solid #4caf50 !important;
658
+ width: 100% !important;
659
+ position: relative !important;
660
+ }
661
+
662
+ /* Starred filter */
663
+ .starred-filter {
664
+ margin: 0 !important;
665
+ padding: 15px !important;
666
+ background: rgba(255, 193, 7, 0.2) !important;
667
+ border-radius: 12px !important;
668
+ border: 2px solid #ffc107 !important;
669
+ width: 100% !important;
670
+ position: relative !important;
671
+ }
672
+
673
+ .starred-filter-container {
674
+ margin-top: 10px;
675
+ display: flex;
676
+ flex-direction: column;
677
+ gap: 8px;
678
+ }
679
+
680
+ .abstract-filter-container {
681
+ margin-top: 10px;
682
+ display: flex;
683
+ flex-direction: column;
684
+ gap: 8px;
685
+ }
686
+
687
+ .checkbox-label {
688
+ display: flex;
689
+ align-items: center;
690
+ gap: 12px;
691
+ cursor: pointer;
692
+ font-weight: 500;
693
+ color: #2d3748;
694
+ font-size: 1rem;
695
+ position: relative;
696
+ padding: 4px 0;
697
+ flex-wrap: wrap;
698
+ }
699
+
700
+ .checkbox-label input[type="checkbox"] {
701
+ position: absolute;
702
+ opacity: 0;
703
+ cursor: pointer;
704
+ height: 0;
705
+ width: 0;
706
+ }
707
+
708
+ .checkmark {
709
+ height: 20px;
710
+ width: 20px;
711
+ background-color: white;
712
+ border: 2px solid #e2e8f0;
713
+ border-radius: 4px;
714
+ position: relative;
715
+ transition: all 0.3s ease;
716
+ flex-shrink: 0;
717
+ }
718
+
719
+ .checkbox-label:hover input ~ .checkmark {
720
+ border-color: #689f38;
721
+ background-color: #f7fafc;
722
+ }
723
+
724
+ .checkbox-label input:checked ~ .checkmark {
725
+ background-color: #689f38;
726
+ border-color: #689f38;
727
+ }
728
+
729
+ .checkmark:after {
730
+ content: "";
731
+ position: absolute;
732
+ display: none;
733
+ left: 6px;
734
+ top: 2px;
735
+ width: 5px;
736
+ height: 10px;
737
+ border: solid white;
738
+ border-width: 0 2px 2px 0;
739
+ transform: rotate(45deg);
740
+ }
741
+
742
+ .checkbox-label input:checked ~ .checkmark:after {
743
+ display: block;
744
+ }
745
+
746
+ .filter-info {
747
+ font-size: 0.9rem;
748
+ color: #718096;
749
+ margin-left: 8px;
750
+ font-weight: normal;
751
+ }
752
+
753
+ .filter-option {
754
+ margin-bottom: 0px;
755
+ }
756
+
757
+ .filter-option:last-child {
758
+ margin-bottom: 0;
759
+ }
760
+
761
+ /* Removed author-filter-info as it's now handled by .filter-info */
762
+
763
+ /* Pagination section */
764
+ .pagination-section {
765
+ margin-bottom: 30px;
766
+ display: flex;
767
+ justify-content: space-between;
768
+ align-items: center;
769
+ flex-wrap: wrap;
770
+ gap: 20px;
771
+ }
772
+
773
+ .pagination-info {
774
+ font-weight: 500;
775
+ color: #4a5568;
776
+ font-size: 0.95rem;
777
+ }
778
+
779
+ .pagination-controls {
780
+ display: flex;
781
+ align-items: center;
782
+ gap: 10px;
783
+ }
784
+
785
+ .pagination-btn {
786
+ display: flex;
787
+ align-items: center;
788
+ gap: 8px;
789
+ padding: 10px 16px;
790
+ background: white;
791
+ border: 2px solid #e2e8f0;
792
+ border-radius: 25px;
793
+ color: #4a5568;
794
+ font-weight: 500;
795
+ cursor: pointer;
796
+ transition: all 0.3s ease;
797
+ font-size: 0.9rem;
798
+ }
799
+
800
+ .pagination-btn:hover:not(:disabled) {
801
+ border-color: #689f38;
802
+ color: #689f38;
803
+ transform: translateY(-1px);
804
+ }
805
+
806
+ .pagination-btn:disabled {
807
+ opacity: 0.5;
808
+ cursor: not-allowed;
809
+ }
810
+
811
+ .page-numbers {
812
+ display: flex;
813
+ align-items: center;
814
+ gap: 5px;
815
+ }
816
+
817
+ .page-number {
818
+ width: 40px;
819
+ height: 40px;
820
+ border: 2px solid #e2e8f0;
821
+ background: white;
822
+ border-radius: 50%;
823
+ color: #4a5568;
824
+ font-weight: 500;
825
+ cursor: pointer;
826
+ transition: all 0.3s ease;
827
+ display: flex;
828
+ align-items: center;
829
+ justify-content: center;
830
+ }
831
+
832
+ .page-number:hover {
833
+ border-color: #689f38;
834
+ color: #689f38;
835
+ }
836
+
837
+ .page-number.active {
838
+ background: #689f38;
839
+ border-color: #689f38;
840
+ color: white;
841
+ box-shadow: 0 2px 10px rgba(104, 159, 56, 0.3);
842
+ }
843
+
844
+ .page-ellipsis {
845
+ color: #a0aec0;
846
+ font-weight: 500;
847
+ padding: 0 5px;
848
+ }
849
+
850
+ /* Page input styles */
851
+ .page-input-container {
852
+ display: flex;
853
+ align-items: center;
854
+ gap: 8px;
855
+ margin: 0 10px;
856
+ }
857
+
858
+ .page-input-label {
859
+ font-size: 0.9rem;
860
+ color: #4a5568;
861
+ font-weight: 500;
862
+ white-space: nowrap;
863
+ }
864
+
865
+ .page-input {
866
+ width: 60px;
867
+ padding: 8px 10px;
868
+ border: 2px solid #e2e8f0;
869
+ border-radius: 6px;
870
+ font-size: 0.9rem;
871
+ color: #2d3748;
872
+ background: white;
873
+ outline: none;
874
+ transition: all 0.3s ease;
875
+ text-align: center;
876
+ }
877
+
878
+ .page-input:focus {
879
+ border-color: #689f38;
880
+ box-shadow: 0 0 0 2px rgba(104, 159, 56, 0.2);
881
+ }
882
+
883
+ .page-input::-webkit-inner-spin-button,
884
+ .page-input::-webkit-outer-spin-button {
885
+ opacity: 1;
886
+ }
887
+
888
+ .page-go-btn {
889
+ display: flex;
890
+ align-items: center;
891
+ justify-content: center;
892
+ padding: 8px 12px;
893
+ background: #689f38;
894
+ border: none;
895
+ border-radius: 6px;
896
+ color: white;
897
+ font-size: 0.9rem;
898
+ cursor: pointer;
899
+ transition: all 0.3s ease;
900
+ min-width: 36px;
901
+ height: 36px;
902
+ }
903
+
904
+ .page-go-btn:hover {
905
+ background: #558b2f;
906
+ transform: translateY(-1px);
907
+ }
908
+
909
+ .page-go-btn:disabled {
910
+ background: #a0aec0;
911
+ cursor: not-allowed;
912
+ transform: none;
913
+ }
914
+
915
+ /* Papers container */
916
+ .papers-container {
917
+ position: relative;
918
+ }
919
+
920
+ .papers-list {
921
+ display: grid;
922
+ gap: 20px;
923
+ }
924
+
925
+ /* Paper card styles */
926
+ .paper-card {
927
+ background: white;
928
+ border-radius: 15px;
929
+ padding: 25px;
930
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
931
+ transition: all 0.3s ease;
932
+ cursor: pointer;
933
+ border: 2px solid transparent;
934
+ }
935
+
936
+ .paper-card:hover {
937
+ transform: translateY(-5px);
938
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
939
+ border-color: #689f38;
940
+ }
941
+
942
+ .paper-header {
943
+ display: flex;
944
+ justify-content: space-between;
945
+ align-items: flex-start;
946
+ gap: 15px;
947
+ margin-bottom: 15px;
948
+ }
949
+
950
+ .paper-title {
951
+ font-size: 1.3rem;
952
+ font-weight: 600;
953
+ color: #2d3748;
954
+ margin-bottom: 10px;
955
+ flex: 1;
956
+ line-height: 1.4;
957
+ }
958
+
959
+ .star-button {
960
+ background: none;
961
+ border: none;
962
+ color: #cbd5e0;
963
+ font-size: 1.2rem;
964
+ cursor: pointer;
965
+ padding: 8px;
966
+ border-radius: 50%;
967
+ transition: all 0.3s ease;
968
+ flex-shrink: 0;
969
+ display: flex;
970
+ align-items: center;
971
+ justify-content: center;
972
+ min-width: 40px;
973
+ min-height: 40px;
974
+ }
975
+
976
+ .star-button:hover {
977
+ background: rgba(104, 159, 56, 0.1);
978
+ color: #689f38;
979
+ transform: scale(1.1);
980
+ }
981
+
982
+ .star-button.starred {
983
+ color: #f6ad55;
984
+ }
985
+
986
+ .star-button.starred:hover {
987
+ background: rgba(246, 173, 85, 0.1);
988
+ color: #ed8936;
989
+ }
990
+
991
+ .find-paper-btn {
992
+ background: none;
993
+ border: none;
994
+ color: #4a90e2;
995
+ font-size: 1.2rem;
996
+ cursor: pointer;
997
+ padding: 8px;
998
+ border-radius: 50%;
999
+ transition: all 0.3s ease;
1000
+ flex-shrink: 0;
1001
+ display: flex;
1002
+ align-items: center;
1003
+ justify-content: center;
1004
+ min-width: 40px;
1005
+ min-height: 40px;
1006
+ }
1007
+
1008
+ .find-paper-btn:hover {
1009
+ background: rgba(74, 144, 226, 0.1);
1010
+ color: #357abd;
1011
+ transform: scale(1.1);
1012
+ }
1013
+
1014
+ .paper-authors {
1015
+ color: #718096;
1016
+ font-weight: 500;
1017
+ margin-bottom: 15px;
1018
+ font-style: italic;
1019
+ }
1020
+
1021
+ .paper-abstract {
1022
+ color: #4a5568;
1023
+ font-size: 0.95rem;
1024
+ line-height: 1.6;
1025
+ margin-bottom: 15px;
1026
+ display: -webkit-box;
1027
+ -webkit-line-clamp: 3;
1028
+ -webkit-box-orient: vertical;
1029
+ overflow: hidden;
1030
+ }
1031
+
1032
+ .paper-meta {
1033
+ display: flex;
1034
+ justify-content: space-between;
1035
+ align-items: center;
1036
+ font-size: 0.85rem;
1037
+ color: #a0aec0;
1038
+ }
1039
+
1040
+ .paper-year {
1041
+ background: #f7fafc;
1042
+ padding: 4px 12px;
1043
+ border-radius: 15px;
1044
+ font-weight: 500;
1045
+ }
1046
+
1047
+ .paper-conference {
1048
+ background: #689f38;
1049
+ color: white;
1050
+ padding: 4px 12px;
1051
+ border-radius: 15px;
1052
+ font-weight: 500;
1053
+ font-size: 0.8rem;
1054
+ }
1055
+
1056
+ .paper-no-abstract-badge {
1057
+ background: #ff9800;
1058
+ color: white;
1059
+ padding: 4px 8px;
1060
+ border-radius: 12px;
1061
+ font-weight: 500;
1062
+ font-size: 0.7rem;
1063
+ }
1064
+
1065
+ .paper-card.no-abstract {
1066
+ border-left: 4px solid #ff9800;
1067
+ background: rgba(255, 152, 0, 0.05);
1068
+ }
1069
+
1070
+ .paper-card.no-abstract .paper-abstract {
1071
+ color: #ff9800;
1072
+ /* font-style: italic; */
1073
+ }
1074
+
1075
+ .paper-card.no-author {
1076
+ border-right: 4px solid #e91e63;
1077
+ background: rgba(233, 30, 99, 0.05);
1078
+ }
1079
+
1080
+ .paper-card.no-author .paper-authors {
1081
+ color: #e91e63;
1082
+ font-style: italic;
1083
+ }
1084
+
1085
+
1086
+ /* Loading indicator */
1087
+ .loading {
1088
+ text-align: center;
1089
+ padding: 60px 20px;
1090
+ color: #718096;
1091
+ }
1092
+
1093
+ .spinner {
1094
+ width: 40px;
1095
+ height: 40px;
1096
+ border: 4px solid #e2e8f0;
1097
+ border-top: 4px solid #689f38;
1098
+ border-radius: 50%;
1099
+ animation: spin 1s linear infinite;
1100
+ margin: 0 auto 20px;
1101
+ }
1102
+
1103
+ @keyframes spin {
1104
+ 0% { transform: rotate(0deg); }
1105
+ 100% { transform: rotate(360deg); }
1106
+ }
1107
+
1108
+ /* No results */
1109
+ .no-results {
1110
+ text-align: center;
1111
+ padding: 60px 20px;
1112
+ color: #718096;
1113
+ }
1114
+
1115
+ .no-results i {
1116
+ font-size: 3rem;
1117
+ color: #a0aec0;
1118
+ margin-bottom: 20px;
1119
+ }
1120
+
1121
+ .no-results h3 {
1122
+ font-size: 1.5rem;
1123
+ margin-bottom: 10px;
1124
+ color: #4a5568;
1125
+ }
1126
+
1127
+ /* Modal */
1128
+ .modal {
1129
+ display: none;
1130
+ position: fixed;
1131
+ z-index: 1000;
1132
+ left: 0;
1133
+ top: 0;
1134
+ width: 100%;
1135
+ height: 100%;
1136
+ background-color: rgba(0, 0, 0, 0.5);
1137
+ backdrop-filter: blur(5px);
1138
+ }
1139
+
1140
+ .modal-content {
1141
+ background-color: white;
1142
+ margin: 5% auto;
1143
+ padding: 0;
1144
+ border-radius: 20px;
1145
+ width: 90%;
1146
+ max-width: 800px;
1147
+ max-height: 80vh;
1148
+ overflow-y: auto;
1149
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
1150
+ animation: modalSlideIn 0.3s ease;
1151
+ }
1152
+
1153
+ @keyframes modalSlideIn {
1154
+ from {
1155
+ opacity: 0;
1156
+ transform: translateY(-50px);
1157
+ }
1158
+ to {
1159
+ opacity: 1;
1160
+ transform: translateY(0);
1161
+ }
1162
+ }
1163
+
1164
+ .modal-header {
1165
+ display: flex;
1166
+ justify-content: space-between;
1167
+ align-items: flex-start;
1168
+ padding: 25px 30px 20px 30px;
1169
+ border-bottom: 2px solid #e2e8f0;
1170
+ background: #f8fafc;
1171
+ border-radius: 20px 20px 0 0;
1172
+ }
1173
+
1174
+ .modal-title-container {
1175
+ display: flex;
1176
+ align-items: flex-start;
1177
+ gap: 15px;
1178
+ flex: 1;
1179
+ margin-right: 15px;
1180
+ justify-content: space-between;
1181
+ }
1182
+
1183
+ .modal-header h2 {
1184
+ font-size: 1.8rem;
1185
+ font-weight: 600;
1186
+ color: #2d3748;
1187
+ margin: 0;
1188
+ line-height: 1.3;
1189
+ flex: 1;
1190
+ }
1191
+
1192
+ .close-btn {
1193
+ background: none;
1194
+ border: none;
1195
+ font-size: 1.5rem;
1196
+ color: #718096;
1197
+ cursor: pointer;
1198
+ padding: 8px;
1199
+ border-radius: 50%;
1200
+ transition: all 0.3s ease;
1201
+ flex-shrink: 0;
1202
+ display: flex;
1203
+ align-items: center;
1204
+ justify-content: center;
1205
+ min-width: 40px;
1206
+ min-height: 40px;
1207
+ }
1208
+
1209
+ .close-btn:hover {
1210
+ background: rgba(220, 53, 69, 0.1);
1211
+ color: #dc3545;
1212
+ transform: scale(1.1);
1213
+ }
1214
+
1215
+ .modal-body {
1216
+ padding: 30px;
1217
+ max-height: calc(80vh - 120px); /* Account for header height */
1218
+ overflow-y: auto;
1219
+ }
1220
+
1221
+ .paper-details {
1222
+ display: flex;
1223
+ flex-direction: column;
1224
+ gap: 25px;
1225
+ }
1226
+
1227
+ .detail-section h3 {
1228
+ font-size: 1.1rem;
1229
+ color: #2d3748;
1230
+ margin-bottom: 10px;
1231
+ display: flex;
1232
+ align-items: center;
1233
+ gap: 10px;
1234
+ }
1235
+
1236
+ .detail-section h3 i {
1237
+ color: #689f38;
1238
+ width: 20px;
1239
+ }
1240
+
1241
+ .detail-section p {
1242
+ color: #4a5568;
1243
+ line-height: 1.6;
1244
+ }
1245
+
1246
+ /* Notification */
1247
+ .notification {
1248
+ position: fixed;
1249
+ top: 20px;
1250
+ right: 20px;
1251
+ background: #689f38;
1252
+ color: white;
1253
+ padding: 15px 20px;
1254
+ border-radius: 10px;
1255
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
1256
+ z-index: 1001;
1257
+ max-width: 400px;
1258
+ animation: slideInRight 0.3s ease;
1259
+ }
1260
+
1261
+ .notification-content {
1262
+ display: flex;
1263
+ align-items: center;
1264
+ gap: 10px;
1265
+ }
1266
+
1267
+ .notification-content i {
1268
+ font-size: 1.2rem;
1269
+ }
1270
+
1271
+ .notification-close {
1272
+ background: none;
1273
+ border: none;
1274
+ color: white;
1275
+ cursor: pointer;
1276
+ padding: 5px;
1277
+ border-radius: 50%;
1278
+ transition: background 0.2s ease;
1279
+ margin-left: auto;
1280
+ }
1281
+
1282
+ .notification-close:hover {
1283
+ background: rgba(255, 255, 255, 0.2);
1284
+ }
1285
+
1286
+ @keyframes slideInRight {
1287
+ from {
1288
+ transform: translateX(100%);
1289
+ opacity: 0;
1290
+ }
1291
+ to {
1292
+ transform: translateX(0);
1293
+ opacity: 1;
1294
+ }
1295
+ }
1296
+
1297
+ /* Export section */
1298
+ .export-section {
1299
+ margin-bottom: 20px;
1300
+ padding: 15px;
1301
+ background: linear-gradient(135deg, #f1f8e9 0%, #e8f5e8 100%);
1302
+ border-radius: 12px;
1303
+ border: 1px solid #c8e6c9;
1304
+ }
1305
+
1306
+ .export-container {
1307
+ display: flex;
1308
+ align-items: center;
1309
+ justify-content: space-between;
1310
+ gap: 15px;
1311
+ }
1312
+
1313
+ .export-title {
1314
+ font-size: 1.1rem;
1315
+ font-weight: 600;
1316
+ color: #2d3748;
1317
+ display: flex;
1318
+ align-items: center;
1319
+ gap: 8px;
1320
+ white-space: nowrap;
1321
+ }
1322
+
1323
+ .export-title i {
1324
+ color: #689f38;
1325
+ }
1326
+
1327
+ .export-buttons {
1328
+ display: flex;
1329
+ gap: 10px;
1330
+ justify-content: flex-end;
1331
+ flex-wrap: nowrap;
1332
+ flex: 1;
1333
+ }
1334
+
1335
+ .export-btn {
1336
+ padding: 8px 16px;
1337
+ border: none;
1338
+ border-radius: 20px;
1339
+ font-size: 0.9rem;
1340
+ font-weight: 500;
1341
+ cursor: pointer;
1342
+ transition: all 0.3s ease;
1343
+ display: flex;
1344
+ align-items: center;
1345
+ gap: 6px;
1346
+ text-decoration: none;
1347
+ color: white;
1348
+ }
1349
+
1350
+ .save-btn {
1351
+ background: linear-gradient(135deg, #4caf50 0%, #66bb6a 100%);
1352
+ box-shadow: 0 3px 10px rgba(76, 175, 80, 0.3);
1353
+ }
1354
+
1355
+ .save-btn:hover {
1356
+ transform: translateY(-1px);
1357
+ box-shadow: 0 4px 15px rgba(76, 175, 80, 0.4);
1358
+ }
1359
+
1360
+ .load-btn {
1361
+ background: linear-gradient(135deg, #8bc34a 0%, #9ccc65 100%);
1362
+ box-shadow: 0 3px 10px rgba(139, 195, 74, 0.3);
1363
+ }
1364
+
1365
+ .load-btn:hover {
1366
+ transform: translateY(-1px);
1367
+ box-shadow: 0 4px 15px rgba(139, 195, 74, 0.4);
1368
+ }
1369
+
1370
+ .export-bib-btn {
1371
+ background: linear-gradient(135deg, #689f38 0%, #7cb342 100%);
1372
+ box-shadow: 0 3px 10px rgba(104, 159, 56, 0.3);
1373
+ }
1374
+
1375
+ .export-bib-btn:hover {
1376
+ background: #689f38;
1377
+ transform: translateY(-2px);
1378
+ }
1379
+
1380
+ .export-starred-btn {
1381
+ background: linear-gradient(135deg, #f6ad55 0%, #ed8936 100%);
1382
+ color: white;
1383
+ }
1384
+
1385
+ .export-starred-btn:hover {
1386
+ background: linear-gradient(135deg, #ed8936 0%, #dd6b20 100%);
1387
+ transform: translateY(-2px);
1388
+ }
1389
+
1390
+ .export-info {
1391
+ margin-top: 20px;
1392
+ padding: 15px;
1393
+ background: rgba(104, 159, 56, 0.1);
1394
+ border-radius: 10px;
1395
+ border: 1px solid rgba(104, 159, 56, 0.3);
1396
+ }
1397
+
1398
+ .export-info i {
1399
+ color: #689f38;
1400
+ margin-right: 4px;
1401
+ }
1402
+
1403
+ /* Enhanced notification styles */
1404
+ .notification-success {
1405
+ border-left-color: #28a745;
1406
+ }
1407
+
1408
+ .notification-error {
1409
+ border-left-color: #dc3545;
1410
+ }
1411
+
1412
+ .notification-warning {
1413
+ border-left-color: #ffc107;
1414
+ }
1415
+
1416
+ .notification-info {
1417
+ border-left-color: #17a2b8;
1418
+ }
1419
+
1420
+ .notification-success .notification-content i {
1421
+ color: #28a745;
1422
+ }
1423
+
1424
+ .notification-error .notification-content i {
1425
+ color: #dc3545;
1426
+ }
1427
+
1428
+ .notification-warning .notification-content i {
1429
+ color: #ffc107;
1430
+ }
1431
+
1432
+ .notification-info .notification-content i {
1433
+ color: #17a2b8;
1434
+ }
1435
+
1436
+ /* Footer */
1437
+ .footer {
1438
+ text-align: center;
1439
+ padding: 20px;
1440
+ color: rgba(255, 255, 255, 0.8);
1441
+ font-size: 0.9rem;
1442
+ }
1443
+
1444
+ /* Responsive design */
1445
+ @media (max-width: 768px) {
1446
+ .container {
1447
+ padding: 0 15px;
1448
+ }
1449
+
1450
+ .header-content {
1451
+ padding: 30px 15px;
1452
+ }
1453
+
1454
+ .title {
1455
+ font-size: 2rem;
1456
+ flex-direction: column;
1457
+ gap: 10px;
1458
+ }
1459
+
1460
+ .main-content {
1461
+ padding: 20px;
1462
+ border-radius: 15px;
1463
+ }
1464
+
1465
+ .search-section {
1466
+ flex-direction: column;
1467
+ gap: 15px;
1468
+ }
1469
+
1470
+ .search-container {
1471
+ min-width: auto;
1472
+ max-width: none;
1473
+ }
1474
+
1475
+ .search-stats {
1476
+ margin-bottom: 12px;
1477
+ font-size: 0.85rem;
1478
+ min-height: 18px;
1479
+ }
1480
+
1481
+ .search-input {
1482
+ padding: 15px 45px 15px 45px;
1483
+ font-size: 0.95rem;
1484
+ }
1485
+
1486
+ .add-keyword-btn {
1487
+ right: 45px;
1488
+ width: 28px;
1489
+ height: 28px;
1490
+ padding: 6px;
1491
+ z-index: 2;
1492
+ }
1493
+
1494
+ .clear-btn {
1495
+ z-index: 3;
1496
+ }
1497
+
1498
+ .search-keywords-container {
1499
+ margin-top: 8px;
1500
+ gap: 6px;
1501
+ }
1502
+
1503
+ .search-keyword-tag {
1504
+ padding: 4px 10px;
1505
+ font-size: 11px;
1506
+ }
1507
+
1508
+ .remove-keywords-container {
1509
+ margin-top: 8px;
1510
+ gap: 6px;
1511
+ }
1512
+
1513
+ .remove-keyword-tag {
1514
+ padding: 4px 10px;
1515
+ font-size: 11px;
1516
+ }
1517
+
1518
+ .search-field-select {
1519
+ font-size: 0.85rem;
1520
+ padding: 7px 10px;
1521
+ margin-right: 8px;
1522
+ }
1523
+
1524
+ .filters-section {
1525
+ gap: 15px;
1526
+ }
1527
+
1528
+ .year-filter {
1529
+ flex-direction: column;
1530
+ align-items: stretch;
1531
+ gap: 15px;
1532
+ }
1533
+
1534
+ .year-input-container {
1535
+ min-width: auto;
1536
+ max-width: none;
1537
+ flex-direction: column;
1538
+ gap: 10px;
1539
+ }
1540
+
1541
+ .year-input-group {
1542
+ flex-direction: row;
1543
+ align-items: center;
1544
+ gap: 10px;
1545
+ }
1546
+
1547
+ .year-input {
1548
+ width: 70px;
1549
+ }
1550
+
1551
+ .conference-filter {
1552
+ margin-top: 0;
1553
+ padding: 15px;
1554
+ }
1555
+
1556
+ .conference-select {
1557
+ min-height: 100px;
1558
+ font-size: 13px;
1559
+ }
1560
+
1561
+ .conference-hint {
1562
+ font-size: 0.85rem;
1563
+ }
1564
+
1565
+ .abstract-filter {
1566
+ padding: 15px;
1567
+ }
1568
+
1569
+ .starred-filter {
1570
+ padding: 15px;
1571
+ }
1572
+
1573
+ .checkbox-label {
1574
+ font-size: 0.9rem;
1575
+ }
1576
+
1577
+ .filter-info {
1578
+ font-size: 0.85rem;
1579
+ margin-left: 8px;
1580
+ }
1581
+
1582
+ .export-section {
1583
+ padding: 12px;
1584
+ margin-bottom: 15px;
1585
+ }
1586
+
1587
+ .export-container {
1588
+ flex-direction: column;
1589
+ align-items: stretch;
1590
+ gap: 10px;
1591
+ }
1592
+
1593
+ .export-title {
1594
+ font-size: 1rem;
1595
+ justify-content: center;
1596
+ }
1597
+
1598
+ .export-buttons {
1599
+ flex-direction: column;
1600
+ align-items: center;
1601
+ gap: 8px;
1602
+ justify-content: center;
1603
+ }
1604
+
1605
+ .export-btn {
1606
+ width: 100%;
1607
+ max-width: 200px;
1608
+ justify-content: center;
1609
+ padding: 10px 14px;
1610
+ }
1611
+
1612
+ .pagination-section {
1613
+ flex-direction: column;
1614
+ align-items: center;
1615
+ text-align: center;
1616
+ }
1617
+
1618
+ .pagination-controls {
1619
+ flex-wrap: wrap;
1620
+ justify-content: center;
1621
+ }
1622
+
1623
+ .page-input-container {
1624
+ margin: 10px 0;
1625
+ justify-content: center;
1626
+ }
1627
+
1628
+ .page-input {
1629
+ width: 50px;
1630
+ padding: 6px 8px;
1631
+ font-size: 0.85rem;
1632
+ }
1633
+
1634
+ .page-go-btn {
1635
+ padding: 6px 10px;
1636
+ font-size: 0.85rem;
1637
+ min-width: 32px;
1638
+ height: 32px;
1639
+ }
1640
+
1641
+ .paper-card {
1642
+ padding: 20px;
1643
+ margin-bottom: 20px;
1644
+ }
1645
+
1646
+ .paper-header {
1647
+ flex-direction: column;
1648
+ align-items: flex-start;
1649
+ gap: 10px;
1650
+ }
1651
+
1652
+ .paper-title {
1653
+ font-size: 1.1rem;
1654
+ margin-bottom: 8px;
1655
+ }
1656
+
1657
+ .star-button {
1658
+ align-self: flex-end;
1659
+ font-size: 1rem;
1660
+ min-width: 35px;
1661
+ min-height: 35px;
1662
+ }
1663
+
1664
+ .find-paper-btn {
1665
+ font-size: 1rem;
1666
+ min-width: 35px;
1667
+ min-height: 35px;
1668
+ }
1669
+
1670
+ .modal-content {
1671
+ margin: 10px;
1672
+ max-height: 90vh;
1673
+ }
1674
+
1675
+ .modal-header {
1676
+ flex-direction: column;
1677
+ align-items: flex-start;
1678
+ gap: 15px;
1679
+ padding: 20px;
1680
+ }
1681
+
1682
+ .modal-title-container {
1683
+ flex-direction: row;
1684
+ align-items: flex-start;
1685
+ gap: 10px;
1686
+ width: 100%;
1687
+ margin-right: 0;
1688
+ }
1689
+
1690
+ .modal-body {
1691
+ padding: 20px;
1692
+ max-height: calc(90vh - 140px);
1693
+ }
1694
+
1695
+ .modal-header h2 {
1696
+ font-size: 1.4rem;
1697
+ }
1698
+ }
1699
+
1700
+ @media (max-width: 480px) {
1701
+ .title {
1702
+ font-size: 1.8rem;
1703
+ }
1704
+
1705
+ .subtitle {
1706
+ font-size: 1rem;
1707
+ }
1708
+
1709
+ .search-section {
1710
+ gap: 10px;
1711
+ }
1712
+
1713
+ .search-stats {
1714
+ margin-bottom: 10px;
1715
+ font-size: 0.8rem;
1716
+ min-height: 16px;
1717
+ }
1718
+
1719
+ .search-input {
1720
+ padding: 12px 40px 12px 40px;
1721
+ font-size: 0.9rem;
1722
+ }
1723
+
1724
+ .search-field-select {
1725
+ font-size: 0.8rem;
1726
+ padding: 6px 8px;
1727
+ margin-right: 6px;
1728
+ }
1729
+
1730
+ .year-filter {
1731
+ gap: 10px;
1732
+ }
1733
+
1734
+ .filter-label {
1735
+ font-size: 0.9rem;
1736
+ flex-wrap: wrap;
1737
+ gap: 4px;
1738
+ }
1739
+
1740
+ .clear-year-btn {
1741
+ align-self: center;
1742
+ }
1743
+
1744
+ .conference-filter {
1745
+ padding: 10px;
1746
+ }
1747
+
1748
+ .conference-select {
1749
+ min-height: 80px;
1750
+ font-size: 12px;
1751
+ }
1752
+
1753
+ .conference-hint {
1754
+ font-size: 0.8rem;
1755
+ }
1756
+
1757
+ .clear-conference-btn {
1758
+ align-self: center;
1759
+ }
1760
+
1761
+ .abstract-filter {
1762
+ padding: 10px;
1763
+ }
1764
+
1765
+ .starred-filter {
1766
+ padding: 10px;
1767
+ }
1768
+
1769
+ .checkbox-label {
1770
+ font-size: 0.85rem;
1771
+ }
1772
+
1773
+ .filter-info {
1774
+ font-size: 0.8rem;
1775
+ margin-left: 6px;
1776
+ }
1777
+
1778
+ .export-section {
1779
+ padding: 10px;
1780
+ margin-bottom: 12px;
1781
+ }
1782
+
1783
+ .export-container {
1784
+ gap: 8px;
1785
+ }
1786
+
1787
+ .export-title {
1788
+ font-size: 0.9rem;
1789
+ justify-content: center;
1790
+ }
1791
+
1792
+ .export-btn {
1793
+ padding: 12px 16px;
1794
+ font-size: 0.9rem;
1795
+ margin-bottom: 10px;
1796
+ width: 100%;
1797
+ }
1798
+
1799
+ .paper-card {
1800
+ padding: 15px;
1801
+ }
1802
+
1803
+ .paper-meta {
1804
+ flex-direction: column;
1805
+ align-items: flex-start;
1806
+ gap: 10px;
1807
+ }
1808
+
1809
+ .paper-no-abstract-badge {
1810
+ font-size: 0.65rem;
1811
+ padding: 3px 6px;
1812
+ }
1813
+
1814
+ .modal-header {
1815
+ padding: 15px;
1816
+ }
1817
+
1818
+ .modal-title-container {
1819
+ gap: 8px;
1820
+ }
1821
+
1822
+ .modal-header h2 {
1823
+ font-size: 1.2rem;
1824
+ }
1825
+
1826
+ .modal-body {
1827
+ padding: 15px;
1828
+ }
1829
+
1830
+ .find-paper-btn {
1831
+ font-size: 0.9rem;
1832
+ min-width: 32px;
1833
+ min-height: 32px;
1834
+ }
1835
+ }
test.html ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Data File Test</title>
7
+ </head>
8
+ <body>
9
+ <h1>Data File Test</h1>
10
+ <div id="results"></div>
11
+
12
+ <script>
13
+ async function testFileLoading() {
14
+ const results = document.getElementById('results');
15
+
16
+ // Test conferences file
17
+ try {
18
+ console.log('Testing conferences file...');
19
+ const confResponse = await fetch('data/unique_conferences.json');
20
+ console.log('Conferences status:', confResponse.status);
21
+
22
+ if (confResponse.ok) {
23
+ const confData = await confResponse.json();
24
+ results.innerHTML += `<p>βœ… Conferences file loaded successfully. Found ${Object.keys(confData).length} conferences.</p>`;
25
+ } else {
26
+ results.innerHTML += `<p>❌ Conferences file failed to load. Status: ${confResponse.status}</p>`;
27
+ }
28
+ } catch (error) {
29
+ results.innerHTML += `<p>❌ Conferences file error: ${error.message}</p>`;
30
+ }
31
+
32
+ // Test BibTeX file
33
+ try {
34
+ console.log('Testing BibTeX file...');
35
+ const bibResponse = await fetch('data/multilingual_papers.bib');
36
+ console.log('BibTeX status:', bibResponse.status);
37
+
38
+ if (bibResponse.ok) {
39
+ const bibText = await bibResponse.text();
40
+ results.innerHTML += `<p>βœ… BibTeX file loaded successfully. Size: ${(bibText.length / 1024 / 1024).toFixed(2)} MB</p>`;
41
+
42
+ // Count entries
43
+ const entries = bibText.split(/(?=@)/);
44
+ results.innerHTML += `<p>πŸ“Š Found ${entries.length} potential BibTeX entries</p>`;
45
+ } else {
46
+ results.innerHTML += `<p>❌ BibTeX file failed to load. Status: ${bibResponse.status}</p>`;
47
+ }
48
+ } catch (error) {
49
+ results.innerHTML += `<p>❌ BibTeX file error: ${error.message}</p>`;
50
+ }
51
+ }
52
+
53
+ // Run test when page loads
54
+ window.addEventListener('load', testFileLoading);
55
+ </script>
56
+ </body>
57
+ </html>