<modelVersion>4.0.0</modelVersion>
<groupId>net.pterodactylus</groupId>
<artifactId>sone</artifactId>
- <version>0.3.6-4</version>
+ <version>0.3.7</version>
<dependencies>
<dependency>
<groupId>net.pterodactylus</groupId>
<artifactId>utils</artifactId>
- <version>0.7.5</version>
+ <version>0.7.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
import net.pterodactylus.sone.data.Client;
import net.pterodactylus.sone.data.Post;
import net.pterodactylus.sone.data.Profile;
+import net.pterodactylus.sone.data.Profile.Field;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.freenet.wot.Identity;
import net.pterodactylus.util.logging.Logging;
import net.pterodactylus.util.number.Numbers;
import net.pterodactylus.util.validation.Validation;
+import net.pterodactylus.util.version.Version;
import freenet.keys.FreenetURI;
/**
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class Core implements IdentityListener {
+public class Core implements IdentityListener, UpdateListener {
/**
* Enumeration for the possible states of a {@link Sone}.
/** The Sone downloader. */
private final SoneDownloader soneDownloader;
+ /** The update checker. */
+ private final UpdateChecker updateChecker;
+
/** Whether the core has been stopped. */
private volatile boolean stopped;
this.freenetInterface = freenetInterface;
this.identityManager = identityManager;
this.soneDownloader = new SoneDownloader(this, freenetInterface);
+ this.updateChecker = new UpdateChecker(freenetInterface);
}
//
}
/**
+ * Returns the update checker.
+ *
+ * @return The update checker
+ */
+ public UpdateChecker getUpdateChecker() {
+ return updateChecker;
+ }
+
+ /**
* Returns the status of the given Sone.
*
* @param sone
profile.setBirthMonth(configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").getValue(null));
profile.setBirthYear(configuration.getIntValue(sonePrefix + "/Profile/BirthYear").getValue(null));
+ /* load profile fields. */
+ while (true) {
+ String fieldPrefix = sonePrefix + "/Profile/Fields/" + profile.getFields().size();
+ String fieldName = configuration.getStringValue(fieldPrefix + "/Name").getValue(null);
+ if (fieldName == null) {
+ break;
+ }
+ String fieldValue = configuration.getStringValue(fieldPrefix + "/Value").getValue("");
+ profile.addField(fieldName).setValue(fieldValue);
+ }
+
/* load posts. */
Set<Post> posts = new HashSet<Post>();
while (true) {
configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").setValue(profile.getBirthMonth());
configuration.getIntValue(sonePrefix + "/Profile/BirthYear").setValue(profile.getBirthYear());
+ /* save profile fields. */
+ int fieldCounter = 0;
+ for (Field profileField : profile.getFields()) {
+ String fieldPrefix = sonePrefix + "/Profile/Fields/" + fieldCounter++;
+ configuration.getStringValue(fieldPrefix + "/Name").setValue(profileField.getName());
+ configuration.getStringValue(fieldPrefix + "/Value").setValue(profileField.getValue());
+ }
+ configuration.getStringValue(sonePrefix + "/Profile/Fields/" + fieldCounter + "/Name").setValue(null);
+
/* save posts. */
int postCounter = 0;
for (Post post : sone.getPosts()) {
*/
public void start() {
loadConfiguration();
+ updateChecker.addUpdateListener(this);
+ updateChecker.start();
}
/**
soneInserter.stop();
}
}
+ updateChecker.stop();
+ updateChecker.removeUpdateListener(this);
soneDownloader.stop();
saveConfiguration();
stopped = true;
/* store the options first. */
try {
+ configuration.getIntValue("Option/ConfigurationVersion").setValue(0);
configuration.getIntValue("Option/InsertionDelay").setValue(options.getIntegerOption("InsertionDelay").getReal());
configuration.getIntValue("Option/PositiveTrust").setValue(options.getIntegerOption("PositiveTrust").getReal());
configuration.getIntValue("Option/NegativeTrust").setValue(options.getIntegerOption("NegativeTrust").getReal());
trustedIdentities.get(ownIdentity).remove(identity);
}
+ //
+ // INTERFACE UpdateListener
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void updateFound(Version version, long releaseTime) {
+ coreListenerManager.fireUpdateFound(version, releaseTime);
+ }
+
}
import net.pterodactylus.sone.data.Post;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.util.version.Version;
/**
* Listener interface for objects that want to be notified on certain
*/
public void soneUnlocked(Sone sone);
+ /**
+ * Notifies a listener that a new version has been found.
+ *
+ * @param version
+ * The version that was found
+ * @param releaseTime
+ * The release time of the new version
+ */
+ public void updateFound(Version version, long releaseTime);
+
}
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.util.event.AbstractListenerManager;
+import net.pterodactylus.util.version.Version;
/**
* Manager for {@link CoreListener}s.
}
}
+ /**
+ * Notifies all listeners that a new version was found.
+ *
+ * @see CoreListener#updateFound(Version, long)
+ * @param version
+ * The new version
+ * @param releaseTime
+ * The release time of the new version
+ */
+ void fireUpdateFound(Version version, long releaseTime) {
+ for (CoreListener coreListener : getListeners()) {
+ coreListener.updateFound(version, releaseTime);
+ }
+ }
+
}
package net.pterodactylus.sone.core;
import java.net.MalformedURLException;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
/** The USK callbacks. */
private final Map<String, USKCallback> soneUskCallbacks = new HashMap<String, USKCallback>();
+ /** The not-Sone-related USK callbacks. */
+ private final Map<FreenetURI, USKCallback> uriUskCallbacks = Collections.synchronizedMap(new HashMap<FreenetURI, USKCallback>());
+
/**
* Creates a new Freenet interface.
*
}
}
+ /**
+ * Registers an arbitrary URI and calls the given callback if a new edition
+ * is found.
+ *
+ * @param uri
+ * The URI to watch
+ * @param callback
+ * The callback to call
+ */
+ public void registerUsk(FreenetURI uri, final Callback callback) {
+ USKCallback uskCallback = new USKCallback() {
+
+ @Override
+ public void onFoundEdition(long edition, USK key, ObjectContainer objectContainer, ClientContext clientContext, boolean metadata, short codec, byte[] data, boolean newKnownGood, boolean newSlotToo) {
+ callback.editionFound(key.getURI(), edition, newKnownGood, newSlotToo);
+ }
+
+ @Override
+ public short getPollingPriorityNormal() {
+ return RequestStarter.PREFETCH_PRIORITY_CLASS;
+ }
+
+ @Override
+ public short getPollingPriorityProgress() {
+ return RequestStarter.INTERACTIVE_PRIORITY_CLASS;
+ }
+
+ };
+ try {
+ node.clientCore.uskManager.subscribe(USK.create(uri), uskCallback, true, (HighLevelSimpleClientImpl) client);
+ uriUskCallbacks.put(uri, uskCallback);
+ } catch (MalformedURLException mue1) {
+ logger.log(Level.WARNING, "Could not subscribe to USK: " + uri, uri);
+ }
+ }
+
+ /**
+ * Unregisters the USK watcher for the given URI.
+ *
+ * @param uri
+ * The URI to unregister the USK watcher for
+ */
+ public void unregisterUsk(FreenetURI uri) {
+ USKCallback uskCallback = uriUskCallbacks.remove(uri);
+ if (uskCallback == null) {
+ logger.log(Level.INFO, "Could not unregister unknown USK: " + uri);
+ return;
+ }
+ try {
+ node.clientCore.uskManager.unsubscribe(USK.create(uri), uskCallback);
+ } catch (MalformedURLException mue1) {
+ logger.log(Level.INFO, "Could not unregister invalid USK: " + uri);
+ }
+ }
+
+ /**
+ * Callback for USK watcher events.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ public static interface Callback {
+
+ /**
+ * Notifies a listener that a new edition was found for a URI.
+ *
+ * @param uri
+ * The URI that a new edition was found for
+ * @param edition
+ * The found edition
+ * @param newKnownGood
+ * Whether the found edition was actually fetched
+ * @param newSlot
+ * Whether the found edition is higher than all previously
+ * found editions
+ */
+ public void editionFound(FreenetURI uri, long edition, boolean newKnownGood, boolean newSlot);
+
+ }
+
}
Profile profile = new Profile().setFirstName(profileFirstName).setMiddleName(profileMiddleName).setLastName(profileLastName);
profile.setBirthDay(profileBirthDay).setBirthMonth(profileBirthMonth).setBirthYear(profileBirthYear);
+ /* parse profile fields. */
+ SimpleXML profileFieldsXml = profileXml.getNode("fields");
+ if (profileFieldsXml != null) {
+ for (SimpleXML fieldXml : profileFieldsXml.getNodes("field")) {
+ String fieldName = fieldXml.getValue("field-name", null);
+ String fieldValue = fieldXml.getValue("field-value", null);
+ if ((fieldName == null) || (fieldValue == null)) {
+ logger.log(Level.WARNING, "Downloaded profile field for Sone %s with missing data! Name: %s, Value: %s", new Object[] { sone, fieldName, fieldValue });
+ return null;
+ }
+ try {
+ profile.addField(fieldName).setValue(fieldValue);
+ } catch (IllegalArgumentException iae1) {
+ logger.log(Level.WARNING, "Duplicate field: " + fieldName, iae1);
+ return null;
+ }
+ }
+ }
+
/* parse posts. */
SimpleXML postsXml = soneXml.getNode("posts");
Set<Post> posts = new HashSet<Post>();
--- /dev/null
+/*
+ * Sone - UpdateChecker.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.util.Date;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import net.pterodactylus.sone.main.SonePlugin;
+import net.pterodactylus.util.collection.Pair;
+import net.pterodactylus.util.io.Closer;
+import net.pterodactylus.util.logging.Logging;
+import net.pterodactylus.util.version.Version;
+import freenet.client.FetchResult;
+import freenet.keys.FreenetURI;
+import freenet.support.api.Bucket;
+
+/**
+ * Watches the official Sone homepage for new releases.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class UpdateChecker {
+
+ /** The logger. */
+ private static final Logger logger = Logging.getLogger(UpdateChecker.class);
+
+ /** The key of the Sone homepage. */
+ private static final String SONE_HOMEPAGE = "USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/";
+
+ /** The current latest known edition. */
+ private static final int LATEST_EDITION = 23;
+
+ /** The Freenet interface. */
+ private final FreenetInterface freenetInterface;
+
+ /** The update listener manager. */
+ private final UpdateListenerManager updateListenerManager = new UpdateListenerManager();
+
+ /** The current URI of the homepage. */
+ private FreenetURI currentUri;
+
+ /** The current latest known version. */
+ private Version currentLatestVersion = SonePlugin.VERSION;
+
+ /** The release date of the latest version. */
+ private long latestVersionDate;
+
+ /**
+ * Creates a new update checker.
+ *
+ * @param freenetInterface
+ * The freenet interface to use
+ */
+ public UpdateChecker(FreenetInterface freenetInterface) {
+ this.freenetInterface = freenetInterface;
+ }
+
+ //
+ // EVENT LISTENER MANAGEMENT
+ //
+
+ /**
+ * Adds the given listener to the list of registered listeners.
+ *
+ * @param updateListener
+ * The listener to add
+ */
+ public void addUpdateListener(UpdateListener updateListener) {
+ updateListenerManager.addListener(updateListener);
+ }
+
+ /**
+ * Removes the given listener from the list of registered listeners.
+ *
+ * @param updateListener
+ * The listener to remove
+ */
+ public void removeUpdateListener(UpdateListener updateListener) {
+ updateListenerManager.removeListener(updateListener);
+ }
+
+ //
+ // ACCESSORS
+ //
+
+ /**
+ * Returns whether a version that is later than the currently running
+ * version has been found.
+ *
+ * @return {@code true} if a new version was found
+ */
+ public boolean hasLatestVersion() {
+ return currentLatestVersion.compareTo(SonePlugin.VERSION) > 0;
+ }
+
+ /**
+ * Returns the latest version. If no new latest version has been found, the
+ * current version is returned.
+ *
+ * @return The latest known version
+ */
+ public Version getLatestVersion() {
+ return currentLatestVersion;
+ }
+
+ /**
+ * Returns the release time of the latest version. If no new latest version
+ * has been found, the returned value is undefined.
+ *
+ * @return The release time of the latest version, if a new version was
+ * found
+ */
+ public long getLatestVersionDate() {
+ return latestVersionDate;
+ }
+
+ //
+ // ACTIONS
+ //
+
+ /**
+ * Starts the update checker.
+ */
+ public void start() {
+ try {
+ currentUri = new FreenetURI(SONE_HOMEPAGE + LATEST_EDITION);
+ } catch (MalformedURLException mue1) {
+ /* this can not really happen unless I screw up. */
+ logger.log(Level.SEVERE, "Sone Homepage URI invalid!", mue1);
+ }
+ freenetInterface.registerUsk(currentUri, new FreenetInterface.Callback() {
+
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public void editionFound(FreenetURI uri, long edition, boolean newKnownGood, boolean newSlot) {
+ logger.log(Level.FINEST, "Found update for %s: %d, %s, %s", new Object[] { uri, edition, newKnownGood, newSlot });
+ if (newKnownGood || newSlot) {
+ Pair<FreenetURI, FetchResult> uriResult = freenetInterface.fetchUri(uri.setMetaString(new String[] { "sone.properties" }));
+ if (uriResult == null) {
+ logger.log(Level.WARNING, "Could not fetch properties of latest homepage: %s", uri);
+ return;
+ }
+ Bucket resultBucket = uriResult.getRight().asBucket();
+ try {
+ parseProperties(resultBucket.getInputStream());
+ } catch (IOException ioe1) {
+ logger.log(Level.WARNING, "Could not parse sone.properties of " + uri, ioe1);
+ } finally {
+ resultBucket.free();
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Stops the update checker.
+ */
+ public void stop() {
+ freenetInterface.unregisterUsk(currentUri);
+ }
+
+ //
+ // PRIVATE ACTIONS
+ //
+
+ /**
+ * Parses the properties of the latest version and fires events, if
+ * necessary.
+ *
+ * @see UpdateListener#updateFound(Version, long)
+ * @see UpdateListenerManager#fireUpdateFound(Version, long)
+ * @param propertiesInputStream
+ * The input stream to parse
+ * @throws IOException
+ * if an I/O error occured
+ */
+ private void parseProperties(InputStream propertiesInputStream) throws IOException {
+ Properties properties = new Properties();
+ InputStreamReader inputStreamReader = null;
+ try {
+ inputStreamReader = new InputStreamReader(propertiesInputStream, "UTF-8");
+ properties.load(inputStreamReader);
+ } finally {
+ Closer.close(inputStreamReader);
+ }
+ String versionString = properties.getProperty("CurrentVersion/Version");
+ String releaseTimeString = properties.getProperty("CurrentVersion/ReleaseTime");
+ if ((versionString == null) || (releaseTimeString == null)) {
+ logger.log(Level.INFO, "Invalid data parsed from properties.");
+ return;
+ }
+ Version version = Version.parse(versionString);
+ long releaseTime = 0;
+ try {
+ releaseTime = Long.parseLong(releaseTimeString);
+ } catch (NumberFormatException nfe1) {
+ /* ignore. */
+ }
+ if ((version == null) || (releaseTime == 0)) {
+ logger.log(Level.INFO, "Could not parse data from properties.");
+ return;
+ }
+ if (version.compareTo(currentLatestVersion) > 0) {
+ currentLatestVersion = version;
+ latestVersionDate = releaseTime;
+ logger.log(Level.INFO, "Found new version: %s (%tc)", new Object[] { version, new Date(releaseTime) });
+ updateListenerManager.fireUpdateFound(version, releaseTime);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Sone - UpdateListener.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core;
+
+import java.util.EventListener;
+
+import net.pterodactylus.util.version.Version;
+
+/**
+ * Listener interface for {@link UpdateChecker} events.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface UpdateListener extends EventListener {
+
+ /**
+ * Notifies a listener that a newer version than the current version was
+ * found.
+ *
+ * @param version
+ * The version that was found
+ * @param releaseTime
+ * The release time of the version
+ */
+ public void updateFound(Version version, long releaseTime);
+
+}
--- /dev/null
+/*
+ * Sone - UpdateListenerManager.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core;
+
+import net.pterodactylus.util.event.AbstractListenerManager;
+import net.pterodactylus.util.version.Version;
+
+/**
+ * Listener manager for {@link UpdateListener} events.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class UpdateListenerManager extends AbstractListenerManager<Void, UpdateListener> {
+
+ /**
+ * Creates a new update listener manager.
+ */
+ public UpdateListenerManager() {
+ super(null);
+ }
+
+ //
+ // ACTIONS
+ //
+
+ /**
+ * Notifies all listeners that a new version has been found.
+ *
+ * @param version
+ * The new version
+ * @param releaseTime
+ * The release time of the new version
+ */
+ void fireUpdateFound(Version version, long releaseTime) {
+ for (UpdateListener updateListener : getListeners()) {
+ updateListener.updateFound(version, releaseTime);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Sone - Fingerprintable.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data;
+
+/**
+ * Interface for objects that can create a fingerprint of themselves, e.g. to
+ * detect modifications. The fingerprint should only contain original
+ * information; derived information should not be included.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface Fingerprintable {
+
+ /**
+ * Returns the fingerprint of this object.
+ *
+ * @return The fingerprint of this object
+ */
+ public String getFingerprint();
+
+}
package net.pterodactylus.sone.data;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import net.pterodactylus.util.validation.Validation;
+
/**
* A profile stores personal information about a {@link Sone}. All information
* is optional and can be {@code null}.
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class Profile {
-
- /** Whether the profile was modified. */
- private volatile boolean modified;
+public class Profile implements Fingerprintable {
/** The first name. */
private volatile String firstName;
/** The year of the birth date. */
private volatile Integer birthYear;
+ /** Additional fields in the profile. */
+ private final List<Field> fields = Collections.synchronizedList(new ArrayList<Field>());
+
/**
* Creates a new empty profile.
*/
this.birthDay = profile.birthDay;
this.birthMonth = profile.birthMonth;
this.birthYear = profile.birthYear;
+ this.fields.addAll(profile.fields);
}
//
//
/**
- * Returns whether this profile was modified after creation. To clear the
- * “is modified” flag you need to create a new profile from this one using
- * the {@link #Profile(Profile)} constructor.
- *
- * @return {@code true} if this profile was modified after creation,
- * {@code false} otherwise
- */
- public boolean isModified() {
- return modified;
- }
-
- /**
* Returns the first name.
*
* @return The first name
* @return This profile (for method chaining)
*/
public Profile setFirstName(String firstName) {
- modified |= ((firstName != null) && (!firstName.equals(this.firstName))) || (this.firstName != null);
this.firstName = firstName;
return this;
}
* @return This profile (for method chaining)
*/
public Profile setMiddleName(String middleName) {
- modified |= ((middleName != null) && (!middleName.equals(this.middleName))) || (this.middleName != null);
this.middleName = middleName;
return this;
}
* @return This profile (for method chaining)
*/
public Profile setLastName(String lastName) {
- modified |= ((lastName != null) && (!lastName.equals(this.lastName))) || (this.lastName != null);
this.lastName = lastName;
return this;
}
* @return This profile (for method chaining)
*/
public Profile setBirthDay(Integer birthDay) {
- modified |= ((birthDay != null) && (!birthDay.equals(this.birthDay))) || (this.birthDay != null);
this.birthDay = birthDay;
return this;
}
* @return This profile (for method chaining)
*/
public Profile setBirthMonth(Integer birthMonth) {
- modified |= ((birthMonth != null) && (!birthMonth.equals(this.birthMonth))) || (this.birthMonth != null);
this.birthMonth = birthMonth;
return this;
}
* @return This profile (for method chaining)
*/
public Profile setBirthYear(Integer birthYear) {
- modified |= ((birthYear != null) && (!birthYear.equals(this.birthYear))) || (this.birthYear != null);
this.birthYear = birthYear;
return this;
}
+ /**
+ * Returns the fields of this profile.
+ *
+ * @return The fields of this profile
+ */
+ public List<Field> getFields() {
+ return new ArrayList<Field>(fields);
+ }
+
+ /**
+ * Returns whether this profile contains the given field.
+ *
+ * @param field
+ * The field to check for
+ * @return {@code true} if this profile contains the field, false otherwise
+ */
+ public boolean hasField(Field field) {
+ return fields.contains(field);
+ }
+
+ /**
+ * Returns the field with the given ID.
+ *
+ * @param fieldId
+ * The ID of the field to get
+ * @return The field, or {@code null} if this profile does not contain a
+ * field with the given ID
+ */
+ public Field getFieldById(String fieldId) {
+ Validation.begin().isNotNull("Field ID", fieldId).check();
+ for (Field field : fields) {
+ if (field.getId().equals(fieldId)) {
+ return field;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the field with the given name.
+ *
+ * @param fieldName
+ * The name of the field to get
+ * @return The field, or {@code null} if this profile does not contain a
+ * field with the given name
+ */
+ public Field getFieldByName(String fieldName) {
+ for (Field field : fields) {
+ if (field.getName().equals(fieldName)) {
+ return field;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Appends a new field to the list of fields.
+ *
+ * @param fieldName
+ * The name of the new field
+ * @return The new field
+ * @throws IllegalArgumentException
+ * if the name is not valid
+ */
+ public Field addField(String fieldName) throws IllegalArgumentException {
+ Validation.begin().isNotNull("Field Name", fieldName).check().isGreater("Field Name Length", fieldName.length(), 0).isNull("Field Name Unique", getFieldByName(fieldName)).check();
+ @SuppressWarnings("synthetic-access")
+ Field field = new Field().setName(fieldName);
+ fields.add(field);
+ return field;
+ }
+
+ /**
+ * Moves the given field up one position in the field list. The index of the
+ * field to move must be greater than {@code 0} (because you obviously can
+ * not move the first field further up).
+ *
+ * @param field
+ * The field to move up
+ */
+ public void moveFieldUp(Field field) {
+ Validation.begin().isNotNull("Field", field).check().is("Field Existing", hasField(field)).isGreater("Field Index", getFieldIndex(field), 0).check();
+ int fieldIndex = getFieldIndex(field);
+ fields.remove(field);
+ fields.add(fieldIndex - 1, field);
+ }
+
+ /**
+ * Moves the given field down one position in the field list. The index of
+ * the field to move must be less than the index of the last field (because
+ * you obviously can not move the last field further down).
+ *
+ * @param field
+ * The field to move down
+ */
+ public void moveFieldDown(Field field) {
+ Validation.begin().isNotNull("Field", field).check().is("Field Existing", hasField(field)).isLess("Field Index", getFieldIndex(field), fields.size() - 1).check();
+ int fieldIndex = getFieldIndex(field);
+ fields.remove(field);
+ fields.add(fieldIndex + 1, field);
+ }
+
+ /**
+ * Removes the given field.
+ *
+ * @param field
+ * The field to remove
+ */
+ public void removeField(Field field) {
+ Validation.begin().isNotNull("Field", field).check().is("Field Existing", hasField(field)).check();
+ fields.remove(field);
+ }
+
+ //
+ // PRIVATE METHODS
+ //
+
+ /**
+ * Returns the index of the field with the given name.
+ *
+ * @param field
+ * The name of the field
+ * @return The index of the field, or {@code -1} if there is no field with
+ * the given name
+ */
+ private int getFieldIndex(Field field) {
+ return fields.indexOf(field);
+ }
+
+ //
+ // INTERFACE Fingerprintable
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getFingerprint() {
+ StringBuilder fingerprint = new StringBuilder();
+ fingerprint.append("Profile(");
+ if (firstName != null) {
+ fingerprint.append("FirstName(").append(firstName).append(')');
+ }
+ if (middleName != null) {
+ fingerprint.append("MiddleName(").append(middleName).append(')');
+ }
+ if (lastName != null) {
+ fingerprint.append("LastName(").append(lastName).append(')');
+ }
+ if (birthDay != null) {
+ fingerprint.append("BirthDay(").append(birthDay).append(')');
+ }
+ if (birthMonth != null) {
+ fingerprint.append("BirthMonth(").append(birthMonth).append(')');
+ }
+ if (birthYear != null) {
+ fingerprint.append("BirthYear(").append(birthYear).append(')');
+ }
+ fingerprint.append("ContactInformation(");
+ for (Field field : fields) {
+ fingerprint.append(field.getName()).append('(').append(field.getValue()).append(')');
+ }
+ fingerprint.append(")");
+ fingerprint.append(")");
+
+ return fingerprint.toString();
+ }
+
+ /**
+ * Container for a profile field.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ public class Field {
+
+ /** The ID of the field. */
+ private final String id;
+
+ /** The name of the field. */
+ private String name;
+
+ /** The value of the field. */
+ private String value;
+
+ /**
+ * Creates a new field with a random ID.
+ */
+ private Field() {
+ this(UUID.randomUUID().toString());
+ }
+
+ /**
+ * Creates a new field with the given ID.
+ *
+ * @param id
+ * The ID of the field
+ */
+ private Field(String id) {
+ Validation.begin().isNotNull("Field ID", id).check();
+ this.id = id;
+ }
+
+ /**
+ * Returns the ID of this field.
+ *
+ * @return The ID of this field
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Returns the name of this field.
+ *
+ * @return The name of this field
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name of this field. The name must not be {@code null} and
+ * must not match any other fields in this profile but my match the name
+ * of this field.
+ *
+ * @param name
+ * The new name of this field
+ * @return This field
+ */
+ public Field setName(String name) {
+ Validation.begin().isNotNull("Field Name", name).check().is("Field Unique", (getFieldByName(name) == null) || equals(getFieldByName(name))).check();
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Returns the value of this field.
+ *
+ * @return The value of this field
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value of this field. While {@code null} is allowed, no
+ * guarantees are made that {@code null} values are correctly persisted
+ * across restarts of the plugin!
+ *
+ * @param value
+ * The new value of this field
+ * @return This field
+ */
+ public Field setValue(String value) {
+ this.value = value;
+ return this;
+ }
+
+ //
+ // OBJECT METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (!(object instanceof Field)) {
+ return false;
+ }
+ Field field = (Field) object;
+ return id.equals(field.id);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ }
+
}
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class Sone {
+public class Sone implements Fingerprintable {
/** comparator that sorts Sones by their nice name. */
public static final Comparator<Sone> NICE_NAME_COMPARATOR = new Comparator<Sone>() {
return this;
}
+ //
+ // FINGERPRINTABLE METHODS
+ //
+
/**
- * Returns a fingerprint of this Sone. The fingerprint only depends on data
- * that is actually stored when a Sone is inserted. The fingerprint can be
- * used to detect changes in Sone data and can also be used to detect if
- * previous changes are reverted.
- *
- * @return The fingerprint of this Sone
+ * {@inheritDoc}
*/
+ @Override
public synchronized String getFingerprint() {
StringBuilder fingerprint = new StringBuilder();
- fingerprint.append("Profile(");
- if (profile.getFirstName() != null) {
- fingerprint.append("FirstName(").append(profile.getFirstName()).append(')');
- }
- if (profile.getMiddleName() != null) {
- fingerprint.append("MiddleName(").append(profile.getMiddleName()).append(')');
- }
- if (profile.getLastName() != null) {
- fingerprint.append("LastName(").append(profile.getLastName()).append(')');
- }
- if (profile.getBirthDay() != null) {
- fingerprint.append("BirthDay(").append(profile.getBirthDay()).append(')');
- }
- if (profile.getBirthMonth() != null) {
- fingerprint.append("BirthMonth(").append(profile.getBirthMonth()).append(')');
- }
- if (profile.getBirthYear() != null) {
- fingerprint.append("BirthYear(").append(profile.getBirthYear()).append(')');
- }
- fingerprint.append(")");
+ fingerprint.append(profile.getFingerprint());
fingerprint.append("Posts(");
for (Post post : getPosts()) {
}
/** The version. */
- public static final Version VERSION = new Version(0, 3, 6, 4);
+ public static final Version VERSION = new Version(0, 3, 7);
/** The logger. */
private static final Logger logger = Logging.getLogger(SonePlugin.class);
}
}
- /* create freenet interface. */
- FreenetInterface freenetInterface = new FreenetInterface(pluginRespirator.getNode());
+ boolean startupFailed = true;
+ try {
+ /* create freenet interface. */
+ FreenetInterface freenetInterface = new FreenetInterface(pluginRespirator.getNode());
- /* create web of trust connector. */
- PluginConnector pluginConnector = new PluginConnector(pluginRespirator);
- WebOfTrustConnector webOfTrustConnector = new WebOfTrustConnector(pluginConnector);
- identityManager = new IdentityManager(webOfTrustConnector);
- identityManager.setContext("Sone");
+ /* create web of trust connector. */
+ PluginConnector pluginConnector = new PluginConnector(pluginRespirator);
+ WebOfTrustConnector webOfTrustConnector = new WebOfTrustConnector(pluginConnector);
+ identityManager = new IdentityManager(webOfTrustConnector);
+ identityManager.setContext("Sone");
- /* create core. */
- core = new Core(oldConfiguration, freenetInterface, identityManager);
+ /* create core. */
+ core = new Core(oldConfiguration, freenetInterface, identityManager);
- /* create the web interface. */
- webInterface = new WebInterface(this);
- core.addCoreListener(webInterface);
+ /* create the web interface. */
+ webInterface = new WebInterface(this);
+ core.addCoreListener(webInterface);
- /* create the identity manager. */
- identityManager.addIdentityListener(core);
+ /* create the identity manager. */
+ identityManager.addIdentityListener(core);
- /* start core! */
- boolean startupFailed = true;
- try {
+ /* start core! */
core.start();
if ((newConfiguration != null) && (oldConfiguration != newConfiguration)) {
logger.log(Level.INFO, "Setting configuration to file-based configuration.");
--- /dev/null
+/*
+ * Sone - JavascriptFilter.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.template;
+
+import java.util.Map;
+
+import net.pterodactylus.util.number.Hex;
+import net.pterodactylus.util.template.DataProvider;
+import net.pterodactylus.util.template.Filter;
+
+/**
+ * Escapes double quotes, backslashes, carriage returns and line feeds, and
+ * additionally encloses a given string with double quotes to make it possible
+ * to use a string in Javascript.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class JavascriptFilter implements Filter {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object format(DataProvider dataProvider, Object data, Map<String, String> parameters) {
+ StringBuilder javascriptString = new StringBuilder();
+ javascriptString.append('"');
+ for (char c : String.valueOf(data).toCharArray()) {
+ if (c == '\r') {
+ javascriptString.append("\\r");
+ continue;
+ }
+ if (c == '\n') {
+ javascriptString.append("\\n");
+ continue;
+ }
+ if (c == '\t') {
+ javascriptString.append("\\t");
+ continue;
+ }
+ if ((c == '"') || (c == '\\')) {
+ javascriptString.append('\\');
+ javascriptString.append(c);
+ } else if (c < 32) {
+ javascriptString.append("\\x").append(Hex.toHex((byte) c));
+ } else {
+ javascriptString.append(c);
+ }
+ }
+ javascriptString.append('"');
+ return javascriptString.toString();
+ }
+
+}
import net.pterodactylus.sone.data.Post;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.text.FreenetLinkParser;
+import net.pterodactylus.sone.text.FreenetLinkParserContext;
import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.ReflectionAccessor;
import net.pterodactylus.util.template.TemplateFactory;
return null;
}
try {
- synchronized (linkParser) {
- linkParser.setPostingSone(post.getSone());
- return linkParser.parse(new StringReader(text));
- }
+ return linkParser.parse(new FreenetLinkParserContext(post.getSone()), new StringReader(text));
} catch (IOException ioe1) {
/* ignore. */
}
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.text.FreenetLinkParser;
+import net.pterodactylus.sone.text.FreenetLinkParserContext;
import net.pterodactylus.util.template.Accessor;
import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.ReflectionAccessor;
} else if (member.equals("text")) {
String text = reply.getText();
try {
- synchronized (linkParser) {
- linkParser.setPostingSone(reply.getSone());
- return linkParser.parse(new StringReader(text));
- }
+ return linkParser.parse(new FreenetLinkParserContext(reply.getSone()), new StringReader(text));
} catch (IOException ioe1) {
/* ignore. */
}
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.util.logging.Logging;
import net.pterodactylus.util.template.TemplateFactory;
import freenet.keys.FreenetURI;
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class FreenetLinkParser implements Parser {
+public class FreenetLinkParser implements Parser<FreenetLinkParserContext> {
/** The logger. */
private static final Logger logger = Logging.getLogger(FreenetLinkParser.class);
/** The template factory. */
private final TemplateFactory templateFactory;
- /** The Sone that posted the currently parsed text. */
- private Sone postingSone;
-
/**
* Creates a new freenet link parser.
*
}
//
- // ACCESSORS
- //
-
- /**
- * Sets the Sone that posted the text that will be parsed in the next call
- * to {@link #parse(Reader)}. You need to synchronize calling this method
- * and {@link #parse(Reader)}!
- *
- * @param sone
- * The Sone that posted the text
- */
- public void setPostingSone(Sone sone) {
- postingSone = sone;
- }
-
- //
// PART METHODS
//
* {@inheritDoc}
*/
@Override
- public Part parse(Reader source) throws IOException {
+ public Part parse(FreenetLinkParserContext context, Reader source) throws IOException {
PartContainer parts = new PartContainer();
BufferedReader bufferedReader = (source instanceof BufferedReader) ? (BufferedReader) source : new BufferedReader(source);
String line;
String name = link;
logger.log(Level.FINER, "Found link: %s", link);
logger.log(Level.FINEST, "Next: %d, CHK: %d, SSK: %d, USK: %d", new Object[] { next, nextChk, nextSsk, nextUsk });
- if (linkType == LinkType.KSK) {
- name = link.substring(4);
- } else if ((linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) {
- if (name.indexOf('/') > -1) {
- if (!name.endsWith("/")) {
- name = name.substring(name.lastIndexOf('/') + 1);
- } else {
- if (name.indexOf('/') != name.lastIndexOf('/')) {
- name = name.substring(name.lastIndexOf('/', name.lastIndexOf('/') - 1));
- } else {
- /* shorten to 5 chars. */
- name = name.substring(4, Math.min(9, name.length()));
- }
- }
- }
+
+ if ((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) {
+ FreenetURI uri;
if (name.indexOf('?') > -1) {
name = name.substring(0, name.indexOf('?'));
}
- boolean fromPostingSone = false;
- if ((linkType == LinkType.SSK) || (linkType == LinkType.USK)) {
- try {
- new FreenetURI(link);
- fromPostingSone = link.substring(4, 47).equals(postingSone.getId());
- parts.add(fromPostingSone ? createTrustedFreenetLinkPart(link, name) : createFreenetLinkPart(link, name));
- } catch (MalformedURLException mue1) {
- /* it’s not a valid link. */
- parts.add(createPlainTextPart(link));
+ if (name.endsWith("/")) {
+ name = name.substring(0, name.length() - 1);
+ }
+ try {
+ uri = new FreenetURI(name);
+ name = uri.lastMetaString();
+ if (name == null) {
+ name = uri.getDocName();
+ }
+ if (name == null) {
+ name = link.substring(0, Math.min(9, link.length()));
}
- } else {
+ boolean fromPostingSone = ((linkType == LinkType.SSK) || (linkType == LinkType.USK)) && link.substring(4, Math.min(link.length(), 47)).equals(context.getPostingSone().getId());
parts.add(fromPostingSone ? createTrustedFreenetLinkPart(link, name) : createFreenetLinkPart(link, name));
+ } catch (MalformedURLException mue1) {
+ /* not a valid link, insert as plain text. */
+ parts.add(createPlainTextPart(link));
+ } catch (NullPointerException npe1) {
+ /* FreenetURI sometimes throws these, too. */
+ parts.add(createPlainTextPart(link));
+ } catch (ArrayIndexOutOfBoundsException aioobe1) {
+ /* oh, and these, too. */
+ parts.add(createPlainTextPart(link));
}
} else if ((linkType == LinkType.HTTP) || (linkType == LinkType.HTTPS)) {
name = link.substring(linkType == LinkType.HTTP ? 7 : 8);
--- /dev/null
+/*
+ * Sone - FreenetLinkParserContext.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.text;
+
+import net.pterodactylus.sone.data.Sone;
+
+/**
+ * {@link ParserContext} implementation for the {@link FreenetLinkParser}. It
+ * stores the {@link Sone} that provided the parsed text so that certain links
+ * can be marked in a different way.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class FreenetLinkParserContext implements ParserContext {
+
+ /** The posting Sone. */
+ private final Sone postingSone;
+
+ /**
+ * Creates a new link parser context.
+ *
+ * @param postingSone
+ * The posting Sone
+ */
+ public FreenetLinkParserContext(Sone postingSone) {
+ this.postingSone = postingSone;
+ }
+
+ /**
+ * Returns the Sone that provided the text that is being parsed.
+ *
+ * @return The posting Sone
+ */
+ public Sone getPostingSone() {
+ return postingSone;
+ }
+
+}
* Interface for parsers that can create {@link Part}s from a text source
* (usually a {@link Reader}).
*
+ * @param <C>
+ * The type of the parser context
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public interface Parser {
+public interface Parser<C extends ParserContext> {
/**
* Create a {@link Part} from the given text source.
*
+ * @param context
+ * The parser context
* @param source
* The text source
* @return The parsed part
* @throws IOException
* if an I/O error occurs
*/
- public Part parse(Reader source) throws IOException;
+ public Part parse(C context, Reader source) throws IOException;
}
--- /dev/null
+
+package net.pterodactylus.sone.text;
+
+/**
+ * Context for the {@link Parser}. This interface needs to be implemented by
+ * {@link Parser}s that need to provide more information than just the text to
+ * parse to {@link Parser#parse(ParserContext, java.io.Reader)}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface ParserContext {
+
+ /* nothing to see. */
+
+}
package net.pterodactylus.sone.web;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
import net.pterodactylus.util.version.Version;
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
- template.set("version", version);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
+ dataProvider.set("version", version);
}
}
import net.pterodactylus.sone.data.Post;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
if (request.getMethod() == Method.POST) {
String text = request.getHttpRequest().getPartAsStringFailsafe("text", 65536).trim();
webInterface.getCore().createPost(currentSone, recipient, System.currentTimeMillis(), text);
throw new RedirectException(returnPage);
}
- template.set("errorTextEmpty", true);
+ dataProvider.set("errorTextEmpty", true);
}
- template.set("returnPage", returnPage);
+ dataProvider.set("returnPage", returnPage);
}
}
import net.pterodactylus.sone.data.Post;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
String postId = request.getHttpRequest().getPartAsStringFailsafe("post", 36);
String text = request.getHttpRequest().getPartAsStringFailsafe("text", 65536).trim();
String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
webInterface.getCore().createReply(currentSone, post, text);
throw new RedirectException(returnPage);
}
- template.set("errorTextEmpty", true);
+ dataProvider.set("errorTextEmpty", true);
}
- template.set("postId", postId);
- template.set("text", text);
- template.set("returnPage", returnPage);
+ dataProvider.set("postId", postId);
+ dataProvider.set("text", text);
+ dataProvider.set("returnPage", returnPage);
}
}
import net.pterodactylus.sone.freenet.wot.OwnIdentity;
import net.pterodactylus.sone.web.page.Page.Request.Method;
import net.pterodactylus.util.logging.Logging;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
import freenet.clients.http.ToadletContext;
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
List<OwnIdentity> ownIdentitiesWithoutSone = getOwnIdentitiesWithoutSone(webInterface.getCore());
- template.set("identitiesWithoutSone", ownIdentitiesWithoutSone);
+ dataProvider.set("identitiesWithoutSone", ownIdentitiesWithoutSone);
if (request.getMethod() == Method.POST) {
String id = request.getHttpRequest().getPartAsStringFailsafe("identity", 44);
OwnIdentity selectedIdentity = null;
}
}
if (selectedIdentity == null) {
- template.set("errorNoIdentity", true);
+ dataProvider.set("errorNoIdentity", true);
return;
}
/* create Sone. */
import net.pterodactylus.sone.data.Post;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
if (request.getMethod() == Method.GET) {
String postId = request.getHttpRequest().getParam("post");
String returnPage = request.getHttpRequest().getParam("returnPage");
Post post = webInterface.getCore().getPost(postId);
- template.set("post", post);
- template.set("returnPage", returnPage);
+ dataProvider.set("post", post);
+ dataProvider.set("returnPage", returnPage);
return;
} else if (request.getMethod() == Method.POST) {
String postId = request.getHttpRequest().getPartAsStringFailsafe("post", 36);
} else if (request.getHttpRequest().isPartSet("abortDelete")) {
throw new RedirectException(returnPage);
}
- template.set("post", post);
- template.set("returnPage", returnPage);
+ dataProvider.set("post", post);
+ dataProvider.set("returnPage", returnPage);
}
}
--- /dev/null
+/*
+ * Sone - DeleteProfileFieldPage.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web;
+
+import net.pterodactylus.sone.data.Profile;
+import net.pterodactylus.sone.data.Profile.Field;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.DataProvider;
+import net.pterodactylus.util.template.Template;
+
+/**
+ * Page that lets the user confirm the deletion of a profile field.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class DeleteProfileFieldPage extends SoneTemplatePage {
+
+ /**
+ * Creates a new “delete profile field” page.
+ *
+ * @param template
+ * The template to render
+ * @param webInterface
+ * The Sone web interface
+ */
+ public DeleteProfileFieldPage(Template template, WebInterface webInterface) {
+ super("deleteProfileField.html", template, "Page.DeleteProfileField.Title", webInterface, true);
+ }
+
+ //
+ // SONETEMPLATEPAGE METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
+ Sone currentSone = getCurrentSone(request.getToadletContext());
+ Profile profile = currentSone.getProfile();
+
+ /* get parameters from request. */
+ String fieldId = request.getHttpRequest().getParam("field");
+ Field field = profile.getFieldById(fieldId);
+ if (field == null) {
+ throw new RedirectException("invalid.html");
+ }
+
+ /* process POST request. */
+ if (request.getMethod() == Method.POST) {
+ if (request.getHttpRequest().getPartAsStringFailsafe("confirm", 4).equals("true")) {
+ fieldId = request.getHttpRequest().getParam("field");
+ field = profile.getFieldById(fieldId);
+ if (field == null) {
+ throw new RedirectException("invalid.html");
+ }
+ profile.removeField(field);
+ currentSone.setProfile(profile);
+ }
+ throw new RedirectException("editProfile.html#profile-fields");
+ }
+
+ /* set current values in template. */
+ dataProvider.set("field", field);
+ }
+
+}
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
String replyId = request.getHttpRequest().getPartAsStringFailsafe("reply", 36);
Reply reply = webInterface.getCore().getReply(replyId);
String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
throw new RedirectException(returnPage);
}
}
- template.set("reply", reply);
- template.set("returnPage", returnPage);
+ dataProvider.set("reply", reply);
+ dataProvider.set("returnPage", returnPage);
}
}
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
if (request.getMethod() == Method.POST) {
if (request.getHttpRequest().isPartSet("deleteSone")) {
Sone currentSone = getCurrentSone(request.getToadletContext());
package net.pterodactylus.sone.web;
import net.pterodactylus.util.notify.Notification;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
String notificationId = request.getHttpRequest().getPartAsStringFailsafe("notification", 36);
Notification notification = webInterface.getNotifications().getNotification(notificationId);
if ((notification != null) && notification.isDismissable()) {
import net.pterodactylus.sone.core.Core;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
if (request.getMethod() == Method.POST) {
String returnPath = request.getHttpRequest().getPartAsStringFailsafe("returnPath", 256);
String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
--- /dev/null
+/*
+ * Sone - EditProfileFieldPage.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web;
+
+import net.pterodactylus.sone.data.Profile;
+import net.pterodactylus.sone.data.Profile.Field;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.DataProvider;
+import net.pterodactylus.util.template.Template;
+
+/**
+ * Page that lets the user edit the name of a profile field.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class EditProfileFieldPage extends SoneTemplatePage {
+
+ /**
+ * Creates a new “edit profile field” page.
+ *
+ * @param template
+ * The template to render
+ * @param webInterface
+ * The Sone web interface
+ */
+ public EditProfileFieldPage(Template template, WebInterface webInterface) {
+ super("editProfileField.html", template, "Page.EditProfileField.Title", webInterface, true);
+ }
+
+ //
+ // SONETEMPLATEPAGE METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
+ Sone currentSone = getCurrentSone(request.getToadletContext());
+ Profile profile = currentSone.getProfile();
+
+ /* get parameters from request. */
+ String fieldId = request.getHttpRequest().getParam("field");
+ Field field = profile.getFieldById(fieldId);
+ if (field == null) {
+ throw new RedirectException("invalid.html");
+ }
+
+ /* process the POST request. */
+ if (request.getMethod() == Method.POST) {
+ if (request.getHttpRequest().getPartAsStringFailsafe("cancel", 4).equals("true")) {
+ throw new RedirectException("editProfile.html#profile-fields");
+ }
+ fieldId = request.getHttpRequest().getPartAsStringFailsafe("field", 36);
+ field = profile.getFieldById(fieldId);
+ if (field == null) {
+ throw new RedirectException("invalid.html");
+ }
+ String name = request.getHttpRequest().getPartAsStringFailsafe("name", 256);
+ Field existingField = profile.getFieldByName(name);
+ if ((existingField == null) || (existingField.equals(field))) {
+ field.setName(name);
+ currentSone.setProfile(profile);
+ throw new RedirectException("editProfile.html#profile-fields");
+ }
+ dataProvider.set("duplicateFieldName", true);
+ }
+
+ /* store current values in template. */
+ dataProvider.set("field", field);
+ }
+
+}
package net.pterodactylus.sone.web;
+import java.util.List;
+
import net.pterodactylus.sone.data.Profile;
+import net.pterodactylus.sone.data.Profile.Field;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.page.Page.Request.Method;
import net.pterodactylus.util.number.Numbers;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
import freenet.clients.http.ToadletContext;
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
ToadletContext toadletContenxt = request.getToadletContext();
Sone currentSone = getCurrentSone(toadletContenxt);
Profile profile = currentSone.getProfile();
Integer birthDay = profile.getBirthDay();
Integer birthMonth = profile.getBirthMonth();
Integer birthYear = profile.getBirthYear();
+ List<Field> fields = profile.getFields();
if (request.getMethod() == Method.POST) {
- firstName = request.getHttpRequest().getPartAsStringFailsafe("first-name", 256).trim();
- middleName = request.getHttpRequest().getPartAsStringFailsafe("middle-name", 256).trim();
- lastName = request.getHttpRequest().getPartAsStringFailsafe("last-name", 256).trim();
- birthDay = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-day", 256).trim());
- birthMonth = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-month", 256).trim());
- birthYear = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-year", 256).trim());
- profile.setFirstName(firstName.length() > 0 ? firstName : null);
- profile.setMiddleName(middleName.length() > 0 ? middleName : null);
- profile.setLastName(lastName.length() > 0 ? lastName : null);
- profile.setBirthDay(birthDay).setBirthMonth(birthMonth).setBirthYear(birthYear);
- if (profile.isModified()) {
+ if (request.getHttpRequest().getPartAsStringFailsafe("save-profile", 4).equals("true")) {
+ firstName = request.getHttpRequest().getPartAsStringFailsafe("first-name", 256).trim();
+ middleName = request.getHttpRequest().getPartAsStringFailsafe("middle-name", 256).trim();
+ lastName = request.getHttpRequest().getPartAsStringFailsafe("last-name", 256).trim();
+ birthDay = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-day", 256).trim());
+ birthMonth = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-month", 256).trim());
+ birthYear = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-year", 256).trim());
+ profile.setFirstName(firstName.length() > 0 ? firstName : null);
+ profile.setMiddleName(middleName.length() > 0 ? middleName : null);
+ profile.setLastName(lastName.length() > 0 ? lastName : null);
+ profile.setBirthDay(birthDay).setBirthMonth(birthMonth).setBirthYear(birthYear);
+ for (Field field : fields) {
+ String value = request.getHttpRequest().getPartAsStringFailsafe("field-" + field.getId(), 400);
+ field.setValue(value);
+ }
currentSone.setProfile(profile);
+ webInterface.getCore().saveSone(currentSone);
+ throw new RedirectException("editProfile.html");
+ } else if (request.getHttpRequest().getPartAsStringFailsafe("add-field", 4).equals("true")) {
+ String fieldName = request.getHttpRequest().getPartAsStringFailsafe("field-name", 256).trim();
+ try {
+ profile.addField(fieldName);
+ currentSone.setProfile(profile);
+ fields = profile.getFields();
+ webInterface.getCore().saveSone(currentSone);
+ throw new RedirectException("editProfile.html#profile-fields");
+ } catch (IllegalArgumentException iae1) {
+ dataProvider.set("fieldName", fieldName);
+ dataProvider.set("duplicateFieldName", true);
+ }
+ } else {
+ String id = getFieldId(request, "delete-field-");
+ if (id != null) {
+ throw new RedirectException("deleteProfileField.html?field=" + id);
+ }
+ id = getFieldId(request, "move-up-field-");
+ if (id != null) {
+ Field field = profile.getFieldById(id);
+ if (field == null) {
+ throw new RedirectException("invalid.html");
+ }
+ profile.moveFieldUp(field);
+ currentSone.setProfile(profile);
+ throw new RedirectException("editProfile.html#profile-fields");
+ }
+ id = getFieldId(request, "move-down-field-");
+ if (id != null) {
+ Field field = profile.getFieldById(id);
+ if (field == null) {
+ throw new RedirectException("invalid.html");
+ }
+ profile.moveFieldDown(field);
+ currentSone.setProfile(profile);
+ throw new RedirectException("editProfile.html#profile-fields");
+ }
+ id = getFieldId(request, "edit-field-");
+ if (id != null) {
+ throw new RedirectException("editProfileField.html?field=" + id);
+ }
}
- throw new RedirectException("index.html");
}
- template.set("firstName", firstName);
- template.set("middleName", middleName);
- template.set("lastName", lastName);
- template.set("birthDay", birthDay);
- template.set("birthMonth", birthMonth);
- template.set("birthYear", birthYear);
+ dataProvider.set("firstName", firstName);
+ dataProvider.set("middleName", middleName);
+ dataProvider.set("lastName", lastName);
+ dataProvider.set("birthDay", birthDay);
+ dataProvider.set("birthMonth", birthMonth);
+ dataProvider.set("birthYear", birthYear);
+ dataProvider.set("fields", fields);
}
+ //
+ // PRIVATE METHODS
+ //
+
+ /**
+ * Searches for a part whose names starts with the given {@code String} and
+ * extracts the ID from the located name.
+ *
+ * @param request
+ * The request to get the parts from
+ * @param partNameStart
+ * The start of the name of the requested part
+ * @return The parsed ID, or {@code null} if there was no part matching the
+ * given string
+ */
+ private String getFieldId(Request request, String partNameStart) {
+ for (String partName : request.getHttpRequest().getParts()) {
+ if (partName.startsWith(partNameStart)) {
+ return partName.substring(partNameStart.length());
+ }
+ }
+ return null;
+ }
}
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
if (request.getMethod() == Method.POST) {
String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
import net.pterodactylus.sone.data.Post;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.util.collection.Pagination;
+import net.pterodactylus.util.number.Numbers;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
Sone currentSone = getCurrentSone(request.getToadletContext());
List<Post> allPosts = new ArrayList<Post>();
allPosts.addAll(currentSone.getPosts());
}
}
Collections.sort(allPosts, Post.TIME_COMPARATOR);
- template.set("posts", allPosts);
+ Pagination<Post> pagination = new Pagination<Post>(allPosts, 25).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0));
+ dataProvider.set("pagination", pagination);
+ dataProvider.set("posts", pagination.getItems());
}
/**
* {@inheritDoc}
*/
@Override
- protected void postProcess(Request request, Template template) {
+ protected void postProcess(Request request, DataProvider dataProvider) {
@SuppressWarnings("unchecked")
- List<Post> posts = (List<Post>) template.get("posts");
+ List<Post> posts = (List<Post>) dataProvider.get("posts");
for (Post post : posts) {
webInterface.getCore().markPostKnown(post);
for (Reply reply : webInterface.getCore().getReplies(post)) {
import java.util.List;
import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
List<Sone> knownSones = new ArrayList<Sone>(webInterface.getCore().getSones());
Collections.sort(knownSones, Sone.NICE_NAME_COMPARATOR);
- template.set("knownSones", knownSones);
+ dataProvider.set("knownSones", knownSones);
}
}
import net.pterodactylus.sone.data.Post;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
if (request.getMethod() == Method.POST) {
String type=request.getHttpRequest().getPartAsStringFailsafe("type", 16);
String id = request.getHttpRequest().getPartAsStringFailsafe(type, 36);
package net.pterodactylus.sone.web;
import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
Sone sone = webInterface.getCore().getLocalSone(soneId, false);
if (sone != null) {
import net.pterodactylus.sone.freenet.wot.OwnIdentity;
import net.pterodactylus.sone.web.page.Page.Request.Method;
import net.pterodactylus.util.logging.Logging;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
import freenet.clients.http.ToadletContext;
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
/* get all own identities. */
List<Sone> localSones = new ArrayList<Sone>(webInterface.getCore().getLocalSones());
Collections.sort(localSones, Sone.NICE_NAME_COMPARATOR);
- template.set("sones", localSones);
+ dataProvider.set("sones", localSones);
if (request.getMethod() == Method.POST) {
String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone-id", 100);
Sone selectedSone = webInterface.getCore().getLocalSone(soneId, false);
}
}
List<OwnIdentity> ownIdentitiesWithoutSone = CreateSonePage.getOwnIdentitiesWithoutSone(webInterface.getCore());
- template.set("identitiesWithoutSone", ownIdentitiesWithoutSone);
+ dataProvider.set("identitiesWithoutSone", ownIdentitiesWithoutSone);
}
/**
package net.pterodactylus.sone.web;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
import freenet.clients.http.ToadletContext;
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
setCurrentSone(request.getToadletContext(), null);
- super.processTemplate(request, template);
+ super.processTemplate(request, dataProvider);
throw new RedirectException("index.html");
}
import net.pterodactylus.sone.core.Options;
import net.pterodactylus.sone.web.page.Page.Request.Method;
import net.pterodactylus.util.number.Numbers;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
Options options = webInterface.getCore().getOptions();
if (request.getMethod() == Method.POST) {
Integer insertionDelay = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("insertion-delay", 16));
webInterface.getCore().saveConfiguration();
throw new RedirectException(getPath());
}
- template.set("insertion-delay", options.getIntegerOption("InsertionDelay").get());
- template.set("positive-trust", options.getIntegerOption("PositiveTrust").get());
- template.set("negative-trust", options.getIntegerOption("NegativeTrust").get());
- template.set("trust-comment", options.getStringOption("TrustComment").get());
- template.set("sone-rescue-mode", options.getBooleanOption("SoneRescueMode").get());
- template.set("clear-on-next-restart", options.getBooleanOption("ClearOnNextRestart").get());
- template.set("really-clear-on-next-restart", options.getBooleanOption("ReallyClearOnNextRestart").get());
+ dataProvider.set("insertion-delay", options.getIntegerOption("InsertionDelay").get());
+ dataProvider.set("positive-trust", options.getIntegerOption("PositiveTrust").get());
+ dataProvider.set("negative-trust", options.getIntegerOption("NegativeTrust").get());
+ dataProvider.set("trust-comment", options.getStringOption("TrustComment").get());
+ dataProvider.set("sone-rescue-mode", options.getBooleanOption("SoneRescueMode").get());
+ dataProvider.set("clear-on-next-restart", options.getBooleanOption("ClearOnNextRestart").get());
+ dataProvider.set("really-clear-on-next-restart", options.getBooleanOption("ReallyClearOnNextRestart").get());
}
}
import java.util.Collection;
import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.main.SonePlugin;
import net.pterodactylus.sone.web.page.Page;
import net.pterodactylus.sone.web.page.TemplatePage;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
import freenet.clients.http.SessionManager.Session;
import freenet.clients.http.ToadletContext;
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
- template.set("currentSone", getCurrentSone(request.getToadletContext(), false));
- template.set("request", request);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
+ dataProvider.set("currentSone", getCurrentSone(request.getToadletContext(), false));
+ dataProvider.set("request", request);
+ dataProvider.set("currentVersion", SonePlugin.VERSION);
+ dataProvider.set("hasLatestVersion", webInterface.getCore().getUpdateChecker().hasLatestVersion());
+ dataProvider.set("latestVersion", webInterface.getCore().getUpdateChecker().getLatestVersion());
+ dataProvider.set("latestVersionTime", webInterface.getCore().getUpdateChecker().getLatestVersionDate());
}
/**
import net.pterodactylus.sone.core.Core;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
if (request.getMethod() == Method.POST) {
String returnPath = request.getHttpRequest().getPartAsStringFailsafe("returnPath", 256);
String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
if (request.getMethod() == Method.POST) {
String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
import net.pterodactylus.sone.data.Post;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
if (request.getMethod() == Method.POST) {
String type = request.getHttpRequest().getPartAsStringFailsafe("type", 16);
String id = request.getHttpRequest().getPartAsStringFailsafe(type, 36);
package net.pterodactylus.sone.web;
import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
Sone sone = webInterface.getCore().getLocalSone(soneId, false);
if (sone != null) {
import net.pterodactylus.sone.core.Core;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
if (request.getMethod() == Method.POST) {
String returnPath = request.getHttpRequest().getPartAsStringFailsafe("returnPath", 256);
String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
import net.pterodactylus.sone.data.Post;
import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
String postId = request.getHttpRequest().getParam("post");
Post post = webInterface.getCore().getPost(postId);
- template.set("post", post);
+ dataProvider.set("post", post);
}
/**
* {@inheritDoc}
*/
@Override
- protected void postProcess(Request request, Template template) {
- Post post = (Post) template.get("post");
+ protected void postProcess(Request request, DataProvider dataProvider) {
+ Post post = (Post) dataProvider.get("post");
webInterface.getCore().markPostKnown(post);
for (Reply reply : webInterface.getCore().getReplies(post)) {
webInterface.getCore().markReplyKnown(reply);
import net.pterodactylus.sone.data.Post;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
/**
* {@inheritDoc}
*/
@Override
- protected void processTemplate(Request request, Template template) throws RedirectException {
- super.processTemplate(request, template);
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+ super.processTemplate(request, dataProvider);
String soneId = request.getHttpRequest().getParam("sone");
Sone sone = webInterface.getCore().getSone(soneId, false);
- template.set("sone", sone);
+ dataProvider.set("sone", sone);
}
/**
* {@inheritDoc}
*/
@Override
- protected void postProcess(Request request, Template template) {
- Sone sone = (Sone) template.get("sone");
+ protected void postProcess(Request request, DataProvider dataProvider) {
+ Sone sone = (Sone) dataProvider.get("sone");
List<Post> posts = sone.getPosts();
for (Post post : posts) {
webInterface.getCore().markPostKnown(post);
import net.pterodactylus.sone.template.CssClassNameFilter;
import net.pterodactylus.sone.template.GetPagePlugin;
import net.pterodactylus.sone.template.IdentityAccessor;
+import net.pterodactylus.sone.template.JavascriptFilter;
import net.pterodactylus.sone.template.NotificationManagerAccessor;
import net.pterodactylus.sone.template.PostAccessor;
import net.pterodactylus.sone.template.ReplyAccessor;
import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
+import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage;
import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage;
import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage;
import net.pterodactylus.sone.web.ajax.DistrustAjaxPage;
+import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage;
import net.pterodactylus.sone.web.ajax.MarkPostAsKnownPage;
import net.pterodactylus.sone.web.ajax.MarkReplyAsKnownPage;
+import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
import net.pterodactylus.sone.web.ajax.TrustAjaxPage;
import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
import net.pterodactylus.util.template.TemplateProvider;
import net.pterodactylus.util.template.XmlFilter;
import net.pterodactylus.util.thread.Ticker;
+import net.pterodactylus.util.version.Version;
import freenet.clients.http.SessionManager;
import freenet.clients.http.SessionManager.Session;
import freenet.clients.http.ToadletContainer;
/** The “Sone locked” notification. */
private final ListNotification<Sone> lockedSonesNotification;
+ /** The “new version” notification. */
+ private final TemplateNotification newVersionNotification;
+
/**
* Creates a new web interface.
*
templateFactory.addFilter("change", new RequestChangeFilter());
templateFactory.addFilter("match", new MatchFilter());
templateFactory.addFilter("css", new CssClassNameFilter());
+ templateFactory.addFilter("js", new JavascriptFilter());
templateFactory.addPlugin("getpage", new GetPagePlugin());
templateFactory.addPlugin("paginate", new PaginationPlugin());
templateFactory.setTemplateProvider(new ClassPathTemplateProvider(templateFactory));
Template lockedSonesTemplate = templateFactory.createTemplate(createReader("/templates/notify/lockedSonesNotification.html"));
lockedSonesNotification = new ListNotification<Sone>("sones-locked-notification", "sones", lockedSonesTemplate);
+
+ Template newVersionTemplate = templateFactory.createTemplate(createReader("/templates/notify/newVersionNotification.html"));
+ newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
}
//
Template createPostTemplate = templateFactory.createTemplate(createReader("/templates/createPost.html"));
Template createReplyTemplate = templateFactory.createTemplate(createReader("/templates/createReply.html"));
Template editProfileTemplate = templateFactory.createTemplate(createReader("/templates/editProfile.html"));
+ Template editProfileFieldTemplate = templateFactory.createTemplate(createReader("/templates/editProfileField.html"));
+ Template deleteProfileFieldTemplate = templateFactory.createTemplate(createReader("/templates/deleteProfileField.html"));
Template viewSoneTemplate = templateFactory.createTemplate(createReader("/templates/viewSone.html"));
Template viewPostTemplate = templateFactory.createTemplate(createReader("/templates/viewPost.html"));
Template deletePostTemplate = templateFactory.createTemplate(createReader("/templates/deletePost.html"));
Template noPermissionTemplate = templateFactory.createTemplate(createReader("/templates/noPermission.html"));
Template optionsTemplate = templateFactory.createTemplate(createReader("/templates/options.html"));
Template aboutTemplate = templateFactory.createTemplate(createReader("/templates/about.html"));
+ Template invalidTemplate = templateFactory.createTemplate(createReader("/templates/invalid.html"));
Template postTemplate = templateFactory.createTemplate(createReader("/templates/include/viewPost.html"));
Template replyTemplate = templateFactory.createTemplate(createReader("/templates/include/viewReply.html"));
pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateSonePage(createSoneTemplate, this), "CreateSone"));
pageToadlets.add(pageToadletFactory.createPageToadlet(new KnownSonesPage(knownSonesTemplate, this), "KnownSones"));
pageToadlets.add(pageToadletFactory.createPageToadlet(new EditProfilePage(editProfileTemplate, this), "EditProfile"));
+ pageToadlets.add(pageToadletFactory.createPageToadlet(new EditProfileFieldPage(editProfileFieldTemplate, this)));
+ pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteProfileFieldPage(deleteProfileFieldTemplate, this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new CreatePostPage(createPostTemplate, this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateReplyPage(createReplyTemplate, this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new ViewSonePage(viewSoneTemplate, this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new AboutPage(aboutTemplate, this, SonePlugin.VERSION), "About"));
pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("noPermission.html", noPermissionTemplate, "Page.NoPermission.Title", this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new DismissNotificationPage(emptyTemplate, this)));
+ pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("invalid.html", invalidTemplate, "Page.Invalid.Title", this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("css/", "/static/css/", "text/css")));
pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("javascript/", "/static/javascript/", "text/javascript")));
pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("images/", "/static/images/", "image/png")));
pageToadlets.add(pageToadletFactory.createPageToadlet(new LikeAjaxPage(this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlikeAjaxPage(this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new GetLikesAjaxPage(this)));
+ pageToadlets.add(pageToadletFactory.createPageToadlet(new EditProfileFieldAjaxPage(this)));
+ pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteProfileFieldAjaxPage(this)));
+ pageToadlets.add(pageToadletFactory.createPageToadlet(new MoveProfileFieldAjaxPage(this)));
ToadletContainer toadletContainer = sonePlugin.pluginRespirator().getToadletContainer();
toadletContainer.getPageMaker().addNavigationCategory("/Sone/index.html", "Navigation.Menu.Name", "Navigation.Menu.Tooltip", sonePlugin);
}
/**
+ * {@inheritDoc}
+ */
+ @Override
+ public void updateFound(Version version, long releaseTime) {
+ newVersionNotification.set("version", version);
+ newVersionNotification.set("releaseTime", releaseTime);
+ notificationManager.addNotification(newVersionNotification);
+ }
+
+ /**
* Template provider implementation that uses
* {@link WebInterface#createReader(String)} to load templates for
* inclusion.
--- /dev/null
+/*
+ * Sone - DeleteProfileFieldAjaxPage.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.ajax;
+
+import net.pterodactylus.sone.data.Profile;
+import net.pterodactylus.sone.data.Profile.Field;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.web.WebInterface;
+import net.pterodactylus.util.json.JsonObject;
+
+/**
+ * AJAX page that lets the user delete a profile field.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class DeleteProfileFieldAjaxPage extends JsonPage {
+
+ /**
+ * Creates a new “delete profile field” AJAX page.
+ *
+ * @param webInterface
+ * The Sone web interface
+ */
+ public DeleteProfileFieldAjaxPage(WebInterface webInterface) {
+ super("deleteProfileField.ajax", webInterface);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected JsonObject createJsonObject(Request request) {
+ String fieldId = request.getHttpRequest().getParam("field");
+ Sone currentSone = getCurrentSone(request.getToadletContext());
+ Profile profile = currentSone.getProfile();
+ Field field = profile.getFieldById(fieldId);
+ if (field == null) {
+ return createErrorJsonObject("invalid-field-id");
+ }
+ profile.removeField(field);
+ currentSone.setProfile(profile);
+ webInterface.getCore().saveSone(currentSone);
+ return createSuccessJsonObject().put("field", new JsonObject().put("id", field.getId()));
+ }
+
+}
return createSuccessJsonObject();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean requiresLogin() {
+ return false;
+ }
+
}
--- /dev/null
+/*
+ * Sone - EditProfileFieldAjaxPage.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.ajax;
+
+import net.pterodactylus.sone.data.Profile;
+import net.pterodactylus.sone.data.Profile.Field;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.web.WebInterface;
+import net.pterodactylus.util.json.JsonObject;
+
+/**
+ * AJAX page that lets the user rename a profile field.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class EditProfileFieldAjaxPage extends JsonPage {
+
+ /**
+ * Creates a new “edit profile field” AJAX page.
+ *
+ * @param webInterface
+ * The Sone web interface
+ */
+ public EditProfileFieldAjaxPage(WebInterface webInterface) {
+ super("editProfileField.ajax", webInterface);
+ }
+
+ //
+ // JSONPAGE METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected JsonObject createJsonObject(Request request) {
+ String fieldId = request.getHttpRequest().getParam("field");
+ Sone currentSone = getCurrentSone(request.getToadletContext());
+ Profile profile = currentSone.getProfile();
+ Field field = profile.getFieldById(fieldId);
+ if (field == null) {
+ return createErrorJsonObject("invalid-field-id");
+ }
+ String name = request.getHttpRequest().getParam("name", "").trim();
+ if (name.length() == 0) {
+ return createErrorJsonObject("invalid-parameter-name");
+ }
+ Field existingField = profile.getFieldByName(name);
+ if ((existingField != null) && !existingField.equals(field)) {
+ return createErrorJsonObject("duplicate-field-name");
+ }
+ field.setName(name);
+ currentSone.setProfile(profile);
+ return createSuccessJsonObject();
+ }
+
+}
Set<Post> newPosts = webInterface.getNewPosts();
JsonArray jsonPosts = new JsonArray();
for (Post post : newPosts) {
- jsonPosts.add(post.getId());
+ JsonObject jsonPost = new JsonObject();
+ jsonPost.put("id", post.getId());
+ jsonPost.put("sone", post.getSone().getId());
+ jsonPost.put("recipient", (post.getRecipient() != null) ? post.getRecipient().getId() : null);
+ jsonPost.put("time", post.getTime());
+ jsonPosts.add(jsonPost);
}
/* load new replies. */
Set<Reply> newReplies = webInterface.getNewReplies();
JsonArray jsonReplies = new JsonArray();
for (Reply reply : newReplies) {
- jsonReplies.add(reply.getId());
+ JsonObject jsonReply = new JsonObject();
+ jsonReply.put("id", reply.getId());
+ jsonReply.put("sone", reply.getSone().getId());
+ jsonReply.put("post", reply.getPost().getId());
+ jsonReply.put("postSone", reply.getPost().getSone().getId());
+ jsonReplies.add(jsonReply);
}
return createSuccessJsonObject().put("sones", jsonSones).put("notifications", jsonNotifications).put("removedNotifications", jsonRemovedNotifications).put("newPosts", jsonPosts).put("newReplies", jsonReplies);
}
return false;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean requiresLogin() {
+ return false;
+ }
+
//
// PRIVATE METHODS
//
return false;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean requiresLogin() {
+ return false;
+ }
+
}
return true;
}
+ /**
+ * Returns whether this page requires the user to be logged in.
+ *
+ * @return {@code true} if the user needs to be logged in to use this page,
+ * {@code false} otherwise
+ */
+ protected boolean requiresLogin() {
+ return true;
+ }
+
//
// PROTECTED METHODS
//
return new Response(401, "Not authorized", "application/json", JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required")));
}
}
+ if (requiresLogin()) {
+ if (getCurrentSone(request.getToadletContext(), false) == null) {
+ return new Response(401, "Not authorized", "application/json", JsonUtils.format(createErrorJsonObject("auth-required")));
+ }
+ }
JsonObject jsonObject = createJsonObject(request);
return new Response(200, "OK", "application/json", JsonUtils.format(jsonObject));
}
return createSuccessJsonObject();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean requiresLogin() {
+ return false;
+ }
+
}
--- /dev/null
+/*
+ * Sone - MoveProfileFieldAjaxPage.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.ajax;
+
+import net.pterodactylus.sone.data.Profile;
+import net.pterodactylus.sone.data.Profile.Field;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.web.WebInterface;
+import net.pterodactylus.util.json.JsonObject;
+
+/**
+ * AJAX page that lets the user move a profile field up or down.
+ *
+ * @see Profile#moveFieldUp(Field)
+ * @see Profile#moveFieldDown(Field)
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class MoveProfileFieldAjaxPage extends JsonPage {
+
+ /**
+ * Creates a new “move profile field” AJAX page.
+ *
+ * @param webInterface
+ * The Sone web interface
+ */
+ public MoveProfileFieldAjaxPage(WebInterface webInterface) {
+ super("moveProfileField.ajax", webInterface);
+ }
+
+ //
+ // JSONPAGE METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected JsonObject createJsonObject(Request request) {
+ Sone currentSone = getCurrentSone(request.getToadletContext());
+ Profile profile = currentSone.getProfile();
+ String fieldId = request.getHttpRequest().getParam("field");
+ Field field = profile.getFieldById(fieldId);
+ if (field == null) {
+ return createErrorJsonObject("invalid-field-id");
+ }
+ String direction = request.getHttpRequest().getParam("direction");
+ try {
+ if ("up".equals(direction)) {
+ profile.moveFieldUp(field);
+ } else if ("down".equals(direction)) {
+ profile.moveFieldDown(field);
+ } else {
+ return createErrorJsonObject("invalid-direction");
+ }
+ } catch (IllegalArgumentException iae1) {
+ return createErrorJsonObject("not-possible");
+ }
+ currentSone.setProfile(profile);
+ webInterface.getCore().saveSone(currentSone);
+ return createSuccessJsonObject();
+ }
+
+}
return createSuccessJsonObject();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean requiresLogin() {
+ return false;
+ }
+
}
import net.pterodactylus.sone.web.page.Page.Request.Method;
import net.pterodactylus.util.logging.Logging;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
import freenet.clients.http.LinkEnabledCallback;
import freenet.clients.http.PageMaker;
private final String path;
/** The template to render. */
- protected final Template template;
+ private final Template template;
/** The L10n handler. */
private final BaseL10n l10n;
pageNode.addForwardLink("icon", shortcutIcon);
}
+ DataProvider dataProvider = template.createDataProvider();
try {
long start = System.nanoTime();
- processTemplate(request, template);
+ processTemplate(request, dataProvider);
long finish = System.nanoTime();
logger.log(Level.FINEST, "Template was rendered in " + ((finish - start) / 1000) / 1000.0 + "ms.");
} catch (RedirectException re1) {
}
StringWriter stringWriter = new StringWriter();
- template.render(stringWriter);
+ template.render(dataProvider, stringWriter);
pageNode.content.addChild("%", stringWriter.toString());
- postProcess(request, template);
+ postProcess(request, dataProvider);
return new Response(200, "OK", "text/html", pageNode.outer.generate());
}
*
* @param request
* The request that is rendered
- * @param template
- * The template to set variables in
+ * @param dataProvider
+ * The data provider to set variables in
* @throws RedirectException
* if the processing page wants to redirect after processing
*/
- protected void processTemplate(Request request, Template template) throws RedirectException {
+ protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
/* do nothing. */
}
/**
* This method will be called after
- * {@link #processTemplate(net.pterodactylus.sone.web.page.Page.Request, Template)}
+ * {@link #processTemplate(net.pterodactylus.sone.web.page.Page.Request, DataProvider)}
* has processed the template and the template was rendered. This method
* will not be called if
- * {@link #processTemplate(net.pterodactylus.sone.web.page.Page.Request, Template)}
+ * {@link #processTemplate(net.pterodactylus.sone.web.page.Page.Request, DataProvider)}
* throws a {@link RedirectException}!
*
* @param request
* The request being processed
- * @param template
- * The template that was rendered
+ * @param dataProvider
+ * The data provider that supplied the rendered data
*/
- protected void postProcess(Request request, Template template) {
+ protected void postProcess(Request request, DataProvider dataProvider) {
/* do nothing. */
}
/**
* Exception that can be thrown to signal that a subclassed {@link Page}
* wants to redirect the user during the
- * {@link TemplatePage#processTemplate(net.pterodactylus.sone.web.page.Page.Request, Template)}
+ * {@link TemplatePage#processTemplate(net.pterodactylus.sone.web.page.Page.Request, DataProvider)}
* method call.
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
Page.EditProfile.Birthday.Label.Day=Day:
Page.EditProfile.Birthday.Label.Month=Month:
Page.EditProfile.Birthday.Label.Year=Year:
-Page.EditProfile.Page.Status.Changed=Your changes have been saved and will be inserted shortly.
+Page.EditProfile.Fields.Title=Custom Fields
+Page.EditProfile.Fields.Description=Here you can enter custom fields into your profile. These fields can contain anything you want and be as terse or as verbose as you wish. Just remember that when it comes to anonymity, sometimes less is more.
+Page.EditProfile.Fields.Button.Edit=edit
+Page.EditProfile.Fields.Button.MoveUp=move up
+Page.EditProfile.Fields.Button.MoveDown=move down
+Page.EditProfile.Fields.Button.Delete=delete
+Page.EditProfile.Fields.Button.ReallyDelete=really delete
+Page.EditProfile.Fields.AddField.Title=Add Field
+Page.EditProfile.Fields.AddField.Label.Name=Name:
+Page.EditProfile.Fields.AddField.Button.AddField=Add Field
Page.EditProfile.Button.Save=Save Profile
+Page.EditProfile.Error.DuplicateFieldName=The field name “{fieldName}” does already exist.
+
+Page.EditProfileField.Title=Edit Profile Field - Sone
+Page.EditProfileField.Page.Title=Edit Profile Field
+Page.EditProfileField.Text=Enter a new name for this profile field.
+Page.EditProfileField.Error.DuplicateFieldName=The field name you entered does already exist.
+Page.EditProfileField.Button.Save=Change
+Page.EditProfileField.Button.Reset=Revert to old name
+Page.EditProfileField.Button.Cancel=Do not change name
+
+Page.DeleteProfileField.Title=Delete Profile Field - Sone
+Page.DeleteProfileField.Page.Title=Delete Profile Field
+Page.DeleteProfileField.Text=Do you really want to delete this profile field?
+Page.DeleteProfileField.Button.Yes=Yes, delete
+Page.DeleteProfileField.Button.No=No, do not delete
Page.CreatePost.Title=Create Post - Sone
Page.CreatePost.Page.Title=Create Post
Page.ViewSone.WriteAMessage=You can write a message to this Sone here. Please note that everybody will be able to read this message!
Page.ViewSone.PostList.Title=Posts by {sone}
Page.ViewSone.PostList.Text.NoPostYet=This Sone has not yet posted anything.
+Page.ViewSone.Profile.Title=Profile
+Page.ViewSone.Profile.Label.Name=Name
Page.ViewPost.Title=View Post - Sone
Page.ViewPost.Page.Title=View Post by {sone}
Page.Logout.Title=Logout - Sone
+Page.Invalid.Title=Invalid Action Performed
+Page.Invalid.Page.Title=Invalid Action Performed
+Page.Invalid.Text=An invalid action was performed, or the action was valid but the parameters were not. Please go back to the {link}index page{/link} and try again. If the error persists you have probably found a bug.
+
View.CreateSone.Text.WotIdentityRequired=To create a Sone you need an identity from the {link}Web of Trust plugin{/link}.
View.CreateSone.Select.Default=Select an identity
View.CreateSone.Text.NoIdentities=You do not have any Web of Trust identities. Please head over to the {link}Web of Trust plugin{/link} and create an identity.
WebInterface.DefaultText.BirthDay=Day
WebInterface.DefaultText.BirthMonth=Month
WebInterface.DefaultText.BirthYear=Year
+WebInterface.DefaultText.FieldName=Field name
WebInterface.DefaultText.Option.InsertionDelay=Time to wait after a Sone is modified before insert (in seconds)
WebInterface.Confirmation.DeletePostButton=Yes, delete!
WebInterface.Confirmation.DeleteReplyButton=Yes, delete!
WebInterface.SelectBox.Yes=Yes
WebInterface.SelectBox.No=No
WebInterface.ClickToShow.Replies=Click here to show hidden replies.
+WebInterface.VersionInformation.CurrentVersion=Current Version:
+WebInterface.VersionInformation.LatestVersion=Latest Version:
Notification.ClickHereToRead=Click here to read the full text of the notification.
Notification.FirstStart.Text=This seems to be the first time you start Sone. To start, create a new Sone from a web of trust identity and start following other Sones.
Notification.SoneRescued.Text=The following Sones have been rescued:
Notification.SoneRescued.Text.RememberToUnlock=Please remember to control the posts and replies you have given and don’t forget to unlock your Sones!
Notification.LockedSones.Text=The following Sones have been locked for more than 5 minutes. Please check if you really want to keep these Sones locked:
+Notification.NewVersion.Text=A new version of the Sone plugin was found: Version {version}.
outline: none;
}
+input[type=text].short {
+ width: 25em;
+}
+
textarea {
height: 4em;
}
+#sone button {
+ background-color: #ddd;
+ border-width: 1px;
+ color: #444;
+ padding: 0.5ex 1.5ex;
+}
+
#sone form {
margin: 0px;
}
color: rgb(255, 172, 0);
}
+#sone a.link {
+ cursor: pointer;
+}
+
#sone a.internet {
color: rgb(255, 0, 0);
}
display: inline;
}
-#sone #create-sone {
+#sone .profile-field, #sone #edit-profile button[type=submit], #sone #delete-profile-field {
+ margin-top: 1em;
+}
+#sone .profile-field .name {
+ display: inline;
+ font-weight: bold;
+}
+
+#sone .profile-field .name.hidden {
+ display: none;
+}
+
+#sone .profile-field button.confirm {
+ font-weight: bold;
+ color: #080;
+}
+
+#sone .profile-field button.cancel {
+ font-weight: bold;
+ color: red;
+}
+
+#sone .profile-field .value {
+ margin-left: 2em;
+}
+
+#sone #edit-profile .profile-field .value {
+ margin-left: inherit;
+}
+
+#sone .profile-field .edit-field-name, #sone .profile-field .move-up-field, #sone .profile-field .move-down-field, #sone .profile-field .delete-field-name {
+ float: right;
+ margin-top: -1ex;
}
#sone #tail {
color: #888;
}
+#sone #tail #version-information {
+ margin-top: 1em;
+}
+
#sone #add-sone textarea, #sone #create-sone textarea, #sone #load-sone textarea, #sone #edit-profile textarea {
height: 1.5em;
}
}
#sone .confirm {
- font-weight: bold !important;
- color: red !important;
+ font-weight: bold;
+ color: red;
}
postReply(postId, text, function(success, error, replyId) {
if (success) {
$(inputField).val("");
- loadNewReply(replyId);
+ loadNewReply(replyId, getCurrentSoneId(), postId);
markPostAsKnown(getPostElement(inputField));
$("#sone .post#" + postId + " .create-reply").addClass("hidden");
} else {
});
/* mark everything as known on click. */
- $(postElement).click(function() {
+ $(postElement).click(function(event) {
+ if ($(event.target).hasClass("click-to-show")) {
+ return false;
+ }
markPostAsKnown(this);
});
});
/* process new posts. */
$.each(data.newPosts, function(index, value) {
- loadNewPost(value);
+ loadNewPost(value.id, value.sone, value.recipient, value.time);
});
/* process new replies. */
$.each(data.newReplies, function(index, value) {
- loadNewReply(value);
+ loadNewReply(value.id, value.sone, value.post, value.postSone);
});
/* do it again in 5 seconds. */
setTimeout(getStatus, 5000);
}
/**
+ * Returns the ID of the currently logged in Sone.
+ *
+ * @return The ID of the current Sone, or an empty string if no Sone is logged
+ * in
+ */
+function getCurrentSoneId() {
+ return $("#currentSoneId").text();
+}
+
+/**
* Returns the content of the page-id attribute.
*
* @returns The page ID
return $("#sone .reply#" + replyId).length > 0;
}
-function loadNewPost(postId) {
+function loadNewPost(postId, soneId, recipientId, time) {
if (hasPost(postId)) {
return;
}
+ if (!isIndexPage()) {
+ if (!isViewPostPage() || (getShownPostId() != postId)) {
+ if (!isViewSonePage() || ((getShownSoneId() != soneId) && (getShownSoneId() != recipientId))) {
+ return;
+ }
+ }
+ }
+ if (getPostTime($("#sone .post").last()) > time) {
+ return;
+ }
$.getJSON("getPost.ajax", { "post" : postId }, function(data, textStatus) {
if ((data != null) && data.success) {
if (hasPost(data.post.id)) {
newPost = $(data.post.html).addClass("hidden");
if (firstOlderPost != null) {
newPost.insertBefore(firstOlderPost);
- } else {
- $("#sone #posts").append(newPost);
}
ajaxifyPost(newPost);
newPost.slideDown();
});
}
-function loadNewReply(replyId) {
+function loadNewReply(replyId, soneId, postId, postSoneId) {
if (hasReply(replyId)) {
return;
}
+ if (!hasPost(postId)) {
+ return;
+ }
$.getJSON("getReply.ajax", { "reply": replyId }, function(data, textStatus) {
/* find post. */
if ((data != null) && data.success) {
(function(postElement) {
$.getJSON("markPostAsKnown.ajax", {"formPassword": getFormPassword(), "post": getPostId(postElement)}, function(data, textStatus) {
$(postElement).removeClass("new");
+ $(".click-to-show", postElement).removeClass("new");
});
})(postElement);
}
$("#sone .notification#" + notificationId + " .short-text").hide();
}
+/**
+ * Deletes the field with the given ID from the profile.
+ *
+ * @param fieldId
+ * The ID of the field to delete
+ */
+function deleteProfileField(fieldId) {
+ $.getJSON("deleteProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId}, function(data, textStatus) {
+ if (data && data.success) {
+ $("#sone .profile-field#" + data.field.id).slideUp();
+ }
+ });
+}
+
+/**
+ * Renames a profile field.
+ *
+ * @param fieldId
+ * The ID of the field to rename
+ * @param newName
+ * The new name of the field
+ * @param successFunction
+ * Called when the renaming was successful
+ */
+function editProfileField(fieldId, newName, successFunction) {
+ $.getJSON("editProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId, "name": newName}, function(data, textStatus) {
+ if (data && data.success) {
+ successFunction();
+ }
+ });
+}
+
+/**
+ * Moves the profile field with the given ID one slot in the given direction.
+ *
+ * @param fieldId
+ * The ID of the field to move
+ * @param direction
+ * The direction to move in (“up” or “down”)
+ * @param successFunction
+ * Function to call on success
+ */
+function moveProfileField(fieldId, direction, successFunction) {
+ $.getJSON("moveProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId, "direction": direction}, function(data, textStatus) {
+ if (data && data.success) {
+ successFunction();
+ }
+ });
+}
+
+/**
+ * Moves the profile field with the given ID up one slot.
+ *
+ * @param fieldId
+ * The ID of the field to move
+ * @param successFunction
+ * Function to call on success
+ */
+function moveProfileFieldUp(fieldId, successFunction) {
+ moveProfileField(fieldId, "up", successFunction);
+}
+
+/**
+ * Moves the profile field with the given ID down one slot.
+ *
+ * @param fieldId
+ * The ID of the field to move
+ * @param successFunction
+ * Function to call on success
+ */
+function moveProfileFieldDown(fieldId, successFunction) {
+ moveProfileField(fieldId, "down", successFunction);
+}
+
//
// EVERYTHING BELOW HERE IS EXECUTED AFTER LOADING THE PAGE
//
text = $(this).find(":input:enabled").val();
$.getJSON("createPost.ajax", { "formPassword": getFormPassword(), "text": text }, function(data, textStatus) {
if ((data != null) && data.success) {
- loadNewPost(data.postId);
+ loadNewPost(data.postId, getCurrentSoneId());
}
});
$(this).find(":input:enabled").val("").blur();
text = $(this).find(":input:enabled").val();
$.getJSON("createPost.ajax", { "formPassword": getFormPassword(), "recipient": getShownSoneId(), "text": text }, function(data, textStatus) {
if ((data != null) && data.success) {
- loadNewPost(data.postId);
+ loadNewPost(data.postId, getCurrentSoneId());
}
});
$(this).find(":input:enabled").val("").blur();
--- /dev/null
+<%include include/head.html>
+
+ <h1><%= Page.DeleteProfileField.Page.Title|l10n|html></h1>
+
+ <p><%= Page.DeleteProfileField.Text|l10n|html></p>
+
+ <div class="profile-field">
+ <div class="name"><% field.name|html></div>
+ <div class="value"><% field.value|html></div>
+ </div>
+
+ <form id="delete-profile-field" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="field" value="<% field.id|html>" />
+ <button type="submit" name="confirm" value="true"><%= Page.DeleteProfileField.Button.Yes|l10n|html></button>
+ <button type="submit" name="cancel" value="true"><%= Page.DeleteProfileField.Button.No|l10n|html></button>
+ </form>
+
+<%include include/tail.html>
<%include include/head.html>
<script language="javascript">
- $(document).ready(function() {
+ function recheckMoveButtons() {
+ $("#sone .profile-field").each(function() {
+ $(".move-up-field", this).toggleClass("hidden", $(this).prev(".profile-field").length == 0);
+ $(".move-down-field", this).toggleClass("hidden", $(this).next(".profile-field").length == 0);
+ });
+ }
+
+ $(function() {
getTranslation("WebInterface.DefaultText.FirstName", function(firstNameDefaultText) {
registerInputTextareaSwap("#sone #edit-profile input[name=first-name]", firstNameDefaultText, "first-name", true, true);
});
getTranslation("WebInterface.DefaultText.BirthYear", function(birthYearDefaultText) {
registerInputTextareaSwap("#sone #edit-profile input[name=birth-year]", birthYearDefaultText, "birth-year", true, true);
});
+ getTranslation("WebInterface.DefaultText.FieldName", function(fieldNameDefaultText) {
+ registerInputTextareaSwap("#sone #add-profile-field input[name=field-name]", fieldNameDefaultText, "field-name", true, true);
+ });
+
+ <%foreach fields field>
+ registerInputTextareaSwap("#sone #edit-profile input[name=field-<% loop.count>]", <% field.key|js>, "field-<% loop.count>", true, true);
+ <%/foreach>
/* hide all the labels. */
- $("#sone #edit-profile label").hide();
+ $("#sone #edit-profile label, #sone #add-profile-field label").hide();
+
+ /* ajaxify the delete buttons. */
+ getTranslation("Page.EditProfile.Fields.Button.ReallyDelete", function(reallyDeleteText) {
+ $("#sone #edit-profile .delete-field-name button").each(function() {
+ confirmButton = $(this).clone().addClass("hidden").addClass("confirm").text(reallyDeleteText).insertAfter(this);
+ (function(deleteButton, confirmButton) {
+ deleteButton.click(function() {
+ deleteButton.fadeOut("slow", function() {
+ confirmButton.fadeIn("slow");
+ $(document).one("click", function() {
+ if (this != confirmButton.get(0)) {
+ confirmButton.fadeOut("slow", function() {
+ deleteButton.fadeIn("slow");
+ });
+ }
+ return false;
+ });
+ });
+ return false;
+ });
+ confirmButton.click(function() {
+ confirmButton.fadeOut("slow");
+ buttonName = confirmButton.attr("name");
+ fieldId = buttonName.substring("delete-field-".length);
+ deleteProfileField(fieldId);
+ recheckMoveButtons();
+ return false;
+ });
+ })($(this), confirmButton);
+ });
+ });
+
+ /* ajaxify the edit button. */
+ $("#sone #edit-profile .edit-field-name button").each(function() {
+ profileField = $(this).parents(".profile-field");
+ fieldNameElement = profileField.find(".name");
+ inputField = $("input[type=text].short", profileField);
+ confirmButton = $("button.confirm", profileField);
+ cancelButton = $("button.cancel", profileField);
+ (function(editButton, inputField, confirmButton, cancelButton, fieldNameElement) {
+ cleanUp = function(editButton, inputField, confirmButton, cancelButton, fieldNameElement) {
+ editButton.removeAttr("disabled");
+ inputField.addClass("hidden");
+ confirmButton.addClass("hidden");
+ cancelButton.addClass("hidden");
+ fieldNameElement.removeClass("hidden");
+ };
+ confirmButton.click(function() {
+ inputField.attr("disabled", "disabled");
+ confirmButton.attr("disabled", "disabled");
+ cancelButton.attr("disabled", "disabled");
+ editProfileField(confirmButton.parents(".profile-field").attr("id"), inputField.val(), function() {
+ fieldNameElement.text(inputField.val());
+ cleanUp(editButton, inputField, confirmButton, cancelButton, fieldNameElement);
+ });
+ return false;
+ });
+ cancelButton.click(function() {
+ cleanUp(editButton, inputField, confirmButton, cancelButton, fieldNameElement);
+ return false;
+ });
+ inputField.keypress(function(event) {
+ if (event.which == 13) {
+ confirmButton.click();
+ return false;
+ } else if (event.which == 27) {
+ cancelButton.click();
+ return false;
+ }
+ });
+ editButton.click(function() {
+ editButton.attr("disabled", "disabled");
+ fieldNameElement.addClass("hidden");
+ inputField.removeAttr("disabled").val(fieldNameElement.text()).removeClass("hidden").focus().select();
+ confirmButton.removeAttr("disabled").removeClass("hidden");
+ cancelButton.removeAttr("disabled").removeClass("hidden");
+ return false;
+ });
+ })($(this), inputField, confirmButton, cancelButton, fieldNameElement);
+ });
+
+ /* ajaxify “move up” and “move down” buttons. */
+ $("#sone .profile-field .move-down-field button").click(function() {
+ profileField = $(this).parents(".profile-field");
+ moveProfileFieldDown(profileField.attr("id"), function() {
+ next = profileField.next();
+ current = profileField.insertAfter(next);
+ recheckMoveButtons();
+ });
+ return false;
+ });
+ $("#sone .profile-field .move-up-field button").click(function() {
+ profileField = $(this).parents(".profile-field");
+ moveProfileFieldUp(profileField.attr("id"), function() {
+ previous = profileField.prev();
+ current = profileField.insertBefore(previous);
+ recheckMoveButtons();
+ });
+ return false;
+ });
});
</script>
<p><%= Page.EditProfile.Page.Description|l10n|html></p>
<p><%= Page.EditProfile.Page.Hint.Optionality|l10n|html></p>
- <%if changed>
- <p><%= Page.EditProfile.Page.Status.Changed|l10n|html></p>
- <%/if>
-
<form id="edit-profile" method="post">
<input type="hidden" name="formPassword" value="<% formPassword|html>" />
</div>
<div>
- <button type="submit"><%= Page.EditProfile.Button.Save|l10n|html></button>
+ <button type="submit" name="save-profile" value="true"><%= Page.EditProfile.Button.Save|l10n|html></button>
+ </div>
+
+ <h1><%= Page.EditProfile.Fields.Title|l10n|html></h1>
+
+ <p><%= Page.EditProfile.Fields.Description|l10n|html></p>
+
+ <%foreach fields field fieldLoop>
+ <div class="profile-field" id="<% field.id|html>">
+ <div class="name"><% field.name|html></div>
+ <input class="short hidden" type="text"><button class="confirm hidden" type="button">✔</button><button class="cancel hidden" type="button">✘</button>
+ <div class="edit-field-name"><button type="submit" name="edit-field-<% field.id|html>" value="true"><%= Page.EditProfile.Fields.Button.Edit|l10n|html></button></div>
+ <div class="delete-field-name"><button type="submit" name="delete-field-<% field.id|html>" value="true"><%= Page.EditProfile.Fields.Button.Delete|l10n|html></button></div>
+ <div class="<%if fieldLoop.last>hidden <%/if>move-down-field"><button type="submit" name="move-down-field-<% field.id|html>" value="true"><%= Page.EditProfile.Fields.Button.MoveDown|l10n|html></button></div>
+ <div class="<%if fieldLoop.first>hidden <%/if>move-up-field"><button type="submit" name="move-up-field-<% field.id|html>" value="true"><%= Page.EditProfile.Fields.Button.MoveUp|l10n|html></button></div>
+ <div class="value"><input type="text" name="field-<% field.id|html>" value="<% field.value|html>" /></div>
+ </div>
+
+ <%if fieldLoop.last>
+ <div>
+ <button type="submit" name="save-profile" value="true"><%= Page.EditProfile.Button.Save|l10n|html></button>
+ </div>
+ <%/if>
+ <%/foreach>
+
+ </form>
+
+ <form id="add-profile-field" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+
+ <a name="profile-fields"></a>
+ <h2><%= Page.EditProfile.Fields.AddField.Title|l10n|html></h2>
+
+ <%if duplicateFieldName>
+ <p><%= Page.EditProfile.Error.DuplicateFieldName|l10n|replace needle="{fieldName}" replacementKey="fieldName"|html></p>
+ <%/if>
+
+ <div id="new-field">
+ <label for="new-field"><%= Page.EditProfile.Fields.AddField.Label.Name|l10n|html></label>
+ <input type="text" name="field-name" value="" />
+ <button type="submit" name="add-field" value="true"><%= Page.EditProfile.Fields.AddField.Button.AddField|l10n|html></button>
</div>
</form>
--- /dev/null
+<%include include/head.html>
+
+ <h1><%= Page.EditProfileField.Page.Title|l10n|html></h1>
+
+ <p><%= Page.EditProfileField.Text|l10n|html></p>
+
+ <%if duplicateFieldName>
+ <p><%= Page.EditProfileField.Error.DuplicateFieldName|l10n|html></p>
+ <%/if>
+
+ <form method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="field" value="<% field.id|html>" />
+ <div>
+ <input type="text" name="name" value="<% field.name|html>" />
+ <button type="submit" name="save" value="true"><%= Page.EditProfileField.Button.Save|l10n|html></button>
+ </div>
+ <p>
+ <button type="reset"><%= Page.EditProfileField.Button.Reset|l10n|html></button>
+ <button type="submit" name="cancel" value="true"><%= Page.EditProfileField.Button.Cancel|l10n|html></button>
+ </p>
+ </form>
+
+<%include include/tail.html>
<div id="sone" class="<%ifnull ! currentSone>online<%else>offline<%/if>">
<div id="formPassword"><% formPassword|html></div>
+ <div id="currentSoneId" class="hidden"><% currentSone.id|html></div>
<script src="javascript/jquery-1.4.2.js" language="javascript"></script>
<script src="javascript/sone.js" language="javascript"></script>
<img src="images/flattr-badge-large.png" alt="Flattr Sone" title="Flattr Sone" />
</a>
</div>
+
+ <div id="version-information">
+ <div class="current-version"><%= WebInterface.VersionInformation.CurrentVersion|l10n|html> <b><% currentVersion|html></b></div>
+ <%if hasLatestVersion>
+ <div class="latest-version"><%= WebInterface.VersionInformation.LatestVersion|l10n|html> <b><% latestVersion|html></b></div>
+ <%/if>
+ </div>
</div>
</div>
<h1><%= Page.Index.PostList.Title|l10n|html></h1>
<div id="posts">
- <%getpage>
- <%paginate list=posts pagesize=25>
<%= page|store key=pageParameter>
<%include include/pagination.html>
<%foreach pagination.items post>
<birth-day><% currentSone.profile.birthDay|xml></birth-day>
<birth-month><% currentSone.profile.birthMonth|xml></birth-month>
<birth-year><% currentSone.profile.birthYear|xml></birth-year>
+ <fields>
+ <%foreach currentSone.profile.fields field>
+ <field>
+ <field-name><% field.key|xml></field-name>
+ <field-value><% field.value|xml></field-value>
+ </field>
+ <%/foreach>
+ </fields>
</profile>
<posts>
--- /dev/null
+<%include include/head.html>
+
+ <h1><%= Page.Invalid.Page.Title|l10n|html></h1>
+
+ <p><%= Page.Invalid.Text|l10n|html|replace needle="{link}" replacement='<a href="index.html">'|replace needle="{/link}" replacement='</a>'></p>
+
+<%include include/tail.html>
<div class="short-text">
<%= Notification.NewPost.ShortText|l10n|html>
- <a href="javascript:showNotificationDetails('<%notification.id|html>'); return false;"><%= Notification.ClickHereToRead|l10n|html></a>
+ <a class="link" onclick="showNotificationDetails('<%notification.id|html>'); return false;"><%= Notification.ClickHereToRead|l10n|html></a>
</div>
<div class="text hidden">
<%= Notification.NewPost.Text|l10n|html>
<div class="short-text">
<%= Notification.NewReply.ShortText|l10n|html>
- <a href="javascript:showNotificationDetails('<%notification.id|html>'); return false;"><%= Notification.ClickHereToRead|l10n|html></a>
+ <a class="link" onclick="showNotificationDetails('<%notification.id|html>'); return false;"><%= Notification.ClickHereToRead|l10n|html></a>
</div>
<div class="text hidden">
<%= Notification.NewReply.Text|l10n|html>
<div class="short-text">
<%= Notification.NewSone.ShortText|l10n|html>
- <a href="javascript:showNotificationDetails('<%notification.id|html>'); return false;"><%= Notification.ClickHereToRead|l10n|html></a>
+ <a class="link" onclick="showNotificationDetails('<%notification.id|html>'); return false;"><%= Notification.ClickHereToRead|l10n|html></a>
</div>
<div class="text hidden">
<%= Notification.NewSone.Text|l10n|html>
--- /dev/null
+<div class="text"><%= Notification.NewVersion.Text|l10n|html|replace needle="{version}" replacementKey=version></div>
<%if ! sone.current>
<%include include/viewSone.html>
+ <%/if>
+
+ <h1><%= Page.ViewSone.Profile.Title|l10n|html></h1>
+ <div class="profile-field">
+ <div class="name"><%= Page.ViewSone.Profile.Label.Name|l10n|html></div>
+ <div class="value"><% sone.niceName|html></div>
+ </div>
+
+ <%foreach sone.profile.fields field>
+ <div class="profile-field">
+ <div class="name"><% field.name|html></div>
+ <div class="value"><% field.value|html></div>
+ </div>
+ <%/foreach>
+
+ <%if ! sone.current>
<p><%= Page.ViewSone.WriteAMessage|l10n|html></p>
<form action="createPost.html" id="post-message" method="post">
</form>
<%/if>
-
<h1><%= Page.ViewSone.PostList.Title|l10n|insert needle="{sone}" key=sone.niceName|html></h1>
<div id="posts">