Improved modapp
This commit is contained in:
139
app.py
139
app.py
@@ -1,11 +1,15 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, flash, abort, make_response, session
|
||||
from flask import Flask, render_template, request, redirect, url_for, flash, abort, make_response, session, jsonify
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_limiter import Limiter
|
||||
from flask_limiter.util import get_remote_address
|
||||
from flask_cors import CORS
|
||||
import datetime
|
||||
import json
|
||||
import random
|
||||
from argon2 import PasswordHasher
|
||||
from argon2.exceptions import VerifyMismatchError
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///quotes.db'
|
||||
app.config['SECRET_KEY'] = 'your_secret_key' # Use environment variable in production
|
||||
@@ -21,13 +25,18 @@ ADMIN_CREDENTIALS = {
|
||||
'password': '$argon2i$v=19$m=65536,t=4,p=1$cWZDc1pQaUJLTUJoaVI4cw$kn8XKz6AEZi8ebXfyyZuzommSypliVFrsGqzOyUEIHA' # Example hash
|
||||
}
|
||||
|
||||
# Define the Quote model
|
||||
# Define the Quote modelclass Quote(db.Model):
|
||||
class Quote(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
text = db.Column(db.Text, nullable=False)
|
||||
votes = db.Column(db.Integer, default=0)
|
||||
date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
|
||||
status = db.Column(db.Integer, default=0) # 0 = pending, 1 = approved, 2 = rejected
|
||||
ip_address = db.Column(db.String(45)) # Store IPv4 and IPv6 addresses
|
||||
user_agent = db.Column(db.String(255)) # Store user-agent strings
|
||||
submitted_at = db.Column(db.DateTime, default=datetime.datetime.utcnow)
|
||||
|
||||
|
||||
|
||||
# Home route to display quotes
|
||||
# Home route to display quotes
|
||||
@@ -51,7 +60,11 @@ def submit():
|
||||
flash("Quote cannot be empty.", 'error')
|
||||
return redirect(url_for('submit'))
|
||||
|
||||
new_quote = Quote(text=quote_text)
|
||||
ip_address = request.remote_addr # Get the user's IP address
|
||||
user_agent = request.headers.get('User-Agent') # Get the user's browser info
|
||||
|
||||
new_quote = Quote(text=quote_text, ip_address=ip_address, user_agent=user_agent)
|
||||
|
||||
try:
|
||||
db.session.add(new_quote)
|
||||
db.session.commit()
|
||||
@@ -68,7 +81,6 @@ def submit():
|
||||
|
||||
return render_template('submit.html', approved_count=approved_count, pending_count=pending_count)
|
||||
|
||||
|
||||
# Route to handle voting (upvote/downvote)
|
||||
@app.route('/vote/<int:id>/<action>')
|
||||
def vote(id, action):
|
||||
@@ -151,6 +163,10 @@ def quote():
|
||||
quote = Quote.query.get_or_404(quote_id)
|
||||
return render_template('quote.html', quote=quote)
|
||||
|
||||
@app.route('/faq')
|
||||
def faq():
|
||||
return render_template('faq.html')
|
||||
|
||||
# Admin login route
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
@@ -178,11 +194,11 @@ def modapp():
|
||||
if not session.get('admin'):
|
||||
flash('You need to log in first.', 'danger')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
# Fetch all quotes (pending, approved, and rejected)
|
||||
all_quotes = Quote.query.order_by(Quote.date.desc()).all()
|
||||
|
||||
return render_template('modapp.html', all_quotes=all_quotes)
|
||||
all_quotes = Quote.query.order_by(Quote.date.desc()).all()
|
||||
total_quotes = Quote.query.filter_by(status=1).count() # Count only approved quotes
|
||||
|
||||
return render_template('modapp.html', all_quotes=all_quotes, total_quotes=total_quotes)
|
||||
|
||||
@app.route('/modapp/bulk_action', methods=['POST'])
|
||||
def bulk_action():
|
||||
@@ -298,6 +314,113 @@ def logout():
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
|
||||
# Initialize rate limiter and CORS for cross-origin API access
|
||||
limiter = Limiter(app, key_func=get_remote_address)
|
||||
CORS(app)
|
||||
|
||||
# API to get all approved quotes
|
||||
@app.route('/api/quotes', methods=['GET'])
|
||||
def get_all_quotes():
|
||||
quotes = Quote.query.filter_by(status=1).all() # Only approved quotes
|
||||
quote_list = [{
|
||||
'id': quote.id,
|
||||
'text': quote.text,
|
||||
'votes': quote.votes,
|
||||
'date': quote.date.strftime('%Y-%m-%d')
|
||||
} for quote in quotes]
|
||||
|
||||
return jsonify(quote_list), 200
|
||||
|
||||
# API to get a specific quote by ID
|
||||
@app.route('/api/quotes/<int:id>', methods=['GET'])
|
||||
def get_quote(id):
|
||||
quote = Quote.query.filter_by(id=id, status=1).first_or_404() # Only approved quotes
|
||||
quote_data = {
|
||||
'id': quote.id,
|
||||
'text': quote.text,
|
||||
'votes': quote.votes,
|
||||
'date': quote.date.strftime('%Y-%m-%d')
|
||||
}
|
||||
return jsonify(quote_data), 200
|
||||
|
||||
# API to get a random approved quote
|
||||
@app.route('/api/random', methods=['GET'])
|
||||
def get_random_quote():
|
||||
count = Quote.query.filter_by(status=1).count()
|
||||
if count == 0:
|
||||
return jsonify({"error": "No approved quotes available"}), 404
|
||||
|
||||
random_offset = random.randint(0, count - 1)
|
||||
random_quote = Quote.query.filter_by(status=1).offset(random_offset).first()
|
||||
|
||||
quote_data = {
|
||||
'id': random_quote.id,
|
||||
'text': random_quote.text,
|
||||
'votes': random_quote.votes,
|
||||
'date': random_quote.date.strftime('%Y-%m-%d')
|
||||
}
|
||||
return jsonify(quote_data), 200
|
||||
|
||||
# API to get the top quotes by vote count
|
||||
@app.route('/api/top', methods=['GET'])
|
||||
def get_top_quotes():
|
||||
top_quotes = Quote.query.filter_by(status=1).order_by(Quote.votes.desc()).limit(10).all() # Limit to top 10
|
||||
quote_list = [{
|
||||
'id': quote.id,
|
||||
'text': quote.text,
|
||||
'votes': quote.votes,
|
||||
'date': quote.date.strftime('%Y-%m-%d')
|
||||
} for quote in top_quotes]
|
||||
|
||||
return jsonify(quote_list), 200
|
||||
|
||||
# API to search for quotes
|
||||
@app.route('/api/search', methods=['GET'])
|
||||
def search_quotes():
|
||||
query = request.args.get('q', '').strip()
|
||||
if not query:
|
||||
return jsonify({"error": "No search term provided"}), 400
|
||||
|
||||
quotes = Quote.query.filter(Quote.text.ilike(f'%{query}%'), Quote.status == 1).all() # Search in approved quotes
|
||||
if not quotes:
|
||||
return jsonify({"error": "No quotes found for search term"}), 404
|
||||
|
||||
quote_list = [{
|
||||
'id': quote.id,
|
||||
'text': quote.text,
|
||||
'votes': quote.votes,
|
||||
'date': quote.date.strftime('%Y-%m-%d')
|
||||
} for quote in quotes]
|
||||
|
||||
return jsonify(quote_list), 200
|
||||
|
||||
# API to submit a new quote
|
||||
@app.route('/api/submit', methods=['POST'])
|
||||
@limiter.limit("5 per minute") # Rate limiting to prevent abuse
|
||||
def submit_quote():
|
||||
data = request.get_json()
|
||||
|
||||
# Validate the input
|
||||
if not data or not data.get('text'):
|
||||
return jsonify({"error": "Quote text is required"}), 400
|
||||
|
||||
quote_text = data.get('text').strip()
|
||||
|
||||
# Basic validation to prevent spam
|
||||
if len(quote_text) < 5 or len(quote_text) > 1000:
|
||||
return jsonify({"error": "Quote must be between 5 and 1000 characters"}), 400
|
||||
|
||||
new_quote = Quote(text=quote_text)
|
||||
|
||||
try:
|
||||
db.session.add(new_quote)
|
||||
db.session.commit()
|
||||
return jsonify({"success": "Quote submitted successfully!", "id": new_quote.id}), 201
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({"error": f"Error submitting quote: {str(e)}"}), 500
|
||||
|
||||
# Run the Flask app
|
||||
if __name__ == '__main__':
|
||||
app.run(host='127.0.0.1', port=5050, debug=True)
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
flask_sqlalchemy
|
||||
argon2-cffi
|
||||
Flask==2.3.2
|
||||
Flask-SQLAlchemy==3.0.5
|
||||
Flask-Limiter==2.4
|
||||
Flask-CORS==3.0.10
|
||||
argon2-cffi==21.3.0
|
||||
|
||||
@@ -5,12 +5,45 @@ body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
/* Dark Mode */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #121212;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #ffa500;
|
||||
}
|
||||
|
||||
table, td, input.text, textarea, select {
|
||||
background-color: #1e1e1e;
|
||||
color: #ffffff;
|
||||
border-color: #ffa500;
|
||||
}
|
||||
|
||||
input.button {
|
||||
background-color: #ffa500;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
input.button:hover {
|
||||
background-color: #ff8c00;
|
||||
}
|
||||
|
||||
.footertext, .toplinks, h2 {
|
||||
color: #ffa500;
|
||||
}
|
||||
}
|
||||
|
||||
/* Links */
|
||||
a {
|
||||
color: #c08000;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
a:visited, a:link, a:hover {
|
||||
@@ -78,6 +111,7 @@ input.text, textarea, select {
|
||||
padding: 5px;
|
||||
border: 1px solid #c08000;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
input.button {
|
||||
@@ -86,6 +120,7 @@ input.button {
|
||||
padding: 5px 10px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
input.button:hover {
|
||||
@@ -141,6 +176,7 @@ footer {
|
||||
border: 1px solid #c08000;
|
||||
margin: 0 5px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
#pagination a:hover {
|
||||
@@ -167,3 +203,29 @@ footer {
|
||||
.quote-id:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Dark Mode Specific */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
td.quote-box {
|
||||
border-color: #ffa500;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: #ffa500;
|
||||
color: #121212;
|
||||
}
|
||||
|
||||
#pagination a {
|
||||
border-color: #ffa500;
|
||||
color: #ffa500;
|
||||
}
|
||||
|
||||
#pagination a:hover {
|
||||
background-color: #333333;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.quote-id {
|
||||
color: #ffa500;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@
|
||||
<a href="/random">Random</a> /
|
||||
<a href="/submit">Submit</a> /
|
||||
<a href="/browse">Browse</a> /
|
||||
<a href="/modapp">ModApp</a>
|
||||
<a href="/modapp">ModApp</a> /
|
||||
<a href="/search">Search</a>
|
||||
<a href="/faq">FAQ</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -79,6 +81,9 @@
|
||||
<a href="/random">Random</a> /
|
||||
<a href="/submit">Submit</a> /
|
||||
<a href="/browse">Browse</a> /
|
||||
<a href="/modapp">ModApp</a> /
|
||||
<a href="/search">Search</a>
|
||||
<a href="/faq">FAQ</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
123
templates/faq.html
Normal file
123
templates/faq.html
Normal file
@@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ircquotes: FAQ</title>
|
||||
<link rel="stylesheet" href="/static/css/styles.css">
|
||||
</head>
|
||||
<body bgcolor="#ffffff" text="#000000" link="#c08000" vlink="#c08000" alink="#c08000">
|
||||
|
||||
<!-- Navigation -->
|
||||
<center>
|
||||
<table cellpadding="2" cellspacing="0" width="80%" border="0">
|
||||
<tr>
|
||||
<td bgcolor="#c08000" align="left">
|
||||
<font size="+1"><b><i>ircquotes</i></b></font>
|
||||
</td>
|
||||
<td bgcolor="#c08000" align="right">
|
||||
<font face="arial" size="+1"><b>FAQ</b></font>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table cellpadding="2" cellspacing="0" width="80%" border="0">
|
||||
<tr>
|
||||
<td class="footertext" align="left" bgcolor="#f0f0f0"></td>
|
||||
<td align="right" bgcolor="#f0f0f0" class="toplinks" colspan="2">
|
||||
<a href="/">Home</a> /
|
||||
<a href="/random">Random</a> /
|
||||
<a href="/submit">Submit</a> /
|
||||
<a href="/browse">Browse</a> /
|
||||
<a href="/modapp">ModApp</a> /
|
||||
<a href="/search">Search</a>
|
||||
<a href="/faq">FAQ</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
|
||||
<!-- FAQ Content -->
|
||||
<center>
|
||||
<table cellpadding="2" cellspacing="0" width="80%">
|
||||
<tr>
|
||||
<td class="bodytext">
|
||||
<h2>Frequently Asked Questions (FAQ)</h2>
|
||||
|
||||
<h3>What is ircquotes?</h3>
|
||||
<p>ircquotes is a community-driven website where users can submit and browse memorable quotes from IRC (Internet Relay Chat). You can browse quotes, submit your own, and vote on others.</p>
|
||||
|
||||
<h3>How does the API work?</h3>
|
||||
<p>The ircquotes API allows users to retrieve quotes programmatically. It is designed for developers who want to integrate IRC quotes into their own applications.</p>
|
||||
|
||||
<h4>Available API Endpoints</h4>
|
||||
<ul>
|
||||
<li><strong>Get All Approved Quotes</strong>: <code>GET /api/quotes</code></li>
|
||||
<li><strong>Get a Specific Quote by ID</strong>: <code>GET /api/quotes/<id></code></li>
|
||||
<li><strong>Get a Random Quote</strong>: <code>GET /api/random</code></li>
|
||||
<li><strong>Get Top Quotes</strong>: <code>GET /api/top</code></li>
|
||||
<li><strong>Search Quotes</strong>: <code>GET /api/search?q=<search_term></code></li>
|
||||
</ul>
|
||||
|
||||
<h4>Submitting Quotes via the API</h4>
|
||||
<p>The API also allows you to submit quotes, but this feature is rate-limited to prevent abuse. Each user is allowed 5 submissions per minute.</p>
|
||||
<ul>
|
||||
<li><strong>Submit a Quote</strong>: <code>POST /api/submit</code></li>
|
||||
<li><strong>Request Body</strong>: The request body should be in JSON format and contain the quote text like this:</li>
|
||||
<pre><code>{
|
||||
"text": "This is a memorable quote!"
|
||||
}</code></pre>
|
||||
<li><strong>Validation Rules</strong>: Quotes must be between 5 and 1000 characters.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Rules for Submitting Quotes</h3>
|
||||
<p>To ensure that ircquotes remains a fun and enjoyable platform for everyone, we ask that you follow a few simple rules when submitting quotes:</p>
|
||||
<ul>
|
||||
<li><strong>No Offensive Content</strong>: Do not submit quotes that contain offensive language, hate speech, or other harmful content.</li>
|
||||
<li><strong>No Spam</strong>: Please avoid submitting irrelevant or repetitive content. The site is for memorable IRC quotes, not spam.</li>
|
||||
<li><strong>Stay On-Topic</strong>: Ensure that your submissions are actual quotes from IRC, not made-up content.</li>
|
||||
<li><strong>Rate Limiting</strong>: The submission rate is limited to 5 quotes per minute to prevent spam.</li>
|
||||
<li><strong>Moderation</strong>: All quotes are subject to approval by site moderators. Rejected quotes will not be publicly visible.</li>
|
||||
</ul>
|
||||
|
||||
<h3>How are quotes moderated?</h3>
|
||||
<p>Quotes go through a moderation process where they are either approved, rejected, or deleted. Approved quotes become visible to all users, while rejected ones remain in the system but aren't shown publicly.</p>
|
||||
|
||||
<h3>How can I get the top 100 quotes?</h3>
|
||||
<p>You can view the top 100 quotes by visiting the <a href="/top">Top 100 page</a>. The quotes are ranked by votes, with the highest-voted quotes appearing at the top.</p>
|
||||
|
||||
<h3>How do I search for quotes?</h3>
|
||||
<p>Use the search bar on the site or the API endpoint <code>/api/search?q=<search_term></code> to find specific quotes.</p>
|
||||
|
||||
<h3>Can I delete my submitted quotes?</h3>
|
||||
<p>No, quotes are submitted anonymously, so there's no direct way to delete quotes after submission. However, site moderators can remove inappropriate content.</p>
|
||||
|
||||
<h3>Who can I contact for help?</h3>
|
||||
<p>If you encounter any issues or have questions, feel free to reach out to the admin via the contact page or submit a help request through the site's interface.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
|
||||
<!-- Footer -->
|
||||
<center>
|
||||
<table border="0" cellpadding="2" cellspacing="0" width="80%" bgcolor="#c08000">
|
||||
<tr>
|
||||
<td bgcolor="#f0f0f0" class="toplinks" colspan="2">
|
||||
<a href="/">Home</a> /
|
||||
<a href="/random">Random</a> /
|
||||
<a href="/submit">Submit</a> /
|
||||
<a href="/browse">Browse</a> /
|
||||
<a href="/modapp">ModApp</a> /
|
||||
<a href="/search">Search</a>
|
||||
<a href="/faq">FAQ</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="footertext" align="left"> </td>
|
||||
<td class="footertext" align="right">{{ total_quotes }} quotes approved</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -33,8 +33,8 @@
|
||||
<a href="/submit">Submit</a> /
|
||||
<a href="/browse">Browse</a> /
|
||||
<a href="/modapp">ModApp</a> /
|
||||
<!-- This link will now load search.html -->
|
||||
<a href="/search">Search</a>
|
||||
<a href="/faq">FAQ</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<a href="/browse">Browse</a> /
|
||||
<a href="/modapp">ModApp</a> /
|
||||
<a href="/search">Search</a>
|
||||
<a href="/faq">FAQ</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -68,6 +69,9 @@
|
||||
<a href="/random">Random</a> /
|
||||
<a href="/submit">Submit</a> /
|
||||
<a href="/browse">Browse</a> /
|
||||
<a href="/modapp">ModApp</a> /
|
||||
<a href="/search">Search</a>
|
||||
<a href="/faq">FAQ</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -40,22 +40,26 @@
|
||||
<tr>
|
||||
<td class="bodytext">
|
||||
<h2>All Quotes for Moderation</h2>
|
||||
|
||||
|
||||
<!-- Check if there are any quotes -->
|
||||
{% if all_quotes %}
|
||||
<form method="POST" action="/modapp/bulk_action">
|
||||
<table>
|
||||
<table border="1" cellpadding="5" cellspacing="0" width="100%">
|
||||
<tr>
|
||||
<th>Select</th>
|
||||
<th>Quote ID</th>
|
||||
<th>Quote</th>
|
||||
<th>Status</th>
|
||||
<th>Submitted At</th>
|
||||
<th>IP Address</th>
|
||||
<th>User Agent</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
<!-- Loop through each quote -->
|
||||
{% for quote in all_quotes %}
|
||||
<tr style="background-color: '{% if quote.status == 1 %}#d4edda{% elif quote.status == 2 %}#f8d7da{% else %}#fff{% endif %}'">
|
||||
<td><input type="checkbox" name="quote_ids" value="{{ quote.id }}"></td>
|
||||
<tr style="background-color:
|
||||
{% if quote.status == 1 %} #d4edda {% elif quote.status == 2 %} #f8d7da {% else %} #fff {% endif %}">
|
||||
<td><input type="checkbox" name="quote_ids[]" value="{{ quote.id }}"></td>
|
||||
<td>#{{ quote.id }}</td>
|
||||
<td>{{ quote.text }}</td>
|
||||
<td>
|
||||
@@ -67,6 +71,15 @@
|
||||
Rejected
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if quote.submitted_at %}
|
||||
{{ quote.submitted_at.strftime('%Y-%m-%d %H:%M:%S') }}
|
||||
{% else %}
|
||||
N/A
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ quote.ip_address }}</td>
|
||||
<td>{{ quote.user_agent }}</td>
|
||||
<td>
|
||||
<a href="/approve/{{ quote.id }}">Approve</a> |
|
||||
<a href="/reject/{{ quote.id }}">Reject</a> |
|
||||
@@ -109,5 +122,11 @@
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
|
||||
<!-- Dark Mode Toggle -->
|
||||
<center>
|
||||
<button id="theme-toggle">Toggle Dark Mode</button>
|
||||
</center>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -32,7 +32,9 @@
|
||||
<a href="/random">Random</a> /
|
||||
<a href="/submit">Submit</a> /
|
||||
<a href="/browse">Browse</a> /
|
||||
<a href="/top">Top 100</a>
|
||||
<a href="/modapp">ModApp</a> /
|
||||
<a href="/search">Search</a>
|
||||
<a href="/faq">FAQ</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -26,10 +26,12 @@
|
||||
<td class="footertext" align="left" bgcolor="#f0f0f0"></td>
|
||||
<td align="right" bgcolor="#f0f0f0" class="toplinks" colspan="2">
|
||||
<a href="/">Home</a> /
|
||||
<a href="/?latest">Latest</a> /
|
||||
<a href="/?browse">Browse</a> /
|
||||
<a href="/random" rel="nofollow">Random</a> /
|
||||
<a href="/?top">Top 100</a>
|
||||
<a href="/random">Random</a> /
|
||||
<a href="/submit">Submit</a> /
|
||||
<a href="/browse">Browse</a> /
|
||||
<a href="/modapp">ModApp</a> /
|
||||
<a href="/search">Search</a>
|
||||
<a href="/faq">FAQ</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -57,10 +59,12 @@
|
||||
<tr>
|
||||
<td bgcolor="#f0f0f0" class="toplinks" colspan="2">
|
||||
<a href="/">Home</a> /
|
||||
<a href="/?latest">Latest</a> /
|
||||
<a href="/?browse">Browse</a> /
|
||||
<a href="/random" rel="nofollow">Random</a> /
|
||||
<a href="/?top">Top 100</a>
|
||||
<a href="/random">Random</a> /
|
||||
<a href="/submit">Submit</a> /
|
||||
<a href="/browse">Browse</a> /
|
||||
<a href="/modapp">ModApp</a> /
|
||||
<a href="/search">Search</a>
|
||||
<a href="/faq">FAQ</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<a href="/browse">Browse</a> /
|
||||
<a href="/modapp">ModApp</a> /
|
||||
<a href="/search">Search</a>
|
||||
<a href="/faq">FAQ</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -83,6 +84,9 @@
|
||||
<a href="/random">Random</a> /
|
||||
<a href="/submit">Submit</a> /
|
||||
<a href="/browse">Browse</a> /
|
||||
<a href="/modapp">ModApp</a> /
|
||||
<a href="/search">Search</a>
|
||||
<a href="/faq">FAQ</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -29,9 +29,11 @@
|
||||
<td align="right" bgcolor="#f0f0f0" class="toplinks" colspan="2">
|
||||
<a href="/">Home</a> /
|
||||
<a href="/random">Random</a> /
|
||||
<a href="/submit"><b>Add Quote</b></a> /
|
||||
<a href="/submit">Submit</a> /
|
||||
<a href="/browse">Browse</a> /
|
||||
<a href="/modapp">ModApp</a> /
|
||||
<a href="/search">Search</a>
|
||||
<a href="/faq">FAQ</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user