feat: initial commit - oncology literature search platform
CI / backend (push) Canceled after 0s
CI / frontend (push) Canceled after 0s

OncoLit: a multi-tenant oncology literature search, feed, and
collaboration platform. Built with FastAPI + Vue 3 + PostgreSQL.
Includes PubMed pipeline, drug approvals, AI summaries, and
systematic review tools.
This commit is contained in:
34047007@qq.com
2026-07-27 07:59:18 +08:00
commit a6cd99a4ca
473 changed files with 151472 additions and 0 deletions
+299
View File
@@ -0,0 +1,299 @@
"""
Robust sky-btn fixer for Vue template NButton elements.
Processes ALL .vue files in frontend/src.
For every <NButton (including <n-button) in the template that:
- does NOT have the "text" prop
- does NOT already have "sky-btn" in its class attribute
- does NOT use :class
Adds class="sky-btn" (or appends " sky-btn" to existing class).
IMPORTANT: Does NOT match NButton inside quoted attribute values (fixes v1's bug).
Also fixes any files corrupted by v1's buggy run.
"""
import re
import os
import glob
def find_template_section(content):
"""Find the <template>...</template> section in a .vue file.
Uses rfind('</template>') to find the LAST closing tag, so nested
<template #header>...</template> inside the main template are handled
correctly (the main template always encompasses all nested blocks).
"""
template_start = content.find('<template>')
if template_start < 0:
# Try <template lang="...">, <template v-if="..." etc.
m = re.search(r'<template\b[^>]*>', content)
if not m:
return None, None
template_start = m.start()
# Use rfind to get the LAST </template> which closes the main template block
template_end = content.rfind('</template>')
if template_end < 0:
return None, None
return template_start, template_end + len('</template>')
def find_all_nbutton_tags(text, start_pos=0):
"""
Find all <NButton or <n-button opening tags in text, EXCLUDING those
inside quoted attribute values.
Returns list of (match_start, match_end, tag_text) for opening tags.
"""
# First, let's strip out everything inside quoted strings to avoid
# matching NButton inside attribute values
# We'll build a map of which positions are "real" vs inside quotes
inside_double_quote = False
inside_single_quote = False
positions = {} # pos -> True if real (not in quotes)
i = start_pos
while i < len(text):
ch = text[i]
if ch == '"' and not inside_single_quote:
inside_double_quote = not inside_double_quote
elif ch == "'" and not inside_double_quote:
inside_single_quote = not inside_single_quote
elif ch == '\\' and i + 1 < len(text):
# Skip escaped characters
positions[i] = not (inside_double_quote or inside_single_quote)
i += 2
continue
positions[i] = not (inside_double_quote or inside_single_quote)
i += 1
# Now find all NButton opening tags with proper regex
# Only consider positions where the '<' is not inside quotes
tag_pattern = re.compile(r'<[Nn][-]?[Bb]utton\b', re.DOTALL)
results = []
for match in tag_pattern.finditer(text, start_pos):
tag_start = match.start()
# Skip if the '<' is inside quotes
if tag_start in positions and not positions[tag_start]:
continue
# Now find the end of this opening tag (the '>' that closes it)
# Need to handle nested quotes within the tag
pos = match.end()
in_dq = False
in_sq = False
while pos < len(text):
ch = text[pos]
if ch == '"' and not in_sq:
in_dq = not in_dq
elif ch == "'" and not in_dq:
in_sq = not in_sq
elif ch == '>' and not in_dq and not in_sq:
tag_end = pos + 1
tag_text = text[tag_start:tag_end]
results.append((tag_start, tag_end, tag_text))
break
pos += 1
return results
def has_text_prop(tag_text):
"""Check if the NButton tag has 'text' as a boolean prop (not :text)."""
# Remove all quoted strings
stripped = re.sub(r'"[^"]*"', '', tag_text)
stripped = re.sub(r"'[^']*'", '', stripped)
# Look for 'text' as a standalone word not preceded by ':'
for m in re.finditer(r'\btext\b', stripped):
if m.start() > 0 and stripped[m.start()-1] == ':':
continue # :text, skip
# Must be preceded by whitespace or start of tag
pre_char = stripped[m.start()-1] if m.start() > 0 else ' '
if pre_char in (' ', '\t', '\n', '\r', '>'):
return True
return False
def has_class_binding(tag_text):
"""Check if the NButton has :class."""
# Remove quoted strings first
stripped = re.sub(r'"[^"]*"', '', tag_text)
stripped = re.sub(r"'[^']*'", '', stripped)
if re.search(r':class\b', stripped):
return True
# Also check for v-bind:class
if re.search(r'v-bind:class\b', stripped):
return True
return False
def has_skybtn(tag_text):
"""Check if the NButton already has sky-btn in class."""
return 'sky-btn' in tag_text
def modify_nbutton_tag(tag_text):
"""Add class="sky-btn" to an NButton tag. Returns modified tag or None."""
# Rules
if has_text_prop(tag_text):
return None
if has_class_binding(tag_text):
return None
if has_skybtn(tag_text):
return None
# Check if it has class="..."
class_match = re.search(r'class\s*=\s*"([^"]*)"', tag_text)
if class_match:
existing = class_match.group(1)
if 'sky-btn' in existing:
return None
new_class = existing + ' sky-btn' if existing.strip() else 'sky-btn'
result = tag_text[:class_match.start(1)] + new_class + tag_text[class_match.end(1):]
return result
# No class attribute - add class="sky-btn"
# Insert before the closing >
if tag_text.endswith('/>') or tag_text.endswith('/>'):
return tag_text[:-2] + ' class="sky-btn" />'
elif tag_text.endswith('>'):
return tag_text[:-1] + ' class="sky-btn">'
return None
def fix_corrupted_file(content):
"""
Fix files corrupted by v1 of the fixer.
v1's bug: it matched <NButton inside quoted attribute values like
placeholder="...<NButton..." and inserted class="sky-btn" there,
breaking the HTML.
We detect this pattern and fix it by removing the misplaced class.
Also fixes cases where NButton tags were mangled together with other tags.
"""
# Pattern 1: NButton class="sky-btn" inside an attribute value
# e.g., placeholder="研究目的(<NButton ... class="sky-btn">...="createReview">
# Fix: remove the corrupted inner NButton artifacts
# Pattern 2: Duplicate/split attributes from v1 mangling
# e.g., class="sky-btn">freshCitations"> should be class="sky-btn">
# and the rest should be proper attributes
original = content
# Fix pattern: something="text...<NButton...class="sky-btn">...some garbage>"
# This happens when v1 matched NButton inside a placeholder or other attribute
pattern1 = re.compile(
r'(placeholder|title|description|label|help-text|filter)\s*=\s*"([^"]*?)(<[Nn][-]?[Bb]utton\b[^>]*?class\s*=\s*"sky-btn"[^>]*>)([^"]*?)"',
re.DOTALL
)
content = pattern1.sub(r'\1="\2sky-btn-placeholder-fix\4"', content)
# Fix pattern: "<NButton ...class="sky-btn">n size="..." ...>"
# This happens when v1 mangled a tag name prefix
pattern2 = re.compile(
r'(<[Nn][-]?[Bb]utton\b[^>]*?class\s*=\s*"sky-btn">)\s*([a-zA-Z]+)\s*=\s*"',
re.DOTALL
)
# Remove attribute fragments that somehow got after the closing >
content = pattern2.sub(r'\1 ', content)
# Fix pattern: "<NButton ... class="sky-btn">..." (where "..." is a truncated/mangled attribute)
pattern3 = re.compile(
r'(<[Nn][-]?[Bb]utton\b[^>]*?class\s*=\s*"sky-btn"[^>]*>)([a-zA-Z-]+)\s*=\s*"',
re.DOTALL
)
content = pattern3.sub(r'\1', content)
return content
def process_file(filepath):
"""Process a single .vue file."""
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
original = content
# First, try to fix any corruption from v1
content = fix_corrupted_file(content)
# Find template section
template_start, template_end = find_template_section(content)
if template_start is None:
return 0, "no <template> found"
template_text = content[template_start:template_end]
# Find all NButton tags in template
nbutton_tags = find_all_nbutton_tags(template_text)
if not nbutton_tags:
return 0, "no NButton found"
changes = 0
# Process in reverse order to preserve positions
for tag_start, tag_end, tag_text in reversed(nbutton_tags):
modified = modify_nbutton_tag(tag_text)
if modified:
abs_start = template_start + tag_start
abs_end = template_start + tag_end
content = content[:abs_start] + modified + content[abs_end:]
changes += 1
if changes > 0 or content != original:
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
return changes, "ok"
def main():
frontend_src = 'd:/ClaudeCode/frontend/src'
# Find all .vue files
vue_files = []
for root, dirs, files in os.walk(frontend_src):
for f in files:
if f.endswith('.vue'):
vue_files.append(os.path.join(root, f))
vue_files.sort()
total_changes = 0
file_results = []
all_files_with_nbutton = []
for filepath in vue_files:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
if not re.search(r'<[Nn][-]?[Bb]utton\b', content):
continue
all_files_with_nbutton.append(filepath)
changes, status = process_file(filepath)
if changes > 0:
rel_path = os.path.relpath(filepath, frontend_src)
file_results.append((rel_path, changes))
total_changes += changes
print(f" MODIFIED: {rel_path} ({changes} change(s))")
print(f"\n=== Summary ===")
print(f"Files with NButton: {len(all_files_with_nbutton)}")
print(f"Files modified: {len(file_results)}")
print(f"Total NButton tags modified: {total_changes}")
if not file_results:
print("\nNO_CHANGES")
if __name__ == '__main__':
main()