some udpates to stuff, getting started on influence, map udpates for workflow ease of use, updates to map z-idexes for visibility
This commit is contained in:
parent
b61e48f4fc
commit
83f5055471
@ -0,0 +1,271 @@
|
||||
---
|
||||
title: "API Reference | Represent Elected Officials and Electoral Districts API for Canada"
|
||||
source: "https://represent.opennorth.ca/api/"
|
||||
author:
|
||||
published:
|
||||
created: 2025-09-17
|
||||
description: "Find the elected officials and electoral districts for any Canadian address or postal code, at all levels of government"
|
||||
tags:
|
||||
- "clippings"
|
||||
---
|
||||
### Endpoints
|
||||
|
||||
The base URL of all endpoints is `https://represent.opennorth.ca`. All endpoints output JSON.
|
||||
|
||||
- [Postal codes](https://represent.opennorth.ca/api/#postcode)
|
||||
- [Boundary sets](https://represent.opennorth.ca/api/#boundaryset)
|
||||
- [Boundaries](https://represent.opennorth.ca/api/#boundary)
|
||||
- [Representative sets](https://represent.opennorth.ca/api/#representativeset)
|
||||
- [Representatives](https://represent.opennorth.ca/api/#representative)
|
||||
- [Elections](https://represent.opennorth.ca/api/#election)
|
||||
- [Candidates](https://represent.opennorth.ca/api/#candidate)
|
||||
|
||||
### Paginate
|
||||
|
||||
Results are paginated 20 per page by default. Set the number of results per page by adding a `limit` query parameter. Change pages using the `offset` query parameter or using the `next` and `previous` links under the `meta` field in the response to navigate to the next and previous pages (if any). Under the `meta` field, `total_count` is the number of results.
|
||||
|
||||
### Filter results
|
||||
|
||||
Filter results with query parameters. Each endpoint below lists the fields on which you can filter results. To filter for representatives whose first name is “Rodney”, for example, request `/representatives/?first_name=Rodney`. To filter for MPs whose first name is "Rodney", request `/representatives/house-of-commons/?last_name=Rodney`.
|
||||
|
||||
Perform substring searches by appending `__querytype` to the parameter name, where `querytype` is one of `iexact`, `contains`, `icontains`, `startswith`, `istartswith`, `endswith`, `iendswith` or `isnull`. A leading `i` makes the match case-insensitive. For example, to find representatives whose last name starts with “M” or “m”, request `/representatives/?last_name__istartswith=m`.
|
||||
|
||||
### Download in bulk
|
||||
|
||||
To download all representatives, send a request to [https://represent.opennorth.ca/representatives/?limit=1000](https://represent.opennorth.ca/representatives/?limit=1000) and follow the `next` link under the `meta` field until you reach the end. We host the shapefiles and postal code concordances on [GitHub](https://github.com/opennorth/represent-canada-data).
|
||||
|
||||
### Rate limits
|
||||
|
||||
Represent is free up to 60 requests per minute (86,400 queries/day). If you need to make more queries, [contact us](https://represent.opennorth.ca/api/); otherwise, you may get HTTP 503 errors.
|
||||
|
||||
### Debugging
|
||||
|
||||
For a browsable, HTML version of the JSON response, add a `format=apibrowser` query parameter. Add `pretty=1` to just indent the raw JSON.
|
||||
|
||||
### JSONP
|
||||
|
||||
We support JSONP for client-side cross-domain requests – just add a `callback` query parameter.
|
||||
|
||||
### Libraries
|
||||
|
||||
- [Drupal](https://drupal.org/project/represent)
|
||||
- [WordPress](https://wordpress.org/plugins/represent-api/)
|
||||
- [Ruby](https://github.com/opennorth/govkit-ca#readme)
|
||||
- [Ruby](https://github.com/cpb/opennorth-represent#readme) by Caleb Buxton
|
||||
- [Python](https://github.com/ncadou/pyrepresent#readme) by Nicolas Cadou
|
||||
- [Node.js](https://github.com/sprice/represent#readme) by Shawn Price
|
||||
- [CiviCRM](https://drupal.org/project/civinorth) by Alan Dixon
|
||||
|
||||
[Privacy policy](https://represent.opennorth.ca/privacy/)
|
||||
|
||||
Find representatives and boundaries by postal code.
|
||||
|
||||
To see what boundary sets and representative sets are available, consult the [boundary sets](https://represent.opennorth.ca/api/#boundaryset) and [representative sets](https://represent.opennorth.ca/api/#representativeset) endpoints. Are we missing information that you need? [Contact us](https://represent.opennorth.ca/api/) so that we can make it a priority.
|
||||
|
||||
### Request
|
||||
|
||||
URLs must include the postal code in uppercase letters with no spaces.
|
||||
|
||||
### Response
|
||||
|
||||
The `boundaries_centroid` field lists boundaries that contain the postal code’s center point (centroid). A centroid is a point, but a postal code can be a line or polygon, so the list of boundaries in `boundaries_centroid` **will sometimes be inaccurate**.
|
||||
|
||||
The `boundaries_concordance` field lists boundaries linked to the postal code according to official government data. Postal codes can cross boundaries, therefore `boundaries_concordance` may list many Ontario provincial districts for a postal code like K0A 1K0.
|
||||
|
||||
The `representatives_centroid` and `representatives_concordance` fields behave similarly.
|
||||
|
||||
In most cases, the `city`, `province` and `centroid` fields will be non-empty.
|
||||
|
||||
Find representatives and boundaries by postal code
|
||||
|
||||
[/postcodes/L5G4L3/](https://represent.opennorth.ca/postcodes/L5G4L3/?format=apibrowser) Click to view JSON
|
||||
|
||||
Find representatives and boundaries by postal code, limiting results to a specific boundary set
|
||||
|
||||
[/postcodes/L5G4L3/?sets=federal-electoral-districts](https://represent.opennorth.ca/postcodes/L5G4L3/?sets=federal-electoral-districts&format=apibrowser)
|
||||
|
||||
To see what boundary sets are available, consult the [boundary sets](https://represent.opennorth.ca/api/#boundaryset) endpoint.
|
||||
|
||||
A boundary set is a group of electoral districts, like BC provincial districts or Toronto wards.
|
||||
|
||||
Do we not have a set of boundaries that you need? [Contact us](https://represent.opennorth.ca/api/) so that we can make it a priority.
|
||||
|
||||
Get one page of boundary sets
|
||||
|
||||
[/boundary-sets/](https://represent.opennorth.ca/boundary-sets/?format=apibrowser) Click to view JSON
|
||||
|
||||
Get one boundary set
|
||||
|
||||
[/boundary-sets/federal-electoral-districts/](https://represent.opennorth.ca/boundary-sets/federal-electoral-districts/?format=apibrowser)
|
||||
|
||||
Filter boundary sets by `name` or `domain`
|
||||
|
||||
[/boundary-sets/?domain=Canada](https://represent.opennorth.ca/boundary-sets/?domain=Canada&format=apibrowser)
|
||||
|
||||
The response's `external_id` field (not always present) is the boundary's machine identifier. The `metadata` field contains all attributes from the source shapefile; it is unmodified and may be out-of-date or erroneous.
|
||||
|
||||
Get one page of boundaries
|
||||
|
||||
[/boundaries/](https://represent.opennorth.ca/boundaries/?format=apibrowser) Click to view JSON
|
||||
|
||||
Get one page of boundaries from a boundary set
|
||||
|
||||
[/boundaries/toronto-wards-2018/](https://represent.opennorth.ca/boundaries/toronto-wards-2018/?format=apibrowser)
|
||||
|
||||
To see what boundary sets are available, consult the [boundary sets](https://represent.opennorth.ca/api/#boundaryset) endpoint.
|
||||
|
||||
Get one page of boundaries from multiple boundary sets (comma-separated)
|
||||
|
||||
[/boundaries/?sets=toronto-wards-2018,ottawa-wards](https://represent.opennorth.ca/boundaries/?sets=toronto-wards-2018,ottawa-wards&format=apibrowser)
|
||||
|
||||
To see what boundary sets are available, consult the [boundary sets](https://represent.opennorth.ca/api/#boundaryset) endpoint.
|
||||
|
||||
Get one boundary
|
||||
|
||||
[/boundaries/nova-scotia-electoral-districts-2019/halifax-atlantic/](https://represent.opennorth.ca/boundaries/nova-scotia-electoral-districts-2019/halifax-atlantic/?format=apibrowser)
|
||||
|
||||
Filter all boundaries by `name` or `external_id`
|
||||
|
||||
[/boundaries/?name=Niagara Falls](https://represent.opennorth.ca/boundaries/?name=Niagara%20Falls&format=apibrowser)
|
||||
|
||||
Filter a boundary set's boundaries by `name` or `external_id`
|
||||
|
||||
[/boundaries/census-subdivisions/?name=Niagara Falls](https://represent.opennorth.ca/boundaries/census-subdivisions/?name=Niagara%20Falls&format=apibrowser)
|
||||
|
||||
To see what boundary sets are available, consult the [boundary sets](https://represent.opennorth.ca/api/#boundaryset) endpoint.
|
||||
|
||||
### Geospatial queries
|
||||
|
||||
Find all boundaries by latitude and longitude
|
||||
|
||||
[/boundaries/?contains=45.524,-73.596](https://represent.opennorth.ca/boundaries/?contains=45.524,-73.596&format=apibrowser)
|
||||
|
||||
Find a boundary set's boundaries by latitude and longitude
|
||||
|
||||
[/boundaries/montreal-boroughs/?contains=45.524,-73.596](https://represent.opennorth.ca/boundaries/montreal-boroughs/?contains=45.524,-73.596&format=apibrowser)
|
||||
|
||||
To see what boundary sets are available, consult the [boundary sets](https://represent.opennorth.ca/api/#boundaryset) endpoint.
|
||||
|
||||
Find boundaries that touch
|
||||
|
||||
[/boundaries/?touches=alberta-electoral-districts-2017/highwood](https://represent.opennorth.ca/boundaries/?touches=alberta-electoral-districts-2017/highwood&format=apibrowser)
|
||||
|
||||
Find boundaries that intersect (“covers or overlaps” in PostGIS lingo)
|
||||
|
||||
[/boundaries/?intersects=alberta-electoral-districts-2017/highwood](https://represent.opennorth.ca/boundaries/?intersects=alberta-electoral-districts-2017/highwood&format=apibrowser)
|
||||
|
||||
### Drawing boundaries
|
||||
|
||||
We recommend the `simple_shape` endpoint, which simplifies the shape to a tolerance of 0.002, looks fine and loads fast. The default geospatial output format is GeoJSON. Add a `format=kml` or `format=wkt` query parameter to get KML or Well-Known Text.
|
||||
|
||||
Get all simple shapes from a boundary set
|
||||
|
||||
[/boundaries/toronto-wards-2018/simple\_shape](https://represent.opennorth.ca/boundaries/toronto-wards-2018/simple_shape?format=apibrowser)
|
||||
|
||||
Get all original shapes from a boundary set
|
||||
|
||||
[/boundaries/toronto-wards-2018/shape](https://represent.opennorth.ca/boundaries/toronto-wards-2018/shape?format=apibrowser)
|
||||
|
||||
Get all centroids from a boundary set
|
||||
|
||||
[/boundaries/toronto-wards-2018/centroid](https://represent.opennorth.ca/boundaries/toronto-wards-2018/centroid?format=apibrowser)
|
||||
|
||||
Get one boundary's simple shape
|
||||
|
||||
[/boundaries/nova-scotia-electoral-districts-2019/halifax-atlantic/simple\_shape](https://represent.opennorth.ca/boundaries/nova-scotia-electoral-districts-2019/halifax-atlantic/simple_shape?format=apibrowser)
|
||||
|
||||
Get one boundary's original shape
|
||||
|
||||
[/boundaries/nova-scotia-electoral-districts-2019/halifax-atlantic/shape](https://represent.opennorth.ca/boundaries/nova-scotia-electoral-districts-2019/halifax-atlantic/shape?format=apibrowser)
|
||||
|
||||
Get one boundary's centroid
|
||||
|
||||
[/boundaries/nova-scotia-electoral-districts-2019/halifax-atlantic/centroid](https://represent.opennorth.ca/boundaries/nova-scotia-electoral-districts-2019/halifax-atlantic/centroid?format=apibrowser)
|
||||
|
||||
A representative set is a group of elected officials, like the House of Commons or Toronto City Council.
|
||||
|
||||
Do we not have a set of representatives that you need? [Contact us](https://represent.opennorth.ca/api/) so that we can make it a priority.
|
||||
|
||||
Get one page of representative sets
|
||||
|
||||
[/representative-sets/](https://represent.opennorth.ca/representative-sets/?format=apibrowser) Click to view JSON
|
||||
|
||||
Get one representative set
|
||||
|
||||
[/representative-sets/ontario-legislature/](https://represent.opennorth.ca/representative-sets/ontario-legislature/?format=apibrowser)
|
||||
|
||||
Get one page of representatives
|
||||
|
||||
[/representatives/](https://represent.opennorth.ca/representatives/?format=apibrowser) Click to view JSON
|
||||
|
||||
Get one page of representatives from a representative set
|
||||
|
||||
[/representatives/house-of-commons/](https://represent.opennorth.ca/representatives/house-of-commons/?format=apibrowser)
|
||||
|
||||
To see what representative sets are available, consult the [representative sets](https://represent.opennorth.ca/api/#representativeset) endpoint.
|
||||
|
||||
Find all representatives by latitude and longitude
|
||||
|
||||
[/representatives/?point=45.524,-73.596](https://represent.opennorth.ca/representatives/?point=45.524,-73.596&format=apibrowser)
|
||||
|
||||
Find a representative set's representatives by latitude and longitude
|
||||
|
||||
[/representatives/house-of-commons/?point=45.524,-73.596](https://represent.opennorth.ca/representatives/house-of-commons/?point=45.524,-73.596&format=apibrowser)
|
||||
|
||||
To see what representative sets are available, consult the [representative sets](https://represent.opennorth.ca/api/#representativeset) endpoint.
|
||||
|
||||
Get the representatives for one boundary
|
||||
|
||||
[/boundaries/toronto-wards-2018/etobicoke-north-1/representatives/](https://represent.opennorth.ca/boundaries/toronto-wards-2018/etobicoke-north-1/representatives/?format=apibrowser)
|
||||
|
||||
Get the representatives for multiple boundaries (comma-separated)
|
||||
|
||||
[/representatives/?districts=calgary-wards/ward-1,calgary-wards/ward-2,calgary-wards/ward-3](https://represent.opennorth.ca/representatives/?districts=calgary-wards/ward-1,calgary-wards/ward-2,calgary-wards/ward-3&format=apibrowser)
|
||||
|
||||
Filter all representatives by `name`, `first_name`, `last_name`, `gender`, `district_name`, `elected_office` or `party_name`
|
||||
|
||||
[/representatives/?last\_name=Trudeau](https://represent.opennorth.ca/representatives/?last_name=Trudeau&format=apibrowser)
|
||||
|
||||
Filter a representative set's representatives by `name`, `first_name`, `last_name`, `gender`, `district_name`, `elected_office` or `party_name`
|
||||
|
||||
[/representatives/house-of-commons/?last\_name=Trudeau](https://represent.opennorth.ca/representatives/house-of-commons/?last_name=Trudeau&format=apibrowser)
|
||||
|
||||
To see what representative sets are available, consult the [representative sets](https://represent.opennorth.ca/api/#representativeset) endpoint.
|
||||
|
||||
Only the **bold** fields are present in all responses:
|
||||
|
||||
| Field | Example | Notes |
|
||||
| --- | --- | --- |
|
||||
| **name** | Stephen Harper | |
|
||||
| **district\_name** | Calgary Southwest | |
|
||||
| **elected\_office** | MP, MLA, Mayor, Councillor, Alderman | |
|
||||
| **source\_url** | The URL at which the data is scraped | May be the same as `url` below |
|
||||
| first\_name | Stephen | |
|
||||
| last\_name | Harper | |
|
||||
| party\_name | Conservative | |
|
||||
| email | example@example.com | |
|
||||
| url | https://legislature.ca/stephen-harper | The representative’s page on the official legislature site |
|
||||
| photo\_url | https://legislature.ca/stephen-harper.jpg | |
|
||||
| personal\_url | https://stephenharper.blogspot.com/ | A site run by the representative that’s not on the official legislature site |
|
||||
| district\_id | 24013 | If there’s an identifier besides the district name |
|
||||
| gender | M, F | |
|
||||
| offices | `[ {"postal": "10 North Pole, H0H 0H0", "tel": "555-555-5555", "type": "constituency"}, {"tel": "444-444-4444", "type": "legislature"} ]` | A list of objects with contact information for the representative’s offices. The keys are: `postal` (mailing address), `tel` (telephone), `fax` (facsimile), `type` (what kind of office this is, e.g. constituency or legislature). |
|
||||
| extra | `{ "hair_colour": "brown" }` | Any extra data |
|
||||
|
||||
This endpoint behaves like the [/representative-sets/](https://represent.opennorth.ca/api/#representativeset) endpoint. See its documentation for more examples.
|
||||
|
||||
If you would like to add an election to Represent, [contact us](https://represent.opennorth.ca/api/).
|
||||
|
||||
Get one page of elections
|
||||
|
||||
[/elections/](https://represent.opennorth.ca/elections/?format=apibrowser) Click to view JSON
|
||||
|
||||
This endpoint behaves like the [/representatives/](https://represent.opennorth.ca/api/#representative) endpoint. See its documentation for more examples.
|
||||
|
||||
Candidate lists may be incomplete or incorrect, as this information changes frequently.
|
||||
|
||||
If you would like to add candidates to Represent, [contact us](https://represent.opennorth.ca/api/).
|
||||
|
||||
Get one page of candidates
|
||||
|
||||
[/candidates/](https://represent.opennorth.ca/candidates/?format=apibrowser) Click to view JSON
|
||||
4
influence/README.MD
Normal file
4
influence/README.MD
Normal file
@ -0,0 +1,4 @@
|
||||
# README
|
||||
|
||||
Welcome to Influence, a tool for creating political change by targeting influential individuals within a community. This application is designed to help campaigns identify and engage with key figures who can sway public opinion and mobilize support.
|
||||
|
||||
0
influence/files-explainer.md
Normal file
0
influence/files-explainer.md
Normal file
60
influence/instruct.md
Normal file
60
influence/instruct.md
Normal file
@ -0,0 +1,60 @@
|
||||
# Instructions
|
||||
|
||||
Welcome to the Influence project! Welcome to Influence, a tool for creating political change by targeting influential individuals within a community. This application is designed to help campaigns identify and engage with key figures who can sway public opinion and mobilize support.
|
||||
|
||||
## Environment Setup
|
||||
|
||||
We want to deploy using a docker container. We use the new docker compose format. We use a `.env` file for environment variables. The developer likes to down, build, and up the container for testing / new features.
|
||||
|
||||
Wej are using NocoDB as a no-code database solution. You will need to set up a NocoDB instance and create the necessary tables for your application. Refer to the `build-nocodb.sh` file for the schema setup.
|
||||
|
||||
|
||||
## Project Overview
|
||||
|
||||
- **Purpose:** Create influence campaigns by identifying and engaging with key community figures over email, text, or phone.
|
||||
- **Backend:** Node.js/Express, with NocoDB as the database (REST API).
|
||||
- **Frontend:** Vanilla JS, Leaflet.js for mapping, modular code in `/public/js`.
|
||||
- **Admin Panel:** Accessible via `/admin.html` for managing start location, walk sheet, cuts, and settings.
|
||||
|
||||
## Key Principles
|
||||
|
||||
- **Separation of Concerns:** Keep logic for API, UI, and data management in their respective files/modules.
|
||||
- **Security:** Never expose sensitive credentials. All API calls to NocoDB go through the backend.
|
||||
- **Scalability:** Write code that is easy to extend (e.g., adding new location fields, new admin features).
|
||||
- **User Experience:** Prioritize clear feedback, error handling, and mobile responsiveness.
|
||||
- **Documentation:** Keep code well-documented and maintain the `Instructions.md`, `README.md` and `files-explainer.md` files.
|
||||
- **Modularity:** Use modular JavaScript to keep code organized and reusable; try to avoid large monolithic scripts keeping functionality separated by feature with files no longer than 500 lines.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
- `app/` - Node.js backend (Express server, routes, controllers, services, utils)
|
||||
- `app/public/` - Frontend static files (HTML, CSS, JS)
|
||||
- `app/public/js/` - Modular JavaScript for map, UI, auth, etc.
|
||||
- `app/controllers/` - Express controllers for business logic
|
||||
- `app/routes/` - Express routers for API endpoints
|
||||
- `app/services/` - Backend services (NocoDB, geocoding, QR code)
|
||||
- `app/utils/` - Shared backend utilities
|
||||
|
||||
## Development Rules
|
||||
|
||||
- **No inline event handlers.** Always use `addEventListener` in JS files.
|
||||
- **Update documentation.** Always update `README.md` and `files-explainer.md` when adding features or files.
|
||||
- **Consistent style.** Follow the existing code style and naming conventions.
|
||||
- **Error handling.** Always provide user feedback for errors (both backend and frontend).
|
||||
- **Environment variables.** Use `.env` for secrets/config, never hardcode sensitive data.
|
||||
- **Testing.** Test new features locally and ensure they do not break existing functionality.
|
||||
- **Pagination** Use pagination for API endpoints returning large datasets to avoid performance issues. For example, getAll should be getAllPaginated
|
||||
|
||||
## How to Add a Feature
|
||||
|
||||
**First look through the existing codebase to understand where similar logic is implemented.**
|
||||
You can find a full listing of the files in the `files-explainer.md` file.
|
||||
|
||||
When adding a new feature, follow these steps:
|
||||
|
||||
1. **Plan:** Decide where your logic belongs (backend controller, frontend JS, etc).
|
||||
2. **Backend:** Add/modify controllers, services, and routes as needed. Use NocoDB API via the service layer.
|
||||
3. **Frontend:** Add/modify JS modules in `/public/js`. Update HTML/CSS as needed.
|
||||
4. **Document:** Update `README.md` and `files-explainer.md`.
|
||||
5. **Test:** Manually test your feature in both desktop and mobile views.
|
||||
6. **Pull Request:** Submit your changes for review.
|
||||
@ -196,3 +196,12 @@ path.leaflet-interactive.cut-polygon {
|
||||
.crosshair {
|
||||
z-index: 1100;
|
||||
}
|
||||
|
||||
/*
|
||||
* Z-Index Hierarchy for Map Markers:
|
||||
* - Start Location Marker: zIndexOffset 1000 (highest - always on top)
|
||||
* - Temporary Location Markers: zIndexOffset 150 (during move operations)
|
||||
* - NocoDB Location Markers: zIndexOffset 100 (main database locations)
|
||||
* - Default: zIndexOffset 0 (cuts, other overlays)
|
||||
* - City Data Markers: zIndexOffset -100 (background - behind main locations)
|
||||
*/
|
||||
|
||||
@ -281,7 +281,7 @@
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn btn-danger" id="delete-location-btn">Delete</button>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
<button type="submit" class="btn btn-primary" id="save-edit-location-btn">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -398,7 +398,7 @@
|
||||
<button type="button" class="btn btn-secondary" id="cancel-modal-btn">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" id="save-location-btn" disabled>
|
||||
<button type="submit" class="btn btn-primary" id="save-location-btn">
|
||||
Save Location
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -119,7 +119,8 @@ class CutLocationManager {
|
||||
locations.forEach(location => {
|
||||
if (location.latitude && location.longitude) {
|
||||
const marker = L.marker([location.latitude, location.longitude], {
|
||||
icon: this.createLocationIcon(location)
|
||||
icon: this.createLocationIcon(location),
|
||||
zIndexOffset: 100 // Above City Data markers
|
||||
});
|
||||
|
||||
const popupContent = this.createLocationPopup(location);
|
||||
|
||||
@ -144,7 +144,10 @@ async function loadEdmontonData(force = false) {
|
||||
iconAnchor: [size/2, size/2],
|
||||
popupAnchor: [0, -size/2]
|
||||
});
|
||||
return L.marker(latlng, { icon: icon });
|
||||
return L.marker(latlng, {
|
||||
icon: icon,
|
||||
zIndexOffset: -100 // Behind NocoDB locations
|
||||
});
|
||||
} else {
|
||||
// Use circle marker for single units (round, red)
|
||||
return L.circleMarker(latlng, {
|
||||
@ -153,7 +156,8 @@ async function loadEdmontonData(force = false) {
|
||||
color: "#ffffff",
|
||||
weight: 1,
|
||||
opacity: 0.9,
|
||||
fillOpacity: 0.7
|
||||
fillOpacity: 0.7,
|
||||
zIndexOffset: -100 // Behind NocoDB locations
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -176,7 +176,8 @@ function createLocationMarker(location) {
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.8,
|
||||
className: 'location-marker'
|
||||
className: 'location-marker',
|
||||
zIndexOffset: 100 // Above City Data markers
|
||||
});
|
||||
|
||||
// Add to map
|
||||
@ -325,14 +326,6 @@ function createPopupContent(location) {
|
||||
export async function handleAddLocation(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Check if address is confirmed
|
||||
const { getAddressConfirmationState } = await import('./ui-controls.js');
|
||||
const { isAddressConfirmed } = getAddressConfirmationState();
|
||||
if (!isAddressConfirmed) {
|
||||
showStatus('Please confirm the address before saving the location', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData(e.target);
|
||||
const data = {};
|
||||
|
||||
@ -450,14 +443,6 @@ export async function handleEditLocation(e) {
|
||||
|
||||
if (!currentEditingLocation) return;
|
||||
|
||||
// Check if address is confirmed
|
||||
const { getAddressConfirmationState } = await import('./ui-controls.js');
|
||||
const { isEditAddressConfirmed } = getAddressConfirmationState();
|
||||
if (!isEditAddressConfirmed) {
|
||||
showStatus('Please confirm the address before saving changes', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the stored location ID
|
||||
const locationIdElement = document.getElementById('edit-location-id');
|
||||
const locationId = locationIdElement.getAttribute('data-location-id') || locationIdElement.value;
|
||||
@ -716,7 +701,8 @@ function showMoveConfirmation(lat, lng) {
|
||||
color: '#fff',
|
||||
weight: 3,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.5
|
||||
fillOpacity: 0.5,
|
||||
zIndexOffset: 150 // Above all other location markers
|
||||
}).addTo(map);
|
||||
|
||||
// Create confirmation popup
|
||||
@ -897,7 +883,8 @@ function createMultiUnitMarker(group) {
|
||||
});
|
||||
|
||||
const marker = L.marker([lat, lng], {
|
||||
icon: icon
|
||||
icon: icon,
|
||||
zIndexOffset: 100 // Above City Data markers
|
||||
});
|
||||
|
||||
// Add to map
|
||||
|
||||
@ -47,20 +47,18 @@ export function setAddressConfirmed(mode, confirmed) {
|
||||
button.classList.add('btn-success');
|
||||
button.disabled = false;
|
||||
}
|
||||
if (saveButton) {
|
||||
saveButton.disabled = false;
|
||||
}
|
||||
} else {
|
||||
// This is essentially what resetAddressConfirmation does
|
||||
if (button) {
|
||||
button.textContent = '📍 Confirm Address';
|
||||
button.classList.remove('btn-success');
|
||||
button.classList.add('btn-secondary');
|
||||
button.disabled = false;
|
||||
}
|
||||
if (saveButton) {
|
||||
saveButton.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Always enable save button - confirmation is now optional
|
||||
if (saveButton) {
|
||||
saveButton.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,7 +289,7 @@ export async function confirmAddress(mode) {
|
||||
// Check if already confirmed
|
||||
const currentlyConfirmed = mode === 'add' ? isAddressConfirmed : isEditAddressConfirmed;
|
||||
|
||||
// If already confirmed, just enable save
|
||||
// If already confirmed, toggle back to unconfirmed state
|
||||
if (currentlyConfirmed) {
|
||||
if (mode === 'add') {
|
||||
isAddressConfirmed = false;
|
||||
@ -299,16 +297,14 @@ export async function confirmAddress(mode) {
|
||||
isEditAddressConfirmed = false;
|
||||
}
|
||||
|
||||
// Reset button and disable save
|
||||
// Reset button but keep save enabled
|
||||
if (button) {
|
||||
button.textContent = '📍 Confirm Address';
|
||||
button.classList.remove('btn-success');
|
||||
button.classList.add('btn-secondary');
|
||||
}
|
||||
if (saveButton) {
|
||||
saveButton.disabled = true;
|
||||
}
|
||||
showStatus('Please confirm address again to enable saving', 'info');
|
||||
// Save button remains enabled - confirmation is optional
|
||||
showStatus('Address confirmation removed - you can still save the location', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -348,12 +344,7 @@ export async function confirmAddress(mode) {
|
||||
button.classList.add('btn-success');
|
||||
}
|
||||
|
||||
// Enable save button
|
||||
if (saveButton) {
|
||||
saveButton.disabled = false;
|
||||
}
|
||||
|
||||
showStatus('Address confirmed! You can now save the location.', 'success');
|
||||
showStatus('Address confirmed and updated!', 'success');
|
||||
} else {
|
||||
showStatus('No address found for these coordinates', 'warning');
|
||||
}
|
||||
@ -386,8 +377,9 @@ export function resetAddressConfirmation(mode) {
|
||||
button.classList.add('btn-secondary');
|
||||
button.disabled = false;
|
||||
}
|
||||
// Keep save button enabled - confirmation is now optional
|
||||
if (saveButton) {
|
||||
saveButton.disabled = true;
|
||||
saveButton.disabled = false;
|
||||
}
|
||||
} else if (mode === 'edit') {
|
||||
isEditAddressConfirmed = false;
|
||||
@ -400,8 +392,9 @@ export function resetAddressConfirmation(mode) {
|
||||
button.classList.add('btn-secondary');
|
||||
button.disabled = false;
|
||||
}
|
||||
// Keep save button enabled - confirmation is now optional
|
||||
if (saveButton) {
|
||||
saveButton.disabled = true;
|
||||
saveButton.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,11 +6,11 @@
|
||||
"language": "HTML",
|
||||
"stars_count": 0,
|
||||
"forks_count": 0,
|
||||
"open_issues_count": 19,
|
||||
"updated_at": "2025-09-02T21:31:48-06:00",
|
||||
"open_issues_count": 18,
|
||||
"updated_at": "2025-09-11T13:42:21-06:00",
|
||||
"created_at": "2025-05-28T14:54:59-06:00",
|
||||
"clone_url": "https://gitea.bnkops.com/admin/changemaker.lite.git",
|
||||
"ssh_url": "git@gitea.bnkops.com:admin/changemaker.lite.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2025-09-02T21:31:48-06:00"
|
||||
"last_build_update": "2025-09-11T13:42:21-06:00"
|
||||
}
|
||||
@ -1285,8 +1285,7 @@
|
||||
<h1>Power Tools for Modern Campaigns</h1>
|
||||
|
||||
<p class="hero-subtitle">
|
||||
Give your supporters instant answers at the door, on the phone, or in person. Turn your campaign website & knowledge into a searchable,
|
||||
mobile-first documentation system that actually works in the field or at the party. Add unlimited users, start as many campaigns as you can, and message all your supporters indefinitely with no extra costs. No corporate middlemen; your data, your servers, <strong>your platform</strong>.
|
||||
Complete political independence on your own infrastructure. Own every byte of data, control every system, scale at your own pace. No corporate surveillance, no foreign interference, no monthly ransoms. Deploy unlimited sites, send unlimited messages, organize unlimited supporters. Free and open source software for community organizers wanting to make change.
|
||||
</p>
|
||||
<div class="hero-cta">
|
||||
<a href="/phil" class="btn-primary">
|
||||
|
||||
@ -6,11 +6,11 @@
|
||||
"language": "HTML",
|
||||
"stars_count": 0,
|
||||
"forks_count": 0,
|
||||
"open_issues_count": 19,
|
||||
"updated_at": "2025-09-02T21:31:48-06:00",
|
||||
"open_issues_count": 18,
|
||||
"updated_at": "2025-09-11T13:42:21-06:00",
|
||||
"created_at": "2025-05-28T14:54:59-06:00",
|
||||
"clone_url": "https://gitea.bnkops.com/admin/changemaker.lite.git",
|
||||
"ssh_url": "git@gitea.bnkops.com:admin/changemaker.lite.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2025-09-02T21:31:48-06:00"
|
||||
"last_build_update": "2025-09-11T13:42:21-06:00"
|
||||
}
|
||||
@ -1285,8 +1285,7 @@
|
||||
<h1>Power Tools for Modern Campaigns</h1>
|
||||
|
||||
<p class="hero-subtitle">
|
||||
Give your supporters instant answers at the door, on the phone, or in person. Turn your campaign website & knowledge into a searchable,
|
||||
mobile-first documentation system that actually works in the field or at the party. Add unlimited users, start as many campaigns as you can, and message all your supporters indefinitely with no extra costs. No corporate middlemen; your data, your servers, <strong>your platform</strong>.
|
||||
Complete political independence on your own infrastructure. Own every byte of data, control every system, scale at your own pace. No corporate surveillance, no foreign interference, no monthly ransoms. Deploy unlimited sites, send unlimited messages, organize unlimited supporters. Free and open source software for community organizers wanting to make change.
|
||||
</p>
|
||||
<div class="hero-cta">
|
||||
<a href="/phil" class="btn-primary">
|
||||
@ -1341,8 +1340,8 @@
|
||||
<section class="problem-section">
|
||||
<div class="section-content">
|
||||
<div class="section-header">
|
||||
<h2>Your Canvassers Are Struggling</h2>
|
||||
<p>Traditional campaign tools weren't built for the reality of door-to-door work</p>
|
||||
<h2>Your Supporters Are Struggling</h2>
|
||||
<p>Traditional campaign tools weren't built for the reality of political action</p>
|
||||
</div>
|
||||
<div class="problem-grid">
|
||||
<div class="problem-card">
|
||||
@ -1383,14 +1382,14 @@
|
||||
<section class="solution-section" id="features">
|
||||
<div class="section-content">
|
||||
<div class="section-header">
|
||||
<h2>Documentation That Works</h2>
|
||||
<h2>Political Documentation That Works</h2>
|
||||
<p>Everything your team needs, instantly searchable, always accessible, and easy to communicate</p>
|
||||
</div>
|
||||
|
||||
<div class="solution-showcase">
|
||||
<div class="showcase-content">
|
||||
<h3>Communicate on Scale</h3>
|
||||
<p>Full email and messenger campaign systems and unlimited users</p>
|
||||
<p>Full email and messenger campaign systems with unlimited users</p>
|
||||
<ul class="feature-list">
|
||||
<li>Drop in replacement for mailchimp, sendgrid, etc.</li>
|
||||
<li>Track emails, clicks, and user actions</li>
|
||||
@ -1407,7 +1406,7 @@
|
||||
<div class="solution-showcase">
|
||||
<div class="showcase-content">
|
||||
<h3>Mobile-First Everything</h3>
|
||||
<p>Built for phones first, because that's what your canvassers carry. Every feature, every interface, optimized for one-handed use in the field.</p>
|
||||
<p>Built for phones first, because that's what your supporters carry. Every feature, every interface, optimized for one-handed use in the field.</p>
|
||||
<ul class="feature-list">
|
||||
<li>Touch-optimized interfaces</li>
|
||||
<li>Offline-capable after first load</li>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1285,8 +1285,7 @@
|
||||
<h1>Power Tools for Modern Campaigns</h1>
|
||||
|
||||
<p class="hero-subtitle">
|
||||
Give your supporters instant answers at the door, on the phone, or in person. Turn your campaign website & knowledge into a searchable,
|
||||
mobile-first documentation system that actually works in the field or at the party. Add unlimited users, start as many campaigns as you can, and message all your supporters indefinitely with no extra costs. No corporate middlemen; your data, your servers, <strong>your platform</strong>.
|
||||
Complete political independence on your own infrastructure. Own every byte of data, control every system, scale at your own pace. No corporate surveillance, no foreign interference, no monthly ransoms. Deploy unlimited sites, send unlimited messages, organize unlimited supporters. Free and open source software for community organizers wanting to make change.
|
||||
</p>
|
||||
<div class="hero-cta">
|
||||
<a href="/phil" class="btn-primary">
|
||||
@ -1341,8 +1340,8 @@
|
||||
<section class="problem-section">
|
||||
<div class="section-content">
|
||||
<div class="section-header">
|
||||
<h2>Your Canvassers Are Struggling</h2>
|
||||
<p>Traditional campaign tools weren't built for the reality of door-to-door work</p>
|
||||
<h2>Your Supporters Are Struggling</h2>
|
||||
<p>Traditional campaign tools weren't built for the reality of political action</p>
|
||||
</div>
|
||||
<div class="problem-grid">
|
||||
<div class="problem-card">
|
||||
@ -1383,14 +1382,14 @@
|
||||
<section class="solution-section" id="features">
|
||||
<div class="section-content">
|
||||
<div class="section-header">
|
||||
<h2>Documentation That Works</h2>
|
||||
<h2>Political Documentation That Works</h2>
|
||||
<p>Everything your team needs, instantly searchable, always accessible, and easy to communicate</p>
|
||||
</div>
|
||||
|
||||
<div class="solution-showcase">
|
||||
<div class="showcase-content">
|
||||
<h3>Communicate on Scale</h3>
|
||||
<p>Full email and messenger campaign systems and unlimited users</p>
|
||||
<p>Full email and messenger campaign systems with unlimited users</p>
|
||||
<ul class="feature-list">
|
||||
<li>Drop in replacement for mailchimp, sendgrid, etc.</li>
|
||||
<li>Track emails, clicks, and user actions</li>
|
||||
@ -1407,7 +1406,7 @@
|
||||
<div class="solution-showcase">
|
||||
<div class="showcase-content">
|
||||
<h3>Mobile-First Everything</h3>
|
||||
<p>Built for phones first, because that's what your canvassers carry. Every feature, every interface, optimized for one-handed use in the field.</p>
|
||||
<p>Built for phones first, because that's what your supporters carry. Every feature, every interface, optimized for one-handed use in the field.</p>
|
||||
<ul class="feature-list">
|
||||
<li>Touch-optimized interfaces</li>
|
||||
<li>Offline-capable after first load</li>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -2,146 +2,146 @@
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://cmlite.org/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/test/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/adv/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/adv/ansible/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/adv/vscode-ssh/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/blog/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/blog/2025/07/03/blog-1/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/blog/2025/07/10/2/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/blog/2025/08/01/3/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/build/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/build/map/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/build/server/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/build/site/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/config/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/config/cloudflare-config/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/config/coder/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/config/map/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/config/mkdocs/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/how%20to/canvass/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/manual/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/manual/map/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/phil/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/phil/cost-comparison/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/services/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/services/code-server/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/services/gitea/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/services/homepage/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/services/listmonk/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/services/map/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/services/mini-qr/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/services/mkdocs/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/services/n8n/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/services/nocodb/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/services/postgresql/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/services/static-server/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmlite.org/blog/archive/2025/</loc>
|
||||
<lastmod>2025-09-04</lastmod>
|
||||
<lastmod>2025-09-11</lastmod>
|
||||
</url>
|
||||
</urlset>
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user